Conf42 JavaScript 2022 - Online

Let's build a 0-cost invite-only website with Next.js and Airtable!

Video size:

Abstract

Imagine you are hosting a private event and you want to create a website to invite all your guests. Of course, you’d like to have an easy way to just share a URL with every guest and they should be able to access all the details of the event. Everyone else should not be allowed to see the page. Even nicer if the website is customized for every guest and if you could use the same website to collect information from the guests (who is coming and who is not). Ok, how do we build all of this? But, most importantly, how do we build it quickly? How do we keep it simple and possibly host it 100% for FREE? I had to do something like this recently so, in this talk, I am going to share my solution, which involves a React SPA (built with Next.js & Vercel) and AirTable as a backend! In the process, we are going to learn some tricks, like how to build a custom React Hook and how to protect our app from AirTable query injection (yes, it’s a thing)!

Summary

  • Jamaica make a real time feedback into the behavior of your distributed systems. observing changes exceptions errors in real time allows youd to not only experiment with confidence, but respond instantly to get things working again. Let's build at zero cost inviteonly website with nextjs and airtable. The slides are already available online.
  • Software engineers want to build an inviteonly website. The idea is to have a database of people that we want to invite to an event. We need a solution that is simple to host, very simple to maintain and update. We also need a lightweight back end.
  • Duchano is a Microsoft MVP and a certified AWS solution architect professional. He is one of the co authors of this book called Node JS Design Patterns. Fourtheorem is a service company focused on cloud and AWS. We are always hiring, we are always looking for new, interesting projects.
  • We are going to be using airtable as a database for a web application. We will also be looking into nextjs and versailles, which are other components that we will be using in our architecture. Finally, we will discuss some interesting security considerations.
  • Nextjs, airtable and Purcell. Also we are coming to be using GitHub as a hosting for all the source code. With this tech stack, we should be able to host an application that doesn't have a huge amount of traffic pretty much for free.
  • NextJs allows you to make an XJS website private. Only people with a valid invite code can access the information that we want to disclose. Using nextjs you can easily build a react single page application.
  • Airtable can be used to build an app inside nextjs. To use airtable in our backend, we need to have API keys and base id. Next step is to create an invite type which contains more or less the same fields that we had in airtable. This allows us to interact with airtable and fetch data from that data.
  • A custom react hook is just a javascript function that had the name starting with the word use. The idea is that youd can only call hooks at the top level of the function. Hooks cannot be inside loops, condition or other nested functions. You can find more details and examples in the official documentation.
  • We want to allow the user the opportunity to submit data to our back end. We need to add new backend utilities in our airtable helper to be able to update our table. And another function that can be used by the front end to trigger the update.
  • Use GitHub and Vercel to create an XJs application. Hook manages all the lifecycle of this data. Allows us to show a spinner while a request is in progress. How do we deploy all of this?
  • 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. Even if a user doesn't have an invite code, they can actually look into that and extrapolate some sensitive information. How can we avoid that?
  • Airtable is a case study for a quick and easy and cheap solution that you can use if you need to build an invite only website. The takeaway here is just take some of the lessons and then try to apply them to your code base. If you have any questions feel free to reach out to me.

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.
...

Luciano Mammino

Senior Architect @ fourTheorem

Luciano Mammino's LinkedIn account Luciano Mammino's twitter account



Join the community!

Learn for free, join the best tech learning community for a price of a pumpkin latte.

Annual
Monthly
Newsletter
$ 0 /mo

Event notifications, weekly newsletter

Delayed access to all content

Immediate access to Keynotes & Panels

Community
$ 8.34 /mo

Immediate access to all content

Courses, quizes & certificates

Community chats

Join the community (7 day free trial)