Jamstack and the power of serverless with FaunaDB
In this article we will create a Jamstack website powered by Gatsby, Netlify Functions, Apollo and FaunaDB. Our site will use the Harry Potter API for its data that will be stored in a FaunaDB database. The data will be accessed using serverless functions and Apollo. Finally we will display our data in a Gatsby site styled using Theme-ui.
This finished site will look a little something like this: serverless-graphql-potter.netlify.app/
We will begin by focusing on what these technologies are and why, as frontend developers, we should be leveraging them. We will then begin our project and create our schema.
Jamstack is a term often used to describe sites that are served as static assets to a CDN, of course this is nothing new, anyone who has made a simple site with HTML and CSS and published it has served a static site. To walk away thinking that the only purpose of Jamstack sites are to serve static files would be doing it a great injustice and miss some of the awesome things this "new" way of building web apps provides.
A few of the benefits of going Jamstack
A popular way of storing the data your site requires, apart from as markdown files, is the use of a headless CMS (Content Management System). These CMSs have adopted the term headless as they don't come with their own frontend that displays the data stored, like Wordpress for example. Instead they are headless, they have no frontend.
A headless CMS can be set up so that once a change to the data is made in the CMS a new build is triggered via a webhook (just one way of doing it, you could trigger rebuilds other ways) and the site will be deployed again with the new data.
As an example we could have some images stored in our CMS that are pulled into our site via a graphql query and shown on our site. If we wanted to change one of our images we could do so via our CMS which would then trigger a new build on publish and the new image would then be visible on our site.
There are many great options to choose from when considering which CMS to use:
The potential list is so long i will point you in the direction of a great site that lists most of them headlesscms.org!
For more information and a great overview of what the Jamstack is and some more of its benefits i recommend checking out jamstack.org.
Just because our site is served as static assets, that doesn't mean we cant work in a dynamic way and have the benefits of dynamic data! We wont be diving deep into all of its benefits, but we will be looking at how we can take our static site and make it dynamic by way of taking a serverless approach to handling our data through AWS Lambda functions, which we will use via Netlify and FaunaDB.
If we paid for a hosted server then we were paying for the up time even when no requests were being sent.
To counter these issues serverless computing was introduced. Now, lets cut through all the magic this might imply and simply state that serverless still involves servers, the big difference is that they are hosted in the cloud and execute some code for us.
Providing the requested resources as a simple function they only run when that request is made. This means that we are only charged for the resources and time the code is running for. With this approach we have done away with the need to pay a server provider for constant up time, which is one of the big plus points of going serverless.
Being able to scale up and down is also a major benefit of using serverless functions to interact with our data stores. In a nutshell this means that as multiple requests come in via our serverless functions, our cloud provider can create multiple instances of the same function to handle those requests and run them in parallel. One downside to this is the concept of cold starts where because our functions are spun up on demand they need a small amount of time to start up which can delay our response. However, once up if multiple requests are received our serverless functions will stay open to requests and handle them before closing down again.
FaunaDB is a global serverless database that has native graphql support, is multi tenancy which allows us to have nested databases and is low latency from any location. Its also one of the only serverless databases to follow the ACID transactions which guarantee consistent reads and writes to the database.
Fauna also provides us with a High Availability solution with each server globally located containing a partition of our database, replicating our data asynchronously with each request with a copy of our database or the transaction made.
Some of the benefits to using Fauna can be summarized as:
In short, Fauna frees the developer from worry about single or multi-document solutions. Guarantees consistent data without burdening the developer on how to model their system to avoid consistency issues. To get a good overview of how Fauna does this see this blog post about the FaunaDB distributed transaction protocol.
There are a few other alternatives that one could choose instead of using Fauna such as:
But these options don't give us the ACID guarantees that Fauna does, compromising scaling.
Now that we have a good overview of the stack we will be using lets get to the code!
We'll create a new folder to house our project, initialize it with yarn and add some files and folders to that we will be working with throughout.
At the projects root create a functions folder with a nested graphql folder. In that folder we will create three files, our graphql schema which we will import into Fauna, our serverless function which will live in graphql.js and create the link to and use the schema from Fauna and our database connection to Fauna.
We'll also need to add some packages.
Add the following to your newly created .gitignore file:
Lets begin with our schema. We are going to take advantage of an awesome feature of Fauna. By creating our schema and importing it into Fauna we are letting it take care of a lot of code for us by auto creating all the classes, indexes and possible resolvers.
Our schema is defining the shape of the data that we will soon be seeding into the data from the Potter API. Our top level query will return two things, an array of Characters and an array of Spells. We have then defined our Character and Spell types. We don't need to specify an id here as when we seed the data from the Potter API we will attach it then.
Now that we have our schema we can import it into Fauna. Head to your fauna console and navigate to the graphql tab on the left, click import schema and find the file we just created, click import and prepare to be amazed!
Once the import is complete we will be presented with a graphql playground where we can run queries against our newly created database using its schema. Alas, we have yet to add any data, but you can check the collections and indexes tabs on the left of the console and see that fauna has created two new collections for us, Character and Spell.
A collection is a grouping of our data with each piece of data being a document. Or a table with rows if you are coming from an SQL background. Click the indexes tab to see our two new query indexes that we specified in our schema, allCharacters and allSpells. db-connection.js
Inside db-connection.js we will create the Fauna client connection, we will use this connection to seed data into our database.
Here we are creating a function which will check to see if we have an admin key from our Fauna database, if none is found we are returning a helpful error message to the console. If the key is found we are creating a connection to our Fauna database and exporting that connection from file. We are also exporting the query variable from Fauna as that will allow us to use some FQL (Fauna Query Language) when seeding our data.
Head over to your Fauna console and click the security tab, click new key and select admin from the role dropdown. The admin role will allow us to manage the database, in our case, seed data into it. Choose the name FAUNA_ADMIN and hit save. We will need to create another key for use in using our stored schema from Fauna. Select server for the role of this key and name it SERVER_KEY. Don't forget to make a note of the keys before you close the windows as you wont be able to view them again!
That’s a great start. Next up we will seed our data and begin implementing our frontend!
Now that we have our keys its time to grab one more, from the Potter API, it's as simple as hitting the get key button in the top right hand corner of the page, make a note of it and head back to your code editor.
We don't want our keys getting into the wrong wizards hands so lets store them as environment variables. Create a .env file at the projects root and add add them. Also add the .env path to the .gitignore file.
Our database isn't much good if it doesn't have any data in it, lets change that! Create a file at the projects root and name it seed.js
There is quite a lot going on here so lets break it down.
From the projects root run our new script.
If you now take a look inside the collections tab in the Fauna console you will see that the database has populated with all the characters from the potterverse! Click on one of the rows (documents) and you can see the data.
We will create another seed script to get our spells data into our database. Run the script and check out the Spell collections tab to view all the spells.
Now that we have data in our database its time to create our serverless function which will pull in our schema from Fauna.
Lets go through what we just did.
Using our data!
Before we can think about displaying our data we must first do some tinkering. We will be using some handy hooks from Apollo for querying our (namely useQuery) and for that to work we must first set up our provider, which is similar to Reacts context provider. We will wrap our sites root with this provider and pass in our client, thus making it available throughout our site. To wrap the root element in a Gatsby site we must use the gatsby-browser.js and gatsby-ssr.js files. The implementation will be identical in both.
gatsby-browser.js && gatsby-ssr.js
We will have to add a few more packages at this point:
There are other ways of setting this up, i had originally just created an ApolloClient instance and passed in the Netlify functions url as a http link then passed that down to the provider but i was encountering authorization issues, with a helpful message stating that the request lacked authorization headers. The solution was to send the authorization along with a header on every http request.
Lets take a look at what we have here:
Now that we have the nuts and bolts all setup we can move onto some frontend code!
Make it work then make it pretty!
We'll also want to create some base components. We'll be using a Gatsby layout plugin to make life easier for us. We'll also utilize some google fonts via a plugin. Stay with me...
Now we need to add the theme-ui, layout and google fonts plugins to our gatsby-config.js file:
We'll begin with our global layout. This will include a css reset and render our header component and any children, which in our case is the rest of the applications pages/components.
Because we are using gatsby-plugin-layout our layout component will be wrapped around all of our pages so that we can skip importing it ourselves. For our site its a trivial step as we could just as easily import it but for more complex layout solutions this can come in real handy.
To provide an easy way to style our whole site through changing just a few variables we can utilize gatsby-plugin-theme-ui.
This article wont cover the specifics of how to use theme-ui, for that i suggest going over another tutorial i have written which covers the hows and whys how-to-make-a-gatsby-ecommerce-theme-part-1/
In this file we will create our sites styles which we will be able to access via the theme-ui sx prop.
Much of this is self explanatory, the breakpoints array is used to allow us to add responsive definitions to our inline styles via the sx prop. For example:
The font size array indexes corresponded to our breakpoints array set in our theme-ui index file. Next we'll create our header component. But before we do we must install another package, i'll explain why once you see the component.
Lets understand our imports first.
You may be wondering why we have installed emotion/styled when we could just use the sx prop, like we have done else where... Well the answer lies in the affect we are using on the page links.
The sx prop doesn’t seem to have access to, or i should say perhaps that its doesn't have in its definitions, the -webkit-background-clip property which we are using to add a cool linear-gradient affect on hover. For this reason we have pulled the logic our into a new component called PageLink which is a styled Gatsby Link. With styled components we can use regular css syntax and as such have access to the -webkit-background-clip property.
The header component is taking the location prop provided by @reach/router which Gatsby uses under the hood for its routing. This is used to determine which page we are on. Due to the fact that we have a different layout for our main home page and the rest of the site we simply use the location object to check if we are on the home page, if we are we set a display none to hide the header component.
The last thing we need to do is set our grid areas which we will be using in later pages. This is just my preferred way of doing it, but i like the separation. Create a new folder inside of src called window and add an index.js file.
Cool, now we have our global layout complete, lets move onto our home page. Open up the index.js file inside of src/pages and add the following:
This is the first page our visitors will see. We are using a grid to compose our layout of the page and utilizing the responsive array syntax in our grid-template-columns and areas properties. To recap how this works we can take a closer look at the gridTemplateAreas property and see that the first index is for phone (or mobile if you will) with the second being tablet and the third desktop. We could add more if we so wished but these will suffice for our needs.
Lets move on to creating our logo section. In src/components/site create two new files called logo.js and logo-section.js
Our logo is the Harry Potter svg mentioned earlier. You can of course choose whatever you like as your sites logo. This one is merely “HR” in a fancy font.
Next up is our author section which will site next to our logo section Create a new file inside of src/components/site called author-section.js
This component outlines what the project is, displays links to the other pages and the projects repository. You can change the text I’ve added, this was just for demo purposes. As you can see, we are again using emotion/styled as we are making use of the -webkit-background-clip property on our cool linear-gradient links. We have two here, one for external links, which uses the a tag, and another for internal link which uses Gatsby Link. Note that you should always use the traditional HTML a tag for external links and the Gatsby Link to configure your internal routing.
You may also notice that there is an import from a helper file what exports some emojis. Lets take a look at that. Create a new folder inside of src.
The emojis were taken from a really cool site called Emoji Clipboard, it lets you search and literally copy paste the emojis! We’ll be using these emojis in our cards to display the characters from Harry Potter. As well as the emojis we have some utility functions that will also be used in the cards. Each house in Harry Potter has a set of colors that sets them apart form the other houses. These we have exported as linear-gradients for later use.
Nice! We are nearly there but we haven’t quite finished yet! Next we will use our data and display it to the user of our site!
We have done quite a bit of setup but haven’t yet had a chance to use our data that we have saved in our Fauna database. Now’s the time to bring in Apollo and put together a page that shows all the characters data for each house. We are also going to implement a simple searchbar to allow the user to search the characters of each house!
Inside src/pages create a new file called houses.js and add the following:
We are using @apollo/client from which we import gql to construct our graphql query and the useQuery hook which will take care of handling the state of the returned data for us. This handy hook returns three states:
Our page will be handling the currently selected house so we use the React useState hook and initialize it with an empty array on first render. There after we use the useEffect hook to set the initial house as Gryffindor (because Gryffindor is best. Fight me!) The dependency array takes in the loading and data states.
We then have a function which returns a switch statement (I know not everyone likes these but i do and i find that they are simple to read and understand). This function checks the currently selected house and if there are no errors in the query it loads the data from that house into the selected house state array. This function is passed down to another component which uses that data to display the house characters in a grid of cards.
Lets create that component now. Inside src/components/site create a new file called main-section.js
Our main section is wrapped in memo, which means that React will render the component and memorize the result. If the next time the props are passed in and they are the same, React will use the memorized result and skip re-rendering the component again. This is helpful as our component will be re-rendering a lot as the user changes houses or uses the searchbar, which will will soon create.
In fact, lets do do that now. We will be creating a search bar component and a custom hook to handle the search logic. Inside src/components/site create two new files. searchbar.js and useSearchbar.js
Our searchbar takes in a search query function which is called when the input is used. The rest is just styling.
Our custom hook takes the selected house data as a prop. It has an internal state which holds an emptyQuery variable which we set to empty string and a filteredData array, set to empty. The function that runs in our searchbar is the following function declared in the hook. It takes the query as an event from the input, checks if the data provided to the hook has data or sets it to an empty array as a new variable called members. It then filters over the members array and checks if the query matches one of the characters names. Finally it sets the state with the returned filtered data and query.
We structure the state and create a new variable which checks if the state had any data or not. Finally returning the data, be that empty or not and the search function.
Phew! That was a lot to go over. Going back to our main section we can see that we are importing our new hook and passing in the selected house data, then destructing the members and search query function. The component checks if the house array has any length, if it does it returns the page. The page displays the current house, how many members the house has, the searchbar (which takes the search query function as a prop), a new house section which we will build and maps over the members returned from our custom hook.
In the house section we will make use of a super amazing library called Framer Motion. Lets first see how our new component looks and what it does.
In src/components/site create a new file called house-section.js
The purpose of this component is to show the user the four houses of Hogwarts, let them select a house and pass that selection back up to the main-section state. The component takes the getHouse function from main-section as a prop. We have created an internal link styled component , which takes each houses colours from our helper file, and returns the selected house on click.
Using framer motion we prepend each li with the motion tag. This allows us to add a simple scale animation by setting the initial value 0 (so it’s not visible), using the animate prop we say that it should animate in to it’s set size. The transition is specifying how the animation will work.
Back to the main-section component, we map over each member in the house and display their data in a Card component by spreading all the character data. Lets create that now.
Inside src/components/site create a new file called card.js
We are importing all of those cool emojis we added earlier in our helper file. The container and item objects are for use in our animations from framer motion. We descructure our props, of which there are many, and return a div which has the framer motion object prepended to it and the item object passed to the variants prop. This is a simpler way of passing the object and all of it’s values through. For certain properties we run a null check against them to determinate what we should show.
The only thing left to do is implement the Spells page and its associated components then the implementation of this site is done! Given all we have covered I’m sure you can handle the last part!
Your final result should resemble something like this: serverless-graphql-potter.
Did you notice the cool particles? That’s a nice touch you could add to your site!
Deploy the beast!
That’s a lot of code and we haven’t even checked that it works!! (of course during development you should check how things look and work and make changes accordingly, I didn’t cover running the site as that’s common practice while developing). Lets deploy our site to Netlify and check it out!
At the projects root create a new file called netlify.toml
If you don’t already have an account, create a new one at netlify.com. To publish your site:
When you first created this new site Netlify tried to deploy it. It wouldn’t have worked as we hadn’t set the environment variables yet. Go to the deploys tab at the top of the page and hit the trigger deploy dropdown and deploy site. If you encounter any issues then please drop me an email at firstname.lastname@example.org and we can try and work through it together.
And that’s it! I hope you enjoyed it and learnt something along the way. Thank you for coming to my TED talk 😅
If you liked this article feel free to give me a follow on Twitter with the blue button at the top of the page. 😇