How to build a serverless app with Gatsby, Netlify and FaunaDB - Part 1

Subscribe to my newsletter and never miss my upcoming articles

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 the 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 along side 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(☕, 🍵, 🍺) near by!

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,

part_3_flow.gif

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 application 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. create_db.png
  • After creating the database, we will be defining the GraphQL schema and import 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 a Query type which returns an array of messages.

    As a next step, upload the testimonial.gql file from the FaunaDB dashboard using the IMPORT SCHEMA button, import_schema.png

  • 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 to create testimonials and fetch them all at a time . To support that, we will be using mutation(for create, 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, mutation_query.png

    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, fetch_query.png

That's cool, now we have a database with a schema and fully operational with create 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, create_security_key.png

On successful creation of the key, you will be able to view it and make sure to copy and save it somewhere safe.

key.png

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, faunadb_dashboard.png

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 the FaunaDB free tier, Netlify is also very flexible.
  • Now we need to install a few dependencies using either npm or yarn. 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 and dotenv will help to load the FAUNA_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 the functions folder. Create a file called, query.js under the utils folder. We will need some common code to query the database for all the serverless functions. The common code will be in the query.js file.
  • First we import the axios library functionality and load the .env file. Next, we export and async function that takes the query and variables. Inside the async function, we make calls using axios 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 the functions 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 a redirect to make it like, /api/get-all-testimonials.

  • Ok, we are done. Now in command prompt type,

     netlify dev
    

    netlify_dev.png

  • 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: get_testimonial_call.png

    Congratulations!!! You have got your first serverless function up and running.

    congrats.gif

  • 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 the mutation 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,

    create_testimonial.png

  • Ok, Let us quickly create another serverless function to update a testimonial. Create a file named, update-testimonial.js under the functions folder. We need to pass the id of the testimonial message we want to update along with the message itself, i.e, the text and the rating.

    // 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,

    update_testimonial.png

  • 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,

    delete_testimonial.png

At this stage, you should have a directory structure like this:

dir_part_1.png

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 git repo. See you soon.


If it was useful to you, please Like/Share so that, it reaches others as well. To get 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.

Comments (4)

Victoria Lo's photo

I have 🍵, 🎵 and 🖥️ but no 🕗...

But I'll definitely follow this series till the end and bookmark all of them so I can 👩‍💻 whenever I'm ready! Sorry and thank you for writing such fantastic articles!

Show +1 replies
Skay's photo

I cannot agree more.. so many articles.. I can barely keep up.. Tapas, superb job :-) I've bookmarked them.. hope to get to them in this lifetime..

Tapas Adhikary's photo

Skay Surely you will..