How to build a serverless app with Gatsby, Netlify and FaunaDB - Part 1
Introduction
Welcome to a brand new series called, Go Serverless. Unlike the previous series, this is going to be a short one and fully focused on doing hands-on app development.
With this series, we will develop a serverless
application using, Gatsbyjs
, Netlify
, and FaunaDB
.
The series will be delivered in three articles,
- Part 1: Build a serverless Data API with FaunaDB and access it using Netlify functions.
- Part 2: Build the skin. Make use of the functions and APIs to build something cool using Gatsby.
- Part 3: Painless integration of Authentication and Authorization using Netlify Identity.
This article is Part 1
of the series. Hope you enjoy the journey!
How to follow this series?
I think the best way to follow this series would be, developing the app alongside of it.
Just follow the steps, take a pause to understand what just happen, and move on. I hope you like building it and feel free to improvise on it.
Make sure to keep your favorite beverages(☕, 🍵, 🍺) nearby!
What are we building?
Have you written any testimonials recently? Let us build an app to allow users to write testimonials with a rating. The app should allow any user to read all the testimonials submitted by other users as well.
We will be taking care of the following use-cases while building the app:
- All testimonials to be stored in a database.
- APIs to support creating a testimonial and fetching all testimonials.
- The user interface to make use of these APIs to show all testimonials and create them.
- User needs to authenticate to create a testimonial.
But wait, there are few important aspects,
- We will not be installing or maintaining any database by ourselves.
- We will not be using any server to host APIs.
- We will not be using any application server to host the client-side app. Remember, we are
serverless
.
Here is the preview of the testimonial app,
If you want to get to the code first, here is the link:
Alright, let's get started!
Quick Background
Have you heard of JAMstack
?
If so, the concept of the serverless app shouldn't be new to you. One of the fundamentals of JAMstack
is, it is practically serverless
. It means, you as a programmer, code owner, or business owner do not manage and maintain the servers. You just focus on building applications using client-side JavaScript, reusable APIs, and prebuilt Markups.
You do not need any prior experience with JAMstack to follow this article. If you are new to
JAMstack
, the link below may be helpful to you.
FaunaDB - Let us set up the database
FaunaDB is the data API for client-serverless applications. It transforms the traditional DBMS into a Data API that gives you all of the capabilities of an old-guard database, without sacrificing flexibility, scale, and performance.
It has multiple APIs for data access, including native GraphQL
and a DSL-like functional query language. We will be using GraphQL type for the testimonial app.
- First thing first, sign up using this url. Please select the free plan which is with generous daily usage quota and more than enough for our usage.
- Next, create a database by providing a database name of your choice. I have used
testimonial
as the database name. After creating the database, we will be defining the
GraphQL
schema and import it into the database.At this stage, let us create our project folder. Create a project folder somewhere on your hard drive with the name,
testimonial
. Create a file with the name,testimonial.gql
with the following content,type Message { text: String! rating: Int! } type Query { allMessages: [Message!]! }
Note, we have defined a
Message
type which consists of a text, i.e, the testimonial message, and a rating. We also have aQuery
type which returns an array of messages.As a next step, upload the
testimonial.gql
file from the FaunaDB dashboard using theIMPORT SCHEMA
button,- That's all, our database has been created successfully and it is ready for use.
Let us try some queries
Our testimonial app will allow us to create testimonials and fetch them all at a time. To support that, we will be using mutation
(for creating, update and delete) and query
(for fetch) of GraphQL
.
Create a testimonial with the text, "This is so cool" with a rating of 5.
mutation { createMessage(data: { text: "This is so cool", rating: 5 }) { _id text rating } }
Try the above query in the GraphQL playground of FaunaDb to see the response,
Great, a testimonial has been created. Let us try fetching all the testimonials. Here is the GraphQL query,
query { allMessages { data { _id text rating } } }
Let us try the above query in the playground as before,
That's cool, now we have a database with a schema and fully operational with creating and fetch functionality.
Create a server secret key
Next, we need to create a secured server secret key to make sure the access to the database is authenticated and authorized.
Click on the SECURITY
option available in the FaunaDB interface to create the key,
On successful creation of the key, you will be able to view it and make sure to copy and save it somewhere safe.
We do not want anyone else to know about this key. Not even to commit it to the source code repository. To maintain this secrecy, create an empty file called, .env
at the root level of your project folder.
Edit the .env
file and add the following line to it(paste the generated server key in the place of, <your secret fauna key>
).
FAUNA_SERVER_SECRET=<your secret fauna key>
Add a .gitignore
file and add the following content to it. This is to make sure, we do not commit the .env
file to the source code repo accidentally.
node_modules
*.env
We are done with all that we had to do with the database setup. Let us move to the next phase to create serverless
functions and APIs to access data from the FaunaDB
database.
This is how the faunadb dashboard may look like based on your usage,
Netlify - Functions and APIs
We have a server key now. Time to make use of it as an API key for invoking the serverless
function.
Netlify is a great platform to create hassle-free serverless
functions. These functions can interact with databases, file-system, and, in-memory objects.
Netlify functions are Powered by AWS Lambda. Setting up AWS Lambdas on our own can be a fairly complex job. With Netlify, we will simply set a folder and drop our functions. Writing simple functions automatically become APIs.
- First, create an account with
Netlify
. This is free and just like theFaunaDB
free tier, Netlify is also very flexible. - Now we need to install a few dependencies using either
npm
oryarn
. Make sure, you have nodejs installed. - Open a command prompt at the root of the project folder. Use the following command to Initialize the project with node dependencies,
npm init
- Install the
netlify-cli
utility so that, we can run the serverless function locally.npm install netlify-cli -g
- Now we will install two important libraries, axios and dotenv.
axios
will be used for making the HTTP calls anddotenv
will help to load theFAUNA_SERVER_SECRET
environment variable from the .env file into process.env.yarn add axios dotenv
Create serverless functions
- Create a folder with the name,
functions
at the root of the project folder. We are going to keep all the serverless functions under it. - Now create a sub-folder called,
utils
under thefunctions
folder. Create a file called,query.js
under theutils
folder. We will need some common code to query the database for all the serverless functions. The common code will be in thequery.js
file. First we import the axios library functionality and load the
.env
file. Next, we export and async function that takes thequery
andvariables
. Inside the async function, we make calls usingaxios
with the secret key. Finally, we return the response.// query.js const axios = require("axios"); require("dotenv").config(); module.exports = async (query, variables) => { const result = await axios({ url: "https://graphql.fauna.com/graphql", method: "POST", headers: { Authorization: `Bearer ${process.env.FAUNA_SERVER_SECRET}` }, data: { query, variables } }); return result.data; };
Create a file with the name,
get-testimonials.js
under thefunctions
folder. We will perform a query to fetch all the testimonial messages.// get-testimonials.js const query = require("./utils/query"); const GET_TESTIMONIALS = ` query { allMessages { data { _id text rating } } } `; exports.handler = async () => { const { data, errors } = await query(GET_TESTIMONIALS); if (errors) { return { statusCode: 500, body: JSON.stringify(errors) }; } return { statusCode: 200, body: JSON.stringify({ messages: data.allMessages.data }) }; };
Time to test the
serverless function
like an API. We need to do a one time setup here. Open a command prompt at the root of the project folder and type,netlify login
This will open a browser tab and ask you to login and authorize access to your netlify account. Please click on the
Authorize
button.Create a file called,
netlify.toml
and add this content to it,[build] functions = "functions" [[redirects]] from = "/api/*" to = "/.netlify/functions/:splat" status = 200
This is to tell netlify about the location of the functions we have written so that, it is known at the build time.
Netlify automatically provides the APIs for the functions. The URL to access the API is in this form,
/.netlify/functions/get-all-testiminials
which may not be very user-friendly. We have written aredirect
to make it like,/api/get-all-testimonials
.Ok, we are done. Now in command prompt type,
netlify dev
Now the netlify dev server is running locally and you can access the first serverless function. Open a browser tab and try this url,
http://localhost:8888/api/get-testimonials
:Congratulations!!! You have got your first
serverless function
up and running.Let us now write the next serverless function to create a testimonial. This is going to be simple. Create a file named,
create-testimonial.js
under the 'functions` folder. We need to write a query by passing the testimonial message text and rating.// create-testimonial.js const query = require("./utils/query"); const CREATE_TESTIMONIAL = ` mutation($text: String!, $rating: Int!){ createMessage(data: {text: $text, rating: $rating}){ _id text rating } } `; exports.handler = async event => { const { text, rating } = JSON.parse(event.body); const { data, errors } = await query( CREATE_TESTIMONIAL, { text, rating }); if (errors) { return { statusCode: 500, body: JSON.stringify(errors) }; } return { statusCode: 200, body: JSON.stringify({ testimonial: data.createMessage }) }; };
What do you think? Yeah right, we have de-structured the payload to get the message text and rating. On getting those, we just call the
query
method to create a testimonial. Have you noticed, the query, in this case, is themutation
query?Alright, let us test it out. I shall be using the
postman
tool to test the API. You can use postman or any other tools of your choice,Ok, let us quickly create another serverless function to update a testimonial. Create a file named,
update-testimonial.js
under thefunctions
folder. We need to pass theid
of the testimonial message we want to update along with the message itself, i.e, thetext
and therating
.// update-testimonial.js const query = require("./utils/query"); const UPDATE_TESTIMONIAL = ` mutation($id: ID!, $text: String!, $rating: Int!){ updateMessage(id: $id, data: {text: $text, rating: $rating}){ _id text rating } } `; exports.handler = async event => { const { id, text, rating } = JSON.parse(event.body); const { data, errors } = await query( UPDATE_TESTIMONIAL, { id, text, rating }); if (errors) { return { statusCode: 500, body: JSON.stringify(errors) }; } return { statusCode: 200, body: JSON.stringify({ updatedMessage: data.updateMessage }) }; };
Let us test this API,
Great, now you have already guessed the
delete
part, isn't it? For deleting a testimonial, we just need the id of it. Here is the function for deleting a testimonial,// delete-testimonial.js const query = require("./utils/query"); const DELETE_TESTIMONIAL = ` mutation($id: ID!) { deleteMessage(id: $id){ _id } } `; exports.handler = async event => { const { id } = JSON.parse(event.body); const { data, errors } = await query( DELETE_TESTIMONIAL, { id }); if (errors) { return { statusCode: 500, body: JSON.stringify(errors) }; } return { statusCode: 200, body: JSON.stringify({ deletedMessage: data.deleteMessage }) }; };
Time to test it out,
At this stage, you should have a directory structure like this:
That's all for now. You have successfully created a database with the FaunaDB, set it up for use, created serverless functions using Netlify Functions, and tested them as APIs.
Congratulations, you did it and that's awesome!!!
What's Next?
In the next article of the series, we will be using these APIs to develop the client-side of the testimonial app with Gatsby. Until then, keep exploring by forking the github repo. See you soon.
If it was useful to you, please Like/Share so that, it reaches others as well. To get an email notification on my latest posts, please subscribe to my blog by hitting the Subscribe button at the top of the page.
Follow me on Twitter @tapasadhikary for more updates.