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

Subscribe to my newsletter and never miss my upcoming articles

Every story has an end

Finally, we are at the end of the series, Go Serverless. Thank you for reading and liking previous articles of the series. Truly appreciate.

If you are new to the series, here is a quick recap of what we have covered so far:

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

  • Created a database, schema, data-set and the server key with FaunaDB.
  • Tested the testimonial data-set with, create and fetch operations.
  • Written serverless functions using Netlify Function capability to fetch, create, update and delete testimonials.
  • Tested these functions like APIs.

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

  • Explored Gatsby to write the client side testimonial app.
  • Fetched the testimonials in the app using the serverless function.
  • Deployed the app with Netlify.

In this article, we will continue the app development to add an authentication layer. We will allow our users to create a testimonial only after they authenticate to our app.

Our Testimonial app

Our plan is to,

  • Add a Login button to the user interface.
  • Allow users to create an account using email id and password.
  • Allow users to enter credentials when they try to log-in.
  • Show the Create Testimonial option on successful login and allow users to create testimonials.
  • Add the newly created testimonial to the list.
  • Allow the logged-in user to log-out.

At the end, the testimonial app may behave like this:

part_3_flow.gif

But, we are Serverless!

So how about the Authentication module? Do we need to implement one? Who is going to manage identities(i,e, account creation, role provisioning etc.)?

70% - 80% of the features that once required a custom back-end can now be done entirely on the front-end. More details from here.

Authentication and Authorization modules are among them. It powers going serverless too. We are not going to implement any of these by ourselves. Rather, we are going to use one.

Netlify Identity

We are going to explore Netlify Identity.

Netlify Identity service brings a full suite of authentication functionality, backed by the GoTrue API. This allows you to manage and authenticate users on your site or app, without requiring them to be users of Netlify or any other service.

  • Login to your Netlify account and browse to the testimonial app we have created in the previous article.
  • Go to the identity tab and click on the Enable Identity button. part_3_enable_identity.png

That's all. You have enabled the identity services for the testimonial app successfully. Let us now make use of it by modifying the testimonial app a bit.

Install dependencies

We have to install few sets of dependencies to use the Netlify Identity in our app.

  • react-netlify-identity-widget: A React component used to authenticate with Netlify's Identity service. It also has some peer dependencies like, @reach/dialog, @reach/tabs, @reach/visually-hidden.
  • gatsby-plugin-netlify-identity: A Gatsby plugin which adds a React Netlify Identity Widget Provider for us.

Open a command prompt at the root of the project folder and install the dependencies using:

yarn add gatsby-plugin-netlify-identity react-netlify-identity-widget @reach/dialog @reach/tabs @reach/visually-hidden

We will use a modal dialog to allow users for creating a testimonial. I am using react-bootstrap for that purpose. Please install it too,

yarn add react-bootstrap bootstrap

Configure Gatsby

Next, we have to tell Gatsby that, we will use the idenity service from Netlify. For that, edit the gatsby-config.js file and add the plugins section as shown below:

module.exports = {
    plugins: [
        {
          resolve: `gatsby-plugin-netlify-identity`,
          options: {
            url: `https://your-project-identity.netlify.app/`
          }
        }
    ]
}

Please note, the url in the above configuration should match the domain name of your app. Here is an example of what you need to select and specify as the url in the gatsby-config.js file:

part_3_identity_url.png

Let's include it in the code

Time to modify the index.js file to use the identity service from Netlify.

  • First, import required packages

    import IdentityModal, { useIdentityContext } 
                         from "react-netlify-identity-widget";
    import "react-netlify-identity-widget/styles.css";
    
    import 'bootstrap/dist/css/bootstrap.min.css';
    import Button from 'react-bootstrap/Button';
    import Modal from 'react-bootstrap/Modal';
    
  • The react-netlify-identity-widget provides a modal dialog-box for providing credentials and creating account. We need to capture if the user is correctly authenticated using it and then, show the Create Testimonial button.

    // identity code to get if the user logged-in
    
    const identity = useIdentityContext();
    const [dialog, setDialog] = useState(false);
    const name =
      (identity && identity.user && identity.user.user_metadata && 
           identity.user.user_metadata.full_name) || "Untitled";
    const isLoggedIn = identity && identity.isLoggedIn;
    
     {
          identity && identity.isLoggedIn ? (
                <div className="auth-btn-grp">
                  <Button 
                           variant="outline-primary" 
                           onClick={handleShow}>Create Testimonial
                   </Button>
                  { ' '}
                  <Button 
                            variant="outline-primary" 
                            className="login-btn" 
                            onClick={() => setDialog(true)}>
                      {isLoggedIn ? `Hello ${name}, Log out here!` : "LOG IN"}
                  </Button>
                </div>
            ) : (
                <div className="auth-btn-grp">
                    <Button 
                              variant="outline-primary" 
                              className="login-btn" 
                              onClick={() => setDialog(true)}>
                        {isLoggedIn ? `Hello ${name}, Log out here!` : "LOG IN"}
                    </Button>
                </div>
            ) 
      }
    
  • This is how to add the identity dialog:

     <IdentityModal showDialog={dialog} 
                               onCloseDialog={() => setDialog(false)} />
    
  • Last, modify the index.css file to add these classes:

     .auth-btn-grp {
        float: right;
        padding: 5px;
     }
    
     .create-testimonial {
        color: #000000;
     }
    
     .create-form {
        display: flex;
        justify-content: center;
        flex-direction: column;
     }
    

That's all for including the identity service from Netlify to the testimonial app!

Here is the complete index.js file after the modification. You can notice the way we are calling the /api/create-testimonial for creating a testimonial after a successful authentication.

import React, { useEffect, useState } from 'react';    
import axios from "axios";
import ReactStars from 'react-stars';
import "react-responsive-carousel/lib/styles/carousel.min.css";
import { Carousel } from "react-responsive-carousel";

import IdentityModal, { useIdentityContext } from "react-netlify-identity-widget";
import "react-netlify-identity-widget/styles.css";

import 'bootstrap/dist/css/bootstrap.min.css';
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';

import './index.css';

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;
  }

  // identity code
  const identity = useIdentityContext();
  const [dialog, setDialog] = useState(false);
  const name =
    (identity && identity.user && identity.user.user_metadata && identity.user.user_metadata.full_name) || "Untitled";
  const isLoggedIn = identity && identity.isLoggedIn;

  // create testimonial
  const [show, setShow] = useState(false);
  const [rating, setRating] = useState(4);
  const [text, setText] = useState('');
  const handleClose = () => setShow(false);
  const handleShow = () => setShow(true);

  const ratingChanged = (newRating) => {
    setRating(newRating);
  }
  const textChanged = evt => {
    const val = evt.target.value;
    setText(val);
  }
  const handleCreate = async event => {
    if(text === '') return;
    await axios.post('/api/create-testimonial', { text, rating });
    const newList = testimonials.concat({ text, rating });
    setTestimonials(newList);
    setShow(false);
  }

  return (
    <>
      {
        identity && identity.isLoggedIn ? (
              <div className="auth-btn-grp">
                <Button variant="outline-primary" onClick={handleShow}>Create Testimonial</Button>
                { ' '}
                <Button variant="outline-primary" className="login-btn" onClick={() => setDialog(true)}>
                    {isLoggedIn ? `Hello ${name}, Log out here!` : "LOG IN"}
                </Button>
              </div>
          ) : (
              <div className="auth-btn-grp">
                  <Button variant="outline-primary" className="login-btn" onClick={() => setDialog(true)}>
                      {isLoggedIn ? `Hello ${name}, Log out here!` : "LOG IN"}
                  </Button>
              </div>
          ) 
      }

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

      <IdentityModal showDialog={dialog} onCloseDialog={() => setDialog(false)} />

      <Modal
          show={show}
          onHide={handleClose}
          animation={true}
          className="create-testimonial"
        >
          <Modal.Header closeButton>
            <Modal.Title>Create a Testimonial</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            <div className="create-form">
              <textarea 
                onChange={(evt) => textChanged(evt)} 
                placeholder="Enter your message here" />
              <br />
              <span>Rating:</span> {' '} 
              <ReactStars
                count={5}
                value={rating}
                onChange={ratingChanged}
                size={24}
                color2={'#ffd700'}
                half={false} />
              </div>
          </Modal.Body>
          <Modal.Footer>
            <Button variant="secondary" onClick={handleClose}>
              Cancel
            </Button>
            <Button variant="primary" onClick={(evt) => handleCreate(evt)}>Create</Button>
          </Modal.Footer>
      </Modal>
    </>
  );    
}

We are done. Just push the code to your git repo. A build should get started in Netlify automatically and the updated app should be deployed to make the site live.

done.gif

Here is the git repo link. Don't forget to give it a star, if you liked the work.

Conclusion

Thanks for reading through and trying out the application. Hope you enjoyed it. Let us end with a few useful links:

You may also like,


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.

Sergey Biryukov's photo

It was just super! For 3 articles, we have analyzed all the technology !! I'm your fan now! Idea for the following topics: it would be interesting to know how to implement sending emails through the feedback form on your site (without PHP)

Tapas Adhikary's photo

Sergey Biryukov, That's huge feedback. Thank You!

it would be interesting to know how to implement sending emails through the feedback form on your site

Sure, We can implement that too purely serverless way. I am going to attempt that in a week's time from now.

Bolaji Ayodeji's photo

This was an interesting read, keep them coming Tapas Adhikary. 💙

Mark Caple's photo

Very concise, excellent work. Could you maybe continue the identity work so that you also secure the netlify functions as it is still possible with this set up to use postman to create a testimonial. Maybe you didn't want to make the article too complicated by introducing the client context in your functions but I think it would complete the series.

Tapas Adhikary's photo

Great feedback Mark Caple. Thank you! Gald you liked the series.

I can certainly add it. As I think more, I feel to write an article about it outside of the series by extending the same testimonial app. How about that?

Mark Caple's photo

Thanks Tapas Adhikary . Have been using Auth0 and recently trying out Identity so anything that makes using Identity clearer is good

Victoria Lo's photo

I'm still here being impressed and wow'ed! Thanks for the article :)