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

Subscribe to my newsletter and never miss my upcoming articles

Let's Continue...

Welcome to the Part 2 of the series Go Serverless. Hope you are enjoying it so far by following the steps to develop the serverless testimonial app.

In the last article(Part 1), we have established a ground by,

  • Creating an account with the client-serverless data API provider called, FaunaDB.
  • Setting up the database, schema, document and the server key.
  • Writing the serverless functions using, Netlify and finally tested them like APIs

In this article, we will be using the serverless functions to build a user interface of the testimonial app. To do that, we will use Gatsby which is a super cool, react-based static site generator. We will also learn about using Netlify to build and deploy the testimonial app.

We will start where we left off in the last article. Let us continue to develop the app on top of the code implemented so far as part of the previous article.

If you haven't got a chance to go through the part 1 of the series yet, here is the link:

As usual, here is the source code repo link:

Gatsby: Install and initial setup

There are multiple ways to setup a Gatsby based project. There are plenty of starter projects to help you get going. You can check out this quick start guide to learn more.

For a better understanding sake, we will not be using any of the starter projects here. We will build things from the scratch.

  • Install gatsby-cli globally. This tool will help us to work with the Gatsby environment.

     npm install -g gatsby-cli
    
  • Install gatsby, react and react-dom

     yarn add gatsby react react-dom
    
  • Edit the scripts section of the package.json file to add a script for 'develop'.

     "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "develop": "gatsby develop"
      }
    
  • Gatsby projects need a special configuration file called, gatsby-config.js. At this time, we will need an empty file. Please create a file named, gatsby-config.js with the following content:

     module.exports = {
      // keep it empty    
     }
    
  • Now it is the time to create our first page with Gatsby. Create a folder named, src at the root of the project folder. Create a sub-folder named, pages under src. Create a file named, index.js under src/pages with the following content:

      import React, { useEffect, useState } from 'react';    
    
      export default () => {    
        const [status, setStatus ] = useState('loading...');    
        const [testimonials, setTestimonials] = useState(null);    
    
        return (
          <>    
            <h1>Testimonials to load here...</h1>
          </>        
        )    
      }
    

    The code above is a simple react component. We import React and two in-built hooks called, useState and useEffect. We have got a couple of state variables to keep track of the array of testimonials and the API responses.

  • Let's run it. We generally need to use the command, gatsby develop to run the app locally. As we have to run the client side application with netlify functions, we will continue to use, netlify dev command.

    This single command will take care of running the serverless functions locally along with the client side gatsby application. Open a command prompt at the root of the project folder and type,

     netlify dev
    
  • That's all. Try accessing the page at localhost:8888. You should see something like this, part_2_inital_page.png

  • Gatsby project build creates couple of output folders which you may not want to push to the source code repository. Let us add few entries to the .gitignore file so that, we do not get unwanted noise.

    Add .cache and public to the .gitignore file. Here is the full content of the file:

    .cache
    public
    node_modules
    *.env
    

    At this stage, your project directory structure should match with the following:

    dir_part_2.png

Load all the Testimonials

Our goal here is to fetch all the testimonials using, /api/get-testimonials call. We would like to show the fetched testimonials in the following fashion:

part_2_flow.gif

First thing first. We need to load all the testimonials using the serverless function and show it to the page we have just created.

Load data

We will follow only a few steps to load all the testimonials. You need to edit the index.js file with the following changes:

  • Import axios library so that, we can make the API calls.

    import axios from "axios";
    
  • Make a call to the serverless function using the URI, api/get-tetsimonials. We will use the hook, useEffect to accomplish it. We use axios to make this call. On a successful response, testimonials are stored in the testimonials state variable.

    Note, we also change the status as loaded to indicate that, the data has been loaded successfully.

     useEffect(() => {
      if (status !== "loading...") return;
      axios("/api/get-testimonials").then(result => {
        if (result.status !== 200) {
          console.error("Error loading testimonials");
          console.error(result);
          return;
        }
        setTestimonials(result.data.messages);
        setStatus("loaded");
      });
    }, [status]);
    
  • Have you noticed those cool avatars in the testimonial interface above? I am using them from https://avatars.dicebear.com/api/. These avatars are available to fetch for free using the URLs.

    We will write a simple util function named, getAvatar() to fetch some random 'happy' avatars.

    const getAvatar = () => {
      const random = Math.floor(Math.random() * (testimonials.length - 0 + 1) + 0);
      const imgUrl = `https://avatars.dicebear.com/api/human/${random}.svg?mood[]=happy`;
      return imgUrl;
    }
    
  • Last is the render function to show the testimonial message, rating along with an avatar in the UI. We loop through the testimonials array and render the details with the ui elements.

    return (
      <>
        {testimonials && testimonials.map((testimonial, index) => (
          <div key={ index }>
            <img 
              src={ getAvatar() } 
              height="50px"
              width="50px"
              alt="avatar" />
            <div className="testimonial">
              <span>{ testimonial.rating }</span>
              <p className="text">
                { testimonial.text }
              </p>
            </div>
          </div>
        ))}
      </>
    );
    

That's all about it! We have completed the loading of all the testimonials and showing them in the UI. Here is the complete code of index.js:

import React, { useEffect, useState } from 'react';    
import axios from "axios";

export default () => {    
  const [status, setStatus ] = useState('loading...');    
  const [testimonials, setTestimonials] = useState(null);

  useEffect(() => {
    if (status !== "loading...") return;
    axios("/api/get-testimonials").then(result => {
      if (result.status !== 200) {
        console.error("Error loading testimonials");
        console.error(result);
        return;
      }
      setTestimonials(result.data.messages);
      setStatus("loaded");
    });
  }, [status]);

  const getAvatar = () => {
    const random = Math.floor(Math.random() * (testimonials.length - 0 + 1) + 0);
    const imgUrl = `https://avatars.dicebear.com/api/human/${random}.svg?mood[]=happy`;
    return imgUrl;
  }

  return (
    <>
      {testimonials && testimonials.map((testimonial, index) => (
        <div key={ index }>
          <img 
            src={ getAvatar() } 
            height="50px"
            width="50px"
            alt="avatar" />
          <div className="testimonial">
            <span>{ testimonial.rating }</span>
            <p className="text">
              { testimonial.text }
            </p>
          </div>
        </div>
      ))}
    </>
  );    
}

How does the UI look now? Well, it looks like this:

part_2_loaded_testi.png

No doubt, we have fetched all the testimonials and showing them in the UI. But it doesn't look great, right?

So, Let's make things look better

Our vision is this,

part_2_ui.png

Notice, there is a rating component(with stars) and a carousel component to browse through the testimonials. We will use a couple of react-based npm to achieve these.

Install libraries

Open a command prompt at the root of the project folder. Try this command(or npm i) to install these libraries.

 yarn add react-stars react-responsive-carousel

Use the libraries

We have imported ReactStars and Carousel components along with the carousel.min.css to the index.js file.

Only few changes that we have to do are,

  • Wrap the JSX portion of the code with the Carousel component
  • Use the ReactStars component for the ratings.

Here is the complete code with the changes:

import React, { useEffect, useState } from 'react';    
import axios from "axios";

// import these libraries
import ReactStars from 'react-stars';
import "react-responsive-carousel/lib/styles/carousel.min.css";
import { Carousel } from "react-responsive-carousel";

export default () => {    
  const [status, setStatus ] = useState('loading...');    
  const [testimonials, setTestimonials] = useState(null);

  useEffect(() => {
    if (status !== "loading...") return;
    axios("/api/get-testimonials").then(result => {
      if (result.status !== 200) {
        console.error("Error loading testimonials");
        console.error(result);
        return;
      }
      setTestimonials(result.data.messages);
      setStatus("loaded");
    });
  }, [status]);

  const getAvatar = () => {
    const random = Math.floor(Math.random() * (testimonials.length - 0 + 1) + 0);
    const imgUrl = `https://avatars.dicebear.com/api/human/${random}.svg?mood[]=happy`;
    return imgUrl;
  }

  return (
    <Carousel
        className="main"
        showArrows={true}
        infiniteLoop={true}
        showThumbs={false}
        showStatus={false}
        autoPlay={false} >

        {testimonials && testimonials.map((testimonial, index) => (
            <div key={ index } className="testimonial"> 
            <img 
                src={ getAvatar() } 
                height="50px"
                width="50px"
                alt="avatar" />
            <div className="message">
                <ReactStars
                    className="rating"
                    count={ testimonial.rating }
                    size={24}
                    color1={'#ffd700'} 
                    edit={false}
                    half={false} />
                <p className="text">
                { testimonial.text }
                </p>
            </div>
            </div>
        ))}
    </Carousel>
  );    
}

Please create a file named, index.css with the following content under the directory, src/pages.

.rating {
    display: flex;
    justify-content: center;
}

.carousel .slide {
    padding: 20px;
    font-size: 20px;
}

body {
    background-color: #000000;
    color: #FFFFFF;
}

Import the index.css file into the index.js file as,

import './index.css';

Try netlify dev and access the url localhost:8888. You should see the UI appearing like this:

part_2_flow.gif

Deploy and Publish

All Good so far. But there is one issue. We are running the app locally. It is fun but, not as much as we will get by running it publicly. Let's do that with few simple steps.

  • Make sure to commit all the code changes to the git repository, say, testimonial.
  • You have an account with netlify already. Please login and click on the button, New site from Git. new_site_netlify.png

  • Provide the one-click authorization to your git repo and select the testimonial repository. select_repo_netlify.png

  • You need to provide few details to deploy the app. Please provide the details as it is shown below and deploy the app. deploy_site_netlify.png

  • Do you remember, we have used a API server key locally for accessing the data from the database? Now we need to tell netlify about this key. Go to the environment setting under Build & deploy option to create a new environment variable.

    Create the new environment variable with the key as, FAUNA_SERVER_SECRET and value is the actual server key from the .env file. netlify_env.png

  • Netlify allocates a domain with a random name for your app/website. You can change it to something more meaningful to you. In my case, I have given the name as, testimonial-greenroots.

    Hence the app will be available on this URL: https://testimonial-greenroots.netlify.app/. change_site_name_netlify.png

  • Finally, deploy the app once again by clearing the cache. deploy_final.png

Congratulations!!!! Your app/site is publicly available now.

production_part_2.png

What's Next?

Up next, the last article of the series is to integrate the authentication module to our app painlessly. We will allow users to create a testimonial only after they authenticate to our application.

Sounds like fun? Yeah, stay tuned to get there 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 (2)

Bolaji Ayodeji's photo

I won't miss your articles for anything, keep them coming Tapas Adhikary :)

Tapas Adhikary's photo

Thanks Bolaji! 🙏