Transcript
This transcript was autogenerated. To make changes, submit a PR.
Jamaica make a real
time feedback into the behavior of your distributed systems and observing
changes exceptions errors in real time allows
youd to not only experiment with confidence, but respond instantly
to get things working again cost
everyone and welcome to this talk. Let's build at zero cost inviteonly
website with nextjs and airtable so
before we get started, let me tell you that the slides are already
available online. You can get them by either scanning this QR code or visiting
the link that you see down there in the slide. And the reason why I
like to share the slides first is because I'm going to be showing you a
bunch of code examples youd might be having questions after the
talk, so it's just easier. It's just an easy way to
get all the material if you enjoy this talk. But don't
worry, you'll have the link to the slides even at the end of this presentation.
So what's our mission for today? Today we want to build this
inviteonly website and to understand a little bit
better what that means, lets me give you literally a 15 seconds
demo of this particular idea.
So you can see in this particular slide that we
have on the left side, kind of a spreadsheet
which acts as our database, and there are different
records. Every record has a unique id, and we can use that particular unique
id as a query string parameter here on the website on the right hand
side. And every time we change that query string parameter, you can see that the
invite changes a little bit. We can see in this case Rafaelo.
Before there was a lo Michelangelo and now there is a lo don
adelo. So basically you can have URLs that are customized
for specific people. And if you provide the wrong invite code or
you don't provide an invite code, you shouldn't be able to see anything. So this
is just a simple way to have a database of people that we want to
invite to an event and build a website that allows
only these people to be able to access all the information related to
that particular event.
Okay, so what are the requirements for building something
like this? Imagine that we are actually building this because we really want to host
a particular personal event. We want to invite a few people.
So we just, as software engineers, we want to have a
custom cool website. But of course we don't want to be spending months in building
all of this. So the idea is that we want to iterate quickly. We need
to have a solution that is very simple to host, very simple to maintain
and update. We want a very lightweight back end and
also imagine that you are organizing this with a bunch of friends,
so probably you want to be able to share with them the entire database
because maybe they want to add people to the invitation
list. They want to see whoever is saying that
is participating to the event.
So we need also a back end solution that's very easy to
access for nontechnical people. And now the best
part, we probably want to build all of this in a cheap way and
aust it if not cheap for free.
So let's see if we can find a solution that satisfies
all of these requirements.
But before we get into that, I didn't introduce myself,
so let me do that. My name is Duchano and I am
a Microsoft MVP and a certified AWS solution architect professional.
I work for a company called fourtheorem as a senior architect and
I'm based in Dublin, Ireland. One of the things that I'm
mostly proud about in my career is that I am one of the co authors
of this book called Node JS Design Patterns. If it's a book that you have
read, definitely let me know what you think. I always love to hear feedback and
if you haven't read that already, consider reading it.
I really love to hear what you think about that.
And feel free to connect with me. I am available in all this channel.
So always we're looking forward to talk with people. So don't be shy.
Feel free to reach out and let's connect.
Okay? But fourtheorem, the company that I
work for, it's a service company. We are mainly focused on
cloud and AWS. We do a lot of serverless and cloud migrations.
So if that's something that you are really interested into,
definitely reach out to us. We are always hiring, we are always looking
for new, interesting projects. So again, don't be shy, let's have
a coffee and let's chat about business together.
Okay, back to the talk. What's the agenda for today? So the
first thing we want to do is of course choose a particular tech stack that
could satisfy all the requirements we discussed before.
Then we want to understand what is the data flow for this particular kind of
application. So we are going to deep dive into that.
We are going to be using airtable. So we are going to be discussing a
little bit how airtable works and how can we use it as a database
for a web application. And then we are going to be looking
into nextjs and versailles, which are other two components that we are going
to be using in our architecture. So we want to see how to create
APIs with NextJs we want to also create
a custom react hook for authentication. So we are going to be
discussing all of that, and we are going to be seeing how can
we allow the users to interact with our application.
And now the user interaction can actually affect our database.
For instance, users will be able to tell us if they are
participating or not to our event, and when they select
one of the options that should be immediately reflected in our
back end system. And finally, we are going
to be discussing very quickly about some interesting security considerations.
After all, we are trying to build a website that by default is private,
and only people who have access to that particular website through an
invite code should be able to see meaningful information. So there
are some security consideration there, and we need to make sure
that we are not leaking any private information to people that don't
really have an invite code.
Okay, let's look at the tech stack. We said that we are going to be
using next js, airtable and Purcell. Also we are coming to
be using GitHub as a hosting for all the source code.
So the reason why I like this combination is because next JS,
if you know, react, it's a very nice and
simple framework to use. It gives you the opportunity to do
not just the front end code, but also to create APIs very easily.
And when you use Vercel as a backend, not surprisingly,
because nextjs is built by Vercel as well, it's very very
easy to take your code from GitHub and ship it to production
and then have everything working but of the box with very minimal
configuration for what concernsafetable.
It's a very nice tool that gives you like a spreadsheet kind of experience
where you can create very lightweight databases,
but also there are APIs so you can use that database and
that visual experience as a backend system
for an actual web application. When you combine that with APIs
and there is a very generous free plan. So for
most of the this is not true just for
airtable, but also for Vercel. So the idea here is that with this
particular tech stack, we should be able to host an application that
doesn't have a huge amount of traffic pretty much for free.
So this is kind of one of the reasons why I like this
stack. Because for this kind of site projects where youd may be creating an
invite page for a private party, you are not going to be spending
any money in hosting and you can get everything up and running pretty
quickly. Okay, so making
an XJS website private is maybe not something that you
would want to do every day, because most of the time you are probably building
public websites, I don't know, marketing pages, landing pages,
SaaS application. So what does
it mean to actually make next JS website private?
After all, it's going to be hosted in the public Internet,
because we just want to give people a public URL and
they can access to that. So how do we actually make this thing
that even though it's everything public facing and publicly accessible
to the Internet, only people with a valid invite code can actually
access the information that we want to disclose.
So the idea is that every guest should see something different.
This is actually another requirement that we want to use. If you remember
from the demo we had Michelangelo Elo Rafaelo
elo donatello. So every guests will get a customized
page, and people that don't have an invite
code should not be able to access any content. So they should be seeing only
an error page that says something like, sorry, you don't have access here.
So how do we make all of that happen when we use next
js? So the idea is that we have a react
single page application. This is what nextjs does for us,
allows us to easily build a react single page application.
Now, in this single page application, what we have is that the ability
that if we pass a specific query string parameter,
we can actually read it after the application bootstraps.
So the application bootstraps, and in that state it doesn't display anything.
So the first thing that it needs to do is to check the current URL,
load the secret, so the invitation code from the URL.
And at that point what it can do is to call an API
in the backend, which is going to be hosted on Vercel, and it's going to
tell, okay, I have this invite code. Is that invite code valid?
And the API in return needs to look up into airtable
and check, do I have any invitation with this particular code? So it's
going to try to read data from that particular airtable spreadsheet.
And if airtable says yes, basically what
happens is coming to show the page to
the user. Say, for instance, hello, Michelangelo, you are invited.
Or if airtable says, sorry, I don't have this invitation code,
that basically means that that invitation code is made up. It's somebody
that is trying to see, is trying to guess an invitation code,
or maybe it's an invitation code that we have removed. So in that case,
we need to display something like access denied. So you can see
that there is a kind of a client side check
for given this particular secret, what could
I display? And then that client side application
needs to do API calls to actually make sure to check,
is this invitation code something valid or not? Can I display some information
or should I display access denied? So the first thing that
we need to do to start to implement this solution is to organize the data
in airtable. Now if you never use airtable before,
this is pretty much the idea you can imagine as a dynamic CSV
file with APIs. So we have records there.
Every line contains a UUID, which is the unique
invite code for every guest, and every record
associates that particular build with guest information, for instance
their name. In this case we have the favorite color. We have weapons,
for instance, just to showcase one
particular characteristic that you might have for guests.
And if we want to understand better
what airtable calls all the different things,
we have a base which is pretty much a project. In this
case we are calling it secret pizza party. Then we have the concept
of a table. In our project we only have one table,
which is called invites, but you could be having multiple tables,
and then in every table you have multiple fields. In our case we have
invite name, favorite color, weapon,
and then we have records. For instance, here we have invited four
people, so we have four different records. How do
we start? Now our Nextjs project one of the things
that I really like in nextjs is that there is a very nice
system to bootstrap applications, and you can even specify
whether you want to use typescript or NPM or yarn
or different other tools for dependency management.
So how do you do that? You just say MPX create nextjs app
and you can even specify a version and then you can pass extra parameter.
And after you do that, basically you're going to be having a project
structure already created for you. Now note here that
I'm using next twelve. NextJs 13 has been published
very recently. I haven't tried yet, so I'm sticking
with next twelve. But I don't expect to see big
changes if you use next 13.
Okay, the next thing that we want to do once we have the project started,
because we are using typescript, we can start to create some types that will be
beneficial for us while we build all the front end and
back end for our application. So in our case we want to create
an invite type which contains more or less the same fields that
we had in airtable. Note that here I also have an
optional field called coming, which is basically something we can use
to track the preference of that particular guest to
see if they are coming or not.
Another thing that we need to do to be able to use airtable in our
backend in our nextjs APIs is we need to have API
keys and base id, which are things that we can find in the configuration
of airtable. So the idea here is that we install
the airtable SDK, this is an official package from airtable,
and then we export these environment variables so that we
can use them to actually initialize the client and connect to our
own account.
Now let's have a quick look at the APIs of airtable.
And this is actually one thing that I really like about airtable, because if you
look closely at these APIs, they are
already giving you a documentation page that is already customized
with all the fields that you have in your project. So this is not
just showing a random set of examples, but these are actually real examples
that will work with your particular project structure.
So for instance here, if we zoom a little bit, we can see
that in this example, after we load our
table, the SDK, we initialize a client with the API
key and the base id. You can see that this example
already had invites, which is the name of our table,
and it's showing us how to do a select in that particular table.
So now this is not really very modern JavaScript.
It's not using ESM, it's using VAR rather than constant let,
it's using callbacks. So we are going to be doing something a little bit more
polished, but I still like the idea that this example
is actually fine tuned to your actual data and not some generic
example. Okay, so let's try to do
something ourselves. So what do we do? We want to use ESM,
of course, because we want to write modern javascript. So we're going to be using
import syntax, we are coming to be importing airtable
and our invite type. And then
what we do is we make sure that we have initialized our
environment variables correctly. So if one of those environment variables
is missing, we are not going to be able to call airtable
APIs. So we need to make sure that these environment variables are set.
If not, we're going to be throwing an error. At this point we can
initialize our airtable client and we can
define the base, the project that we want to reference to from
this client. Now all of this code is a utility
file that we created under Udil's airtable ts.
So this way what we can do is easily import
this client and use it somewhere else.
Or another idea is actually that we can export
specific functions. So we build kind of our own data model
layer in this particular file. And for instance, the first
thing that we want to build is if I have an invite code,
can I retrieve an invite for that particular invite
code? So we can create a function here
which returns a promise. And this function,
what it does is basically uses the SDK that we initialize
in this file to do a select. Now this select,
I'm not going to be spending too much time, but you can imagine that it's
just checking in this particular table, if I use this invite
code, is there
a record that has a matching invite code? And we only want
to get one record at a time. That's why we have max record
equal one. Now don't worry too much about this
escape function. It's something that we will be discussing at the end. Just keep it
in the back of your mind. Then what we do, we take the first
page and we want to make sure that if there is an error,
we reject the current promise.
Or if
we get one page, but this page doesn't contain anything, that means that
we didn't find an invite. So that's also something we consider an error.
And finally, if we found an invite code, then what we do, we map all
the data from airtable into an object that represents our data
model in our application.
Okay, the next step is now that we have built all
this data layer that allows us to interact with
airtable and fetch data from airtable. Specifically,
if we have an invite code, we can fetch all the data for that particular
invite. What we need to do next to build an app inside
next JS API layer and that API
needs to expose that information to our
front end layer. So how do we build APIs with
nextjs? This is another thing that I really like in nextjs
that is very easy to build APIs, you just need to create a file inside
pages API. So there is a very specific folder structure in next
JS. It's file based routing, it's called.
And basically the idea is that if you create for instance in this
example a file called, that means that
your application will be exposing an API underapi.
Hello. So if we have this application running in localhost,
we are going to be having something like localost app law.
If we have it running on the web, somewhere there will be some domain name
app, then the interface is
basically saying you need to import this next
API request and next API response. And at this
point you can export an andler and the under takes
a request and a response and then you can use the response to return some
data to the user. In this case we are just returning
200 successful response where the body
is a JSON object containing message hello world. This is the
simplest kind of API you could see. But of course this is not what we
want to build. We actually want to build an API that takes
as input an invite code and it returns all the
information related to that particular invite.
So how do we go and do that? So we want to
create a new endpoint so it needs to live inside pages API
and we are going to be calling it invite. So it's
going to be called invite TS. Now the
first thing that we do, we import our type and we
import our utility from the airtable file we created before.
That allows us to fetch involves using an invite code directly
from airtable. Then we need to create an
andler and this handler will return an invite response.
And there are a few things that we need to check.
For instance, if this request is
not a get, sorry, we are going to refuse because we
only want to accept get and we are going to return
a 405 saying method not allowed. Then if there is
no query string code, this is also another error because we don't
have an invite code. So we are going to be returning a 400 saying
missing invite code.
Now the next thing we want to do is that we need to normalize the
code because in nextjs when you pass query
strings, you might be having the same query string repeated multiple times.
For instance, you might be having the same code repeated more than
once. So what we want to do is to make sure that we take
only the first instance of that code.
And then what we do is we try to read the invite
code using our airtable helper. If we can fetch the invite
code, that's what we return. Otherwise there is an error and
we need to handle that particular error. Either it's an invite
not found or there is a server error and we will be propagating the
error accordingly to that.
So how do we test this? If we have the application running locally,
we can just do a call with an invite code that we know exists and
we should be seeing something like this. So our API is
using a JSON interface. We pass an inviteonly code, we get back a
JSON response with all the details associated to that
particular invite that are coming from airtable.
Now at this point we have everything at the backend layer.
So we have airtable that contains all the data is effectively our
database. We have an API layer that allows us to read data
from that database layer. Now we want to work on our single page
application. So the front end side and the front end side needs to manage
all the lifecycle to bootstrap the application, read the inviteonly
code from the query string, call the API that we just implemented,
and then based on the response of that API, it needs to
display either a page successful page with all the user information,
or it needs to display an error. So we are effectively implementing an
invite validation workflow in the single page application.
So what's the attack plan? So when the SPI loads, we grab the
code from the URL, we call the inviteonly API with that particular
code. If it's valid, then we render the
content of that particular invite. If there is
an error, most likely the invite
code is not valid, so we need to render an error page.
So how do we manage all this data fetching lifecycle in
react and next js? So there are different options,
and I've actually tried to implement this in a bunch of different ways.
The first idea might be we might have a top level component,
for instance app, and we can do all this loading in
that top level component, for instance by using a use effect hook.
Another approach could be we could be creating a context provider.
It's a quite common concept in react.
You can create a context that allows you to expose
some data to your entire application. So that context could take
care of doing all this kind of interaction with our backend APIs,
and eventually it's going to expose some data to the rest of the application
and the rest of the application will render accordingly to the data in
the context. Another idea could be we could be implementing a
specialized react hook. So a custom react hook that takes care
of doing all of this. And this is actually the idea that I like the
most, and I think the one that gave me the
most concise code from a consumer perspective. So it's probably
the easiest solution when you need to integrate it into your
actual application. So the next question is, if you
never built a custom react hook, how do we go and do
that? And a custom react hook is
just a javascript function that had the
name starting with the word use. This is kind of a convention
in react, use effect, use state.
So if you build your own hook, it's going to be called use
something. And this particular
function can also call underneath other hooks. So when we build this
function inside that function, we can use use effect, use state,
use callback, use memo, any other hook
from the react core, or even other custom hooks.
It doesn't need to have any specific signature. So inside the
function, all the common rules of hooks apply. Hooks are
nice to use, but there are some weird rules that you need to
get used to. And when you build a custom hook, the same rules apply.
And the idea is that youd can only call hooks at the top level of
the function. Every react component is effectively a
function and youd can use use state use effect only at the very top
of that function. And when you call use
state use effect or any other hook, they cannot
be inside loops, condition or other nested functions.
And the idea is that every time the component renders, the hook
needs to be always called and always be called with the same order
as opposed to all the other rooks in that function. Now these rules
are defined by react and this is just the way hooks are implemented
in react. And you can find some more details and examples in the
official documentation.
Okay, so how do we create a custom hook?
The first thing we want to do is of course to import our
invite type and then we will have
our fetch invite function, which basically I'm
not showing it here just for brevity, but the idea is that
this is going to be doing a fetch request to the backend API
and it's going to return an invite response or an error.
And now this is actually our custom hook use invite.
So what is the idea of this hook? That when we
want to create a component that wants to
check if a user had a valid invite code, we can
just use this hook. We can just say use invite pass an invite
code and then that hook will give us back
the invite response.
So we have a state that this hook needs to maintain,
some state inside because basically it needs
to say is there an invite associated to this
invite code? And if there is, it's going to be stored inside the
state of this particular hook. Also there might be errors.
So we have another state which is whether there are errors or not.
And then the next thing that it needs to do is to,
when this hook is actually being initialized
in a component, we want to trigger a particular
action. So there is an effect that needs to happens. And inside this
effect what we do is we try to read the code from the query string
parameter. We try to see if there is a query string
parameter called code and we read that particular one.
If there isn't, then that's an error. No code was
provided. We can set the internal state of this hook as
there is an error and the error is that there is no code. Otherwise we
need to fetch the invite. So we are going to be calling our other function
that makes an HTTP request. And if that HTTP
request succeeds, we set the invite response. If it fails, we set
an error with the message of that particular error, and at
that point the hook returns two values in an array invite
response and error.
So how do we use it? It's actually very simple.
Let's say that we have some example component, this is just a react component
and this component is just going to call use invite.
And then inside use invite we are going to be having either an invite
response or an error. Now keep in mind that
all this stuff is asynchronous because use invite will
need to load some data, will need to do HTTP requests
in the background. So we will have here different potential
states. One state is when all the request was completed,
but it was failing with an error. So we need to check, okay, if there
was an error, we need to display the error. If there
wasn't an error, but we don't have an invite response either. That means
that we are still loading the data from the API, so we need
to display some loading information, a spinner or something
like that. And finally, the last date is when we
actually completed everything successfully. So we have some data and
we can render the page with the data.
Okay, now that we know how to build a custom
MOOC that can be used in a component, to actually make sure that we render
information only when the inviteonly code is valid, and we render
an error when the inviteonly code is not valid. The next step
is what if we want to collect some user data? Now we are displaying a
page for every single user, but we want to know if that
particular user is going to attend our party or not.
So we want to allow the user the opportunity to submit data
to our back end. How do we do that?
First of all, we need to do some changes, so we need to
add new fields in airtable. For instance, we can add a
new field called coming, which allows us to store the information
for every single invite in that table.
If the person invited is attending or not,
then we need to add new backend utilities in our airtable helper
to be able to update our table.
When we receive the information from the user, we need to add a new endpoint
to our API and then we need to update our react hook
to also be able to not just read the data related to
an invite, but also be able to change some of the data related to
that particular invite. So adding a field to airtable
is easy. It's a visual tool youd just click and say, add a new
field. And you can also select a particular kind of field where
you have only specific choices. For instance here it can either
be empty or it can be a yes or a no. So there are
three potential states in this particular field.
Now let's build first our RSDP utility
so a user can say whether
they are attending or not. So what we want to do is to
basically add a new piece of functionality
in our airtable helper that basically receives an
invite code receives an or SVP state,
which can either be true I am attending or false I'm not
attending, and it returns a promise. So what it
needs to do is it fetches the particular record for that invite
code and what it does, it calls this
function called update with an id,
and it specifies the fields that needs to be updated.
Now if everything is fine, we resolve. If there is
an error, we reject. Nothing particularly complicated here.
How do we create a new endpoint to be able to rsvp?
So we need to create a new file inside pages API
RSVP. So that means that we will be having an
API endpoint called API RSVP.
And here what we want to do is very similar to
what we did before, but this time we want to
accept only put requests. So if the request is not a
put, we return a four or five. If there is no code
in the query string. Again, we need to have an invite code to be able
to update only that particular invite.
So that's also going to be an error if we don't have an invite.
Now at this point what we need to do is we
are going to be receiving in the request body the
RSVP field. So imagine that we are
going to be building later on some interaction in our UI. That UI
will do a request. It's going to put in the put
payload body a JSON object that represents
for that particular user if they are coming or not.
Imagine in a more advanced application you might be having a bigger
form, maybe with multiple fields. So in the body you might
be having multiple fields. In this case we only have one particular field,
which is going to tell us true or false.
Again, we need to sanitize the query
string. We need to read only one code if there are multiple ones.
And then finally what we do, we call our update RSVP using
the request body coming. And if everything is
fine we return a 200. If there is an error, either the
invite was not found, so that's a particular error,
or there was an internal server error.
So now we need to update our hook to be able to
not just load the invite information, but also
to expose some piece of functionality that allowed us, our single page
application, to update the back end every
time that the user does different kinds of interactions.
So basically before we were just returning
an array with two values,
the error and the invite response. Now it makes sense to
create a proper object because we have more fields.
So the fields that we want to use in addition to the existing
ones is updating, which basically tells us if
the current state of the hook is sending
a request to the back end to update the data. And then
we have another function that can be used by our front end
to actually trigger the update. So when the user tricks
in the coming UI saying I'm coming yes or no,
the front end is going to be able to call this particular function to actually
trigger an action to the backend. So the idea of this
hook is that basically we don't want to expose to whoever
is utilizing the hook all the business logic that is required
to update to interact with the backend. But we just want to
expose an interface and that interface is part of the hook
return data.
Okay, so the next thing that we do
is fill the diesels here and
basically the use invite now is slightly updated
and the changes that we are making is that now we also
have a new additional state which is updating setup
dating. So this is the new piece of information that
we can use to show, for instance a spinner. When the user clicks on
preference for RSVP, we need to show a
spinner while that request is
in progress. And then when updating is not
false, then at that point we can hide the spinner use
effect. Is everything the same. This is used to load the invite
from the backend and this doesn't change.
Then we have a new function called update or SVP. And what
this does is basically checking if there is an invite response.
We want to set the updating field, the updating state
to true because we are about to make a request to the back end and
that request is going to update our table.
So while that request is in progress we need to show the spinner.
At this point we actually do the request and when we receive
a response we are going to be updating our
invite response in the state of the
hook. And finally we set updating to false.
So again, the idea is that this hook needs to somehow
take care of managing all the lifecycle of this data.
This invitation object can change and it will take care
of keeping track of the current value for that invitation
object. Keeping track of if there are any error, keeping track,
if we are in the process of updating the backend and all this
information is available through the hook, so we can use this information to
render different things in our UI.
So finally we can use this hook. And this is just a simplified version
of our actual application. So what we do is
we call use invite, and this time we receive back an
object, and from this object we can destructure the four properties
that we want to use, invite response, error,
updating and update, or SVP. Now if there was an error,
we need to display an error. If there is no invite response, it means
that we are still doing the initial loading from the back end of
that particular invite. So we display a loading information.
Then we create an andler. And this andler says when the
user clicks on a preference whether they are coming or not to
the party, we want to involves this handler. And this andler is
going to read the current preference and use it to update the
back end. And finally we have our form, and in
this form we attach the handler and
we have effectively two checkboxes and the user can
say, I'm coming true, I'm coming false. And every
time they change that preference, it's going to make a request to the back end
and it's going to update the table in airtable.
So that's everything we need to do. Let's see 15 seconds
demo of that. So here I have my airtable
on the left side and the application running on the right side.
So what we want to do initially, we don't have any code, so what
we want to do is to load the first user, the first invitation.
So here we have Leonardo, and Leonardo can say, okay, I'm coming.
Okay, let's try the second one.
Now we have another invitation, and this time we say no.
And this is updating in real time, as you can see.
The third one, we can say no again,
and it's updating in real time. And the fourth
one is going to be saying yes.
And you can see that if I change the preference also this is reflected
more or less in real time. So the UI is making
sure to call the hook, the hook is calling our next JS
backend API. The next JS backend app is connecting
to airtable. And this is how we keep things in sync between
our front end and our database.
How do we deploy all of this? This is actually the easiest part,
because if we use GitHub and Vercel, the only thing that you need to do
is create an account on Vercel using your own GitHub credentials.
So you do an Oauth login using GitHub, and at that
point there is just a few steps that you need to do
to basically say select the repository where your source
code lives. And because that's going to be an XJs application,
Versaille already recognizes that and makes it available
on Versailles, and you get a custom URL
which contains something like Versailles app at the end.
But if you want to use your own custom domains, you can also connect a
custom domain. Okay, now that
we know how to deploy the application, we are pretty much done,
except that I mentioned that one of the last topics I wanted to cover
is some security considerations. Again, we are building a website
that effectively contains some private information. We don't want to
disclose all of that information to everyone in the world. We just want to
make sure that people that have an invite code can see that information.
Other people shouldn't be seeing anything private.
So what kind of problems can we have? How can people actually
try to exploit our website even if they don't have an invite,
they can try to extrapolate some useful information and maybe they
show up at the party even if they are not invited.
So the first thing that we need to be careful is that because
we are building a single page application, most of the information that
we have in our website is effectively going to be compiled inside the client
side javascript that then eventually is made available by the
browser. So if we have a clever user that knows how
to use the debugging tools and read the original source code in
JavaScript, they could be actually finding some sensitive
information. For instance here you can see that we are highlighting
where the event is taking place, the day when it's taking place. And this is
already a quite sensitive piece of information.
So even if they don't have an invite code, because you can see here that
the UI saying invalid code, all that information is still embedded
in the JavaScript bundle. So they can actually look into that
and extrapolate some sensitive information. So this
is probably something that we want to avoid, but how can we avoid that?
And there might be different ways to fix this, but the idea
is that if you had code, anything inside
your react templates, inside your JSX,
that piece of code will end up in the compiled
JavaScript bundle. And that JavaScript bundle is something that is
surfaced to the user regardless if they have an invitation code
or not. So the first thing to do is don't hard code
any sensitive information in your JSX. You should
be using some placeholder in your template, and then you
could populate that placeholder only when you verify
that the user had an invitation code that is valid.
So one idea to do that is basically to update
our API to return with the invitation
data. Also, a set of messages that then can
be interpolated in the UI.
For instance, I don't know, the time
and the place where the party takes place can
be part of the response that we receive only when we
have a valid invite code. So only the app is going to be
aware of this information. The front end only needs to interpolate
that information. If we do that, the Javascript
bundle that we are going to generate is not going to display this information.
It's only going to display some placeholder variables that
cannot be known by a user that doesn't have an invite code.
If you want to see this working, actually I'm going to be giving youd
a repository with the complete example, and I'm implementing
this solution there so you can actually see a working complete implementation
of this idea.
Now, a slightly more complex problem, and this is something that
I was actually figuring out and I reported it to airtable, because this
is something that can be very dangerous. If you ever
used SQL, you probably know of the term SQL injection,
which is basically when you have user data that gets interpolated
into a query, a user, a malicious user, can actually give
you very specific pieces
of information that try to alter the shape of that query to
do something that the user was not really intended to do. For instance
here, even if we are using airtable,
because airtable has a query language and we are using user
data, which is the invite code, which is coming from a query
string parameter. So something that the user could change if they wanted
to. We are basically interpreting that information
in our queries. So we need to be careful that the
user is not really trying to inject anything malicious to
try to break our query. If a user doesn't know
an valid invite code, what they could be doing is
to try to craft an invite code that manipulates our query to
basically say invite equal true.
So an invite should match that particular type of query,
and if they are able to generate the kind of query, they're basically
getting access to our application without knowing an
actual valid invite code. So in a way we are trying to do,
or at least here trying to prevent any injections in
the formula, this is the name that query I'll call in airtable
that the user could be doing to manipulate the formula to their advantage.
So imagine here that we
have an escape function. I mentioned it before, but I didn't show you the code.
Imagine that we didn't have the escape function. This is what
our code could be. It's like leader is saying the invite in the
table needs to be the same as the invite that we are receiving from
the user. Again, keep in mind
that this invite code is something that the user is giving us. So in this
particular string interpolation, the user can do
something to actually change the structure of this particular query.
Let's see an example. So this is actually a valid example.
When the user is giving us this code, what happens is that we
build the following formula, which is totally valid and there is no problem
with it. Now, if there
is a malicious user that gives us this particular query,
in the query string, they put this particular code, which if you
decode it, pretty much looks like this. So code is equal
to an open
string where we say major, equal to zero and something
else. And basically if we interpolate this particular
string, what we get as the final formula is
pretty much this. We are saying the invite is
empty, needs to be measured
or equal than zero and empty. And this is, don't worry
too much about the meaning of this, but the problem with this is that this
formula is always going to be true for every single record.
So if table formula is just going to match whatever record come
first in our table, and what happens if we actually
try to put that into our URL, is that a
user is going to have access straight away and
is going to get any arbitrary user, in this case Donatello,
because it's probably the first one that matches in our table.
But a user without a valid user code was able to actually
get access without a valid user code by crafting
this particular injection. So how can we
prevent this particular flow? And we need
to use an escape function. And unfortunately
airtable doesn't give you an escape function by default. It's something that I
lied to them and hopefully they will be working on it in the future and
provide as part of the SDK, an official sanitization or escape
function. So this is just my own implementation.
But be careful that this implementation might not be 100% bulletproof.
So make sure to review it. And also if you have a similar use case,
make sure to reach out to s tableau because they should be eventually providing
their own custom, sorry, their own official
tested escape functionality to avoid injections
in airtable. So we are done pretty
much with this talk. Let's try to recap and wrap things up.
What are some of the limitations that you might encourage with this solution?
Airtable, when you use the free plan, had a very
strong rate limiting. You can only do five requests per second.
And keep in mind that when we update our record we actually do two
requests because we need to make sure that the invitation code is correct
and then we update the invite code.
So that could be one of the problems. What are some of the
alternatives? You could be using Google spreadsheets. There is also an API and different
packages on NPM. You could be using DynamoDB with amplify.
You could be using Firebase, any atlas, CMS,
Subabase, Strapi. There are many many alternatives that you could
be using as something that could replace airtable
in case you don't like airtable. And most of these things are either
free or there are very generous entry level plans.
So for your small website you're not going to incur in massive costs.
So what are the takeaways from this talk that
we just saw? A case study for a quick and easy and cheap solution
that you can use if you need to build an invite only website.
We learned about next js. We learned specifically about the API endpoints,
how you can build custom react hooks, how can you integrate with backends
such as airtable using their own sdks? And we
also learned a bunch of security related things. Now this solution
is just one of the possible solutions and for a very specific use
case, don't just copy paste it for any other similar use case,
always make sure to evaluate your context and assess whether
your context fits this particular implementation.
Probably if you just copy this code it's not going to fit your
use case. You will need to change something. So the takeaway here
is just take some of the lessons and then try to apply them to your
code base. Don't just copy paste code blindly
and I have written all this information in an article. Probably there are
even more details there and the code that is easy to copy paste. Also,
as I said, there is an entire code base that works with the example
I showed you before on GitHub. So you can check these two links
if you want get all the code, or if you want to read the same
context that I explained here in the form of an article.
And that's everything I have. Thank you very much. I hope you found this
interesting and if you have any questions feel free to reach out to me and
I'm more than happy to try to answer your questions.