Skip to content

Instantly share code, notes, and snippets.

@jeancochrane
Created April 3, 2019 16:55
Show Gist options
  • Save jeancochrane/705dda18da74fafe4b8182d15284114d to your computer and use it in GitHub Desktop.
Save jeancochrane/705dda18da74fafe4b8182d15284114d to your computer and use it in GitHub Desktop.
Notes for Gatsby Lunch&Learn

Lunch&Learn: Gatsby

April 3rd, 2019

What is Gatsby?

Gatsby is a static site generator built on top of React components that uses GraphQL to pull in data from anywhere.

When I first read about Gatsby that description didn't really help me grasp what Gatsby was all about. An alternative way to describe it is:

Gatsby is the Jekyll killer.

Gatsby vs. Jekyll

Like Jekyll, Gatsby is a static site generator. That means that the Gatsby build tool will read your source files and your configs, and produce a static bundle of HTML/JS/CSS that you can deploy without a backend.

A common example is a blog: Rather than host your blog on Wordpress, where every page will have to be generated based on database models translated into a view and eventually pushed to the frontend via an HTML template, a static site generator will read all of your posts ahead of time and use templates to pre-generate the pages for you.

Unlike Jekyll, however, Gatsby can read data from practically anywhere. To generate pages with Jekyll, your data have to be formatted as markdown files; to generate pages with Gatbsy, your data can come from anywhere: CSV files, markdown files, Google Pages, even a Postgres database.

There are two key technologies that underpin Gatsby's ability to template data from anywhere: React components and GraphQL queries. Turns out that the basics of both of them are pretty easy to understand. I'll review each in turn.

React components

Gatsby uses React components for templating data. This has the really nice property that it means you can make use of the entire React ecosystem for your app. It also has the downside that you have to understand components in order to use it.

What's a React component? At the most basic level, it's a composible piece of coupled HTML/CSS/JS. Here's a simple example:

// src/components/header.js
import React from "react"

export default props => <h1>{props.headerText}</h1>
// src/pages/about.js
import React from "react"
import Header from "../components/header"

export default () => (
  <div style={{ color: `teal` }}>
    <Header headerText="About Gatsby" />
    <p>Such wow. Very React.</p>
  </div>
)

At the most basic level, you can think of the role that components play in Gatsby as replacing Jekyll's Liquid templates, or Django's templating engine (like Jinja).

Here's what a component looks like that pulls data from a Postgres database and templates it:

https://github.com/datamade/cps-ssce-dashboard/blob/8a4d3dbdafee6a707961ff4802d5caca00e60352/src/frontend/src/pages/networks.js#L8-L41

const NetworkListPage = ({ data }) => (
  <div className="row header">
    <div className="card networks" style={{width: '100%'}}>
      <h2><i className="fas fa-globe"></i>&nbsp; Network List</h2>
      <p><br /></p>
      <table className="table table-striped table-hover compact responsive" id="network-list">
        <thead>
          <tr>
            <th scope="col"><p>Network</p></th>
            <th scope="col"><p>Schools</p></th>
            <th scope="col"><p>% Low Income</p></th>
            <th scope="col"><p>% Asian</p></th>
            <th scope="col"><p>% Black</p></th>
            <th scope="col"><p>% Hispanic</p></th>
            <th scope="col"><p>% White</p></th>
          </tr>
        </thead>
        <tbody>
          {data.cps.cps_app_network.map((network) => (
          <tr>
            <td><p><Link to={`/network/${network.slug}`}>{network.name}</Link></p></td>
            <td><p>{network.school_set.count}</p></td>
            <td><p>{network.fr_lunch}</p></td>
            <td><p>{network.demo_asian}</p></td>
            <td><p>{network.demo_aa}</p></td>
            <td><p>{network.demo_hispanic}</p></td>
            <td><p>{network.demo_white}</p></td>
          </tr>
          ))}
        </tbody>
      </table>
    </div>
  </div>
)

For contrast, here's the same code templated using Django templates:

https://github.com/datamade/cps-ssce-dashboard/blob/c5098bdc0aea9ae196b897a7e3cc7eb80bc8bece/cps_app/templates/cps_app/networks.html#L20-L52

<div class="row header">
  <div class="card networks" style="width: 100%">
    <h2><i class="fas fa-globe"></i>&nbsp; Network List</h2>
    <p><br /></p>
    <table class="table table-striped table-hover compact responsive" id="network-list">
      <thead>
        <tr>
          <th scope="col"><p>Network</p></th>
          <th scope="col"><p>Schools</p></th>
          <th scope="col"><p>% Low Income</p></th>
          <th scope="col"><p>% Asian</p></th>
          <th scope="col"><p>% Black</p></th>
          <th scope="col"><p>% Hispanic</p></th>
          <th scope="col"><p>% White</p></th>
        </tr>
      </thead>
      <tbody>
        {% for network in networks %}
        <tr>
          <td><p><a href="/network/{{ network.slug }}">{{ network.name }}</a></p></td>
          <td><p>{{ network.school_set.count }}</p></td>
          <td><p>{{ network|avg_demo:"fr_lunch"|floatformat}}%</p></td>
          <td><p>{{ network|avg_demo:"demo_asian"|floatformat}}%</p></td>
          <td><p>{{ network|avg_demo:"demo_aa"|floatformat}}%</p></td>
          <td><p>{{ network|avg_demo:"demo_hispanic"|floatformat}}%</p></td>
          <td><p>{{ network|avg_demo:"demo_white"|floatformat}}%</p></td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
  </div>
</div>

The main difference here is that where Django needs server-side view logic to create the networks list, Gatsby serializes it directly to JSON and templates it using a JavaScript map operation.

GraphQL as a data layer

So if Gatsby doesn't use view logic, how does it serialize objects to JSON for templating?

The answer is that Gatsby uses GraphQL as a source-agnostic data layer. What this means is that as long as you can write a plugin to make data available via GraphQL, Gatsby can use GraphQL queries to retrieve data for templating.

Let's take a look at the GraphQL query for the school networks example above:

const networkQuery = graphql`
  query {
    cps {
      cps_app_network {
        name
        slug
        fr_lunch
        demo_asian
        demo_aa
        demo_hispanic
        demo_white
        cps_app_school {
          school_id
        }
      }
    }
  }
`

This query works because we have a Hasura server sitting on top of our local Postgres database, turning GraphQL queries into SQL queries and serializing JSON for us. Notice how Hasura automatically figures out the reverse foreign-key relationship between schools and networks and autopopulates the cps_app_school object for us.

The reliance on GraphQL can sometimes feel clunky. Here's a not-so-nice example, showing how Gatsby pulls in and templates images:

https://github.com/datamade/cps-ssce-dashboard/blob/b48496879a1701208158b14ebff635411d4809a1/src/frontend/src/components/header.js#L18-L49

const Header = ({ siteTitle }) => (
  <StaticQuery
    query={graphql`
      query {
        logo: file(relativePath: { eq: "ssce_logo.png" }) {
          childImageSharp {
            fixed(height: 60) {
              ...GatsbyImageSharpFixed
            }
          }
        }
      }
    `}
    render={data => (
      <nav class="navbar navbar-expand-md navbar-light">
        <div class="navbar-left">
          <Link to="/">
            <Img fixed={data.logo.childImageSharp.fixed} />
          </Link>
        </div>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse d-flex flex-row-reverse" id="navbarSupportedContent">
          <NavItem href="/about" icon="question-circle" title="About" />
          <NavItem href="/networks" icon="address-card" title="Network List" />
          <NavItem href="/" icon="search" title="School Search" />
        </div>
      </nav>
    )}
  />

Note that you can see components in action above in our use of NavItem. Here's the definition of the NavItem component:

const NavItem = props => (
  <div className="nav-item" style={{ fontSize: '15px', margin: '15px' }}>
    <p style={{ textAlign: 'left', marginTop: '15px' }}>
      <Link to={props.href}>
        <i className={`fas fa-${props.icon}`}></i>&nbsp;
        <strong>{props.title}</strong>
      </Link>
    </p>
  </div>
)

The upshot is that all data -- image, CSV, markdown, whatever -- is accessible in Gatsby via GraphQL queries that populate React components. There's some necessary complexity here, but the implications are powerful: Gatsby can make a static site out of basically any data.

When is Gatsby a good fit?

Gatsby's a great fit for static apps with lots of data that don't require any dynamic server-side functionality like authentication or user storage. While there are options for accomplishing these features in entirely client-side apps (Netlify has some cool features for this) I don't yet have enough confidence in any of them to recommend we use them.

When I thought about this further, I realized that this description fit a good number of DataMade projects I'd worked on, including:

  • Neighborhood Opportunity Fund
  • Various IHS projects
  • Where to Buy
  • And now, LISC CNDA

Further reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment