From Static Sites To End User JAMstack Apps With FaunaDB

guestbook-form-and-signature

From Static Sites To End User JAMstack Apps With FaunaDB

From Static Sites To End User JAMstack Apps With FaunaDB

Bryan Robinson

2020-06-09T12:00:00+00:00
2020-06-09T12:36:44+00:00

The JAMstack has proven itself to be one of the top ways of producing content-driven sites, but it’s also a great place to house applications, as well. If you’ve been using the JAMstack for your performant websites, the demos in this article will help you extend those philosophies to applications as well.

When using the JAMstack to build applications, you need a data service that fits into the most important aspects of the JAMstack philosophy:

  • Global distribution
  • Zero operational needs
  • A developer-friendly API.

In the JAMstack ecosystem there are plenty of software-as-a-service companies that provide ways of getting and storing specific types of data. Whether you want to send emails, SMS or make phone calls (Twilio) or accept form submissions efficiently (Formspree, Formingo, Formstack, etc.), it seems there’s an API for almost everything.

These are great services that can do a lot of the low-level work of many applications, but once your data is more complex than a spreadsheet or needs to be updated and store in real-time, it might be time to look into a database.

The service API can still be in use, but a central database managing the state and operations of your app becomes much more important. Even if you need a database, you still want it to follow the core JAMstack philosophies we outlined above. That means, we don’t want to host our own database server. We need a Database-as-a-Service solution. Our database needs to be optimized for the JAMstack:

  • Optimized for API calls from a browser or build process.
  • Flexible to model your data in the specific ways your app needs.
  • Global distribution of our data like a CDN houses our sites.
  • Hands-free scaling with no need of a database administrator or developer intervention.

Whatever service you look into needs to follow these tenets of serverless data. In our demos, we’ll explore FaunaDB, a global serverless database, featuring native GraphQL to assure that we keep our apps in step with the philosophies of the JAMstack.

Let’s dive into the code!

A JAMstack Guestbook App With Gatsby And Fauna

I’m a big fan of reimagining the internet tools and concepts of the 1990s and early 2000s. We can take these concepts and make them feel fresh with the new set of tools and interactions.

guestbook-form-and-signature

A look at the app we’re creating. A signature form with a signature list below. The form will populate a FaunaDB database and that database will create the view list. (Large preview)

In this demo, we’ll create an application that was all the rage in that time period: the guestbook. A guestbook is nothing but app-generated content and interaction. A user can come to the site, see all the signatures of past “guests” and then leave their own.

To start, we’ll statically render our site and build our data from Fauna during our build step. This will provide the fast performance we expect from a JAMstack site. To do this, we’ll use GatsbyJS.

Initial setup

Our first step will be to install Gatsby globally on our computer. If you’ve never spent much time in the command line, Gatsby’s “part 0” tutorial will help you get up and running. If you already have Node and NPM installed, you’ll install the Gatsby CLI globally and create a new site with it using the following commands:

npm install -g gatsby-cli
gatsby new <directory-to-install-into> <starter>

Gatsby comes with a large repository of starters that can help bootstrap your project. For this demo, I chose a simple starter that came equipped with the Bulma CSS framework.

gatsby new guestbook-app https://github.com/amandeepmittal/gatsby-bulma-quickstart

This gives us a good starting point and structure. It also has the added benefit of coming with styles that are ready to go.

Let’s do a little cleanup for things we don’t need. We’ll start by simplifying our components.header.js

import React from 'react';

import './style.scss';

const Header = ({ siteTitle }) => (
  <section className="hero gradientBg ">
    <div className="hero-body">
      <div className="container container--small center">
        <div className="content">
          <h1 className="is-uppercase is-size-1 has-text-white">
            Sign our Virtual Guestbook
          </h1>
          <p className="subtitle has-text-white is-size-3">
            If you like all the things that we do, be sure to sign our virtual guestbook
          </p>
        </div>
      </div>
    </div>
  </section>
);

export default Header;

This will get rid of much of the branded content. Feel free to customize this section, but we won’t write any of our code here.

Next we’ll clean out the components/midsection.js file. This will be where our app’s code will render.

import React, { useState } from 'react';
import Signatures from './signatures';
import SignForm from './sign-form';


const Midsection = () => {

    const [sigData, setSigData] = useState(data.allSignatures.nodes);
    return (
        <section className="section">
            <div className="container container--small">
                <section className="section is-small">
                    <h2 className="title is-4">Sign here</h2>
                    <SignForm></SignForm>
                </section>

                <section className="section">
                    <h2 className="title is-5">View Signatures</h2>
                    <Signatures></Signatures>
                </section>
            </div>
        </section>
    )
}

export default Midsection;

In this code, we’ve mostly removed the “site” content and added in a couple new components. A that will contain our form for submitting a signature and a component to contain the list of signatures.

Now that we have a relatively blank slate, we can set up our FaunaDB database.

Setting Up A FaunaDB Collection

After logging into Fauna (or signing up for an account), you’ll be given the option to create a new Database. We’ll create a new database called guestbook.

signatures Collection

The initial state of our signatures Collection after we add our first Document. (Large preview)

Inside this database, we’ll create a “Collection” called signature. Collections in Fauna a group of Documents that are in turn JSON objects.

In this new Collection, we’ll create a new Document with the following JSON:

{
 name: "Bryan Robinson",
 message:
   "Lorem ipsum dolor amet sum Lorem ipsum dolor amet sum Lorem ipsum dolor amet sum Lorem ipsum dolor amet sum"
}

This will be the simple data schema for each of our signatures. For each of these Documents, Fauna will create additional data surrounding it.

{
 "ref": Ref(Collection("signatures"), "262884172900598291"),
 "ts": 1586964733980000,
 "data": {
   "name": "Bryan Robinson",
   "message": "Lorem ipsum dolor amet sum Lorem ipsum dolor amet sum Lorem ipsum dolor amet sum Lorem ipsum dolor amet sum "
 }
}

The ref is the unique identifier inside of Fauna and the ts is the time (as a Unix timestamp) the document was created/updated.

After creating our data, we want an easy way to grab all that data and use it in our site. In Fauna, the most efficient way to get data is via an Index. We’ll create an Index called allSignatures. This will grab and return all of our signature Documents in the Collection.

Now that we have an efficient way of accessing the data in Gatsby, we need Gatsby to know where to get it. Gatsby has a repository of plugins that can fetch data from a variety of sources, Fauna included.

Setting up the Fauna Gatsby Data Source Plugin

npm install gatsby-source-faunadb

After we install this plugin to our project, we need to configure it in our gatsby-config.js file. In the plugins array of our project, we’ll add a new item.

{
    resolve: `gatsby-source-faunadb`,
    options: {
    // The secret for the key you're using to connect to your Fauna database.
    // You can generate on of these in the "Security" tab of your Fauna Console.
        secret: process.env.YOUR_FAUNADB_SECRET,
    // The name of the index you want to query
    // You can create an index in the "Indexes" tab of your Fauna Console.
        index: `allSignatures`,
    // This is the name under which your data will appear in Gatsby GraphQL queries
    // The following will create queries called `allBird` and `bird`.
        type: "Signatures",
    // If you need to limit the number of documents returned, you can specify a 
    // Optional maximum number to read.
    // size: 100
    },
},

In this configuration, you provide it your Fauna secret Key, the Index name we created and the “type” we want to access in our Gatsby GraphQL query.

Where did that process.env.YOUR_FAUNADB_SECRET come from?

In your project, create a .env file — and include that file in your .gitignore! This file will give Gatsby’s Webpack configuration the secret value. This will keep your sensitive information safe and not stored in GitHub.

YOUR_FAUNADB_SECRET = "value from fauna"

We can then head over to the “Security” tab in our Database and create a new key. Since this is a protected secret, it’s safe to use a “Server” role. When you save the Key, it’ll provide your secret. Be sure to grab that now, as you can’t get it again (without recreating the Key).

Once the configuration is set up, we can write a GraphQL query in our components to grab the data at build time.

Getting the data and building the template

We’ll add this query to our Midsection component to make it accessible by both of our components.

const Midsection = () => {
 const data = useStaticQuery(
 graphql`
            query GetSignatures {
                allSignatures {
                  nodes {
                    name
                    message
                    _ts
                    _id
                  }
                }
            }`
        );
// ... rest of the component
}

This will access the Signatures type we created in the configuration. It will grab all the signatures and provide an array of nodes. Those nodes will contain the data we specify we need: name, message, ts, id.

We’ll set that data into our state — this will make updating it live easier later.

const [sigData, setSigData] = useState(data.allSignatures.nodes);

Now we can pass sigData as a prop into and setSigData into .

<SignForm setSigData={setSigData}></SignForm>


<Signatures sigData={sigData}></Signatures>

Let’s set up our Signatures component to use that data!

import React from 'react';
import Signature from './signature'   

const Signatures = (props) => {
    const SignatureMarkup = () => {
        return props.sigData.map((signature, index) => {
            return (
                <Signature key={index} signature={signature}></Signature>
            )
        }).reverse()
    }

    return (
        <SignatureMarkup></SignatureMarkup>
    )
}

export default Signatures

In this function, we’ll .map() over our signature data and create an Array of markup based on a new component that we pass the data into.

The Signature component will handle formatting our data and returning an appropriate set of HTML.

import React from 'react';

const Signature = ({signature}) => {
    const dateObj = new Date(signature._ts / 1000);
    let dateString = `${dateObj.toLocaleString('default', {weekday: 'long'})}, ${dateObj.toLocaleString('default', { month: 'long' })} ${dateObj.getDate()} at ${dateObj.toLocaleTimeString('default', {hour: '2-digit',minute: '2-digit', hour12: false})}`

    return (
    <article className="signature box">      
        <h3 className="signature__headline">{signature.name} - {dateString}</h3>
        <p className="signature__message">
            {signature.message} 
        </p>
    </article>
)};

export default Signature;

At this point, if you start your Gatsby development server, you should have a list of signatures currently existing in your database. Run the following command to get up and running:

gatsby develop

Any signature stored in our database will build HTML in that component. But how can we get signatures INTO our database?

Let’s set up a signature form component to send data and update our Signatures list.

Let’s Make Our JAMstack Guestbook Interactive

First, we’ll set up the basic structure for our component. It will render a simple form onto the page with a text input, a textarea, and a button for submission.

import React from 'react';

import faunadb, { query as q } from "faunadb"

var client = new faunadb.Client({ secret: process.env.GATSBY_FAUNA_CLIENT_SECRET  })

export default class SignForm extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            sigName: "",
            sigMessage: ""
        }
    }

    handleSubmit = async event => {
        // Handle the submission
    }

    handleInputChange = event => {
        // When an input changes, update the state
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <div className="field">
                    <div className="control">
                 <label className="label">Label
                    <input 
                        className="input is-fullwidth"
                        name="sigName" 
                        type="text"
                        value={this.state.sigName}
                        onChange={this.handleInputChange}
                    />
                    </label>
                    </div>
                </div>
                <div className="field">
                    <label>
                        Your Message:
                        <textarea 
                            rows="5"
                            name="sigMessage" 
                            value={this.state.sigMessage}
                            onChange={this.handleInputChange} 
                            className="textarea" 
                            placeholder="Leave us a happy note"></textarea>

                    </label>
                </div>
                <div className="buttons">
                    <button className="button is-primary" type="submit">Sign the Guestbook</button>
                </div>
            </form>
        )
    }

}

To start, we’ll set up our state to include the name and the message. We’ll default them to blank strings and insert them into our