Dynamic JAMStack with Gatsby and Hasura GraphQL
JAMStack is a modern architecture for building web apps. The developer workflow is vastly improved in comparison to the traditional CMS based experience.
With a JAMStack powered web app, you get faster performance due to pre-built markup, the ability to deliver scalable apps via CDN, clean separation of concerns and security between the app UI and the data from the API and finally easy deployments with automated builds and git integration.
But what better than making it even more extensible and powerful by making the site dynamic, powered by Hasura's GraphQL APIs. In this article we will look at how a dynamic app can be built with all the benefits of JAMStack using Hasura and GraphQL.
Getting Started
JAMStack doesn’t have any opinion on the tooling used as long as the output is a good old static site. If you are a frontend developer looking to onboard the JAMStack workflow, you might be wondering if your popular framework supports it. There are many popular static site generators around modern frontend frameworks like React and Vue.
In this post, we will look at the most popular React static site generator - Gatsby.
Static Sites with Gatsby
Let’s create a new gatsby site and build a simple static site to list down articles.
- Install the gatsby CLI -
npm install -g gatsby-cli
- Create a new site -
gatsby new gatsby-site
- Start development -
gatsby develop
- Navigate to
http://localhost:8000
to see the app running.
Source data during the build phase, rebuild on data changes
Static site generators source data during the build phase and generates an entirely static website. Now that you have the site running, change any file in src/pages
to see the rebuild happening for a live change. This is of course doing a live reload of the changes in a development setup.
Let’s generate production build to output HTML files.
Execute the following commands:
gatsby build && gatsby serve
to build and serve the static site. You should see the page looking something like this:
Gatsby generates HTML in build phase. So what happens underneath when you run gatsby build
? Gatsby traverses through every path of your app and compiles flat HTML files out of it. It uses specified data sources to compile the HTML. The site you ran above has static data written directly in HTML. Any change to data in the HTML would be rebuilt the next time you run the build command.
Great! So any static content becomes HTML that can be served by a simple HTTP server. But this is not enough for most apps. Isn't it? Typically you would need data coming from a database, content specific to users or restricted behind a paywall or even interactive actions on the page with data processing. The data requirements quickly become dynamic.
Adding Dynamic Data
In the above static site, what if the article list is coming from a database instead of hand writing each one of them? HTML pages have to be built for as many articles the site has. Static site generators like Gatsby come with the ability to connect to external data sources. GraphQL can be one of them.
Gatsby comes with a native GraphQL support and lets you add any GraphQL API as a data source plugin among a list of external data sources. GraphQL APIs can also be explored using GraphiQL that runs at http://localhost:8000/___graphql
How Hasura fits into Dynamic JAMStack
Hasura is an open-source engine that gives you realtime GraphQL APIs on new or existing Postgres databases, with built-in support for stitching custom GraphQL APIs.
Hasura fits into JAMStack by being the data source for Gatsby via its unified GraphQL API endpoint. It also supports Authorization (which is not the scope of this post)
Follow the instructions in the docs to deploy Hasura and create the table users and articles required for the app. Note the Heroku URL for GraphQL Endpoint. You will be configuring this in the Gatsby app next to fetch data.
Now let’s add the Hasura data source to get content from Postgres into Gatsby.
We use the gatsby plugin gatsby-source-graphql
to connect to any external GraphQL API. Do note that all static site generators would have some way to connect to external API for fetching data.
In your gatsby-config.js
, the plugin configuration would look like:
{
resolve: 'gatsby-source-graphql',
options: {
typeName: 'HASURA',
fieldName: 'hasura',
url: `${ process.env.HASURA_GRAPHQL_URL }`,
},
},
Now head to http://localhost:8000/___graphql
to explore the hasura source with articles
and users
.
In the app, head to src/pages/index.js
and add the following GraphQL Query to fetch data from Postgres.
export const query = graphql`
query ArticleQuery {
hasura {
articles {
id
title
}
}
}
`
Now change the listing of articles to:
const IndexPage = ({data}) => (
<Layout>
<SEO title="Home" />
<ul>
{data.hasura.articles.map((article) => (
<Link key={article.id} to={"/"+article.id}><li>{article.title}</li></Link>
))}
</ul>
</Layout>
)
This should now fetch articles from Postgres database with its id
and title
.
Awesome! So the articles now come from the database and sourced at build time.
Here's the source repo for code reference.
Separate Static and Dynamic Data
Now let’s consider the case where the listing page has articles which cannot be accessed without authorization. Only once the user logs in, articles which are private can be accessed by them. This is a typical use case for a dynamic app where content is different for logged in users. There’s also an account page which can be accessed only by users who have logged in. Obviously these pages cannot be built during compile time. We have two components to this:
- Setting up Auth (for example: Auth0)
- Configuring permissions for users
Setting up Auth
The first part is to setup Authentication for our app. Head to docs to setup Auth0. You can replace it with any Auth provider or write your own custom Auth. Once the Auth provider is in place, the gatsby implementation is all about restricting content on specific routes based on whether a user is authenticated or not.
Protected Routes
The gatsby-source-graphql
setup configured earlier would work during build phase. If we need to make authenticated requests on the client side, we need to setup react-apollo on the client and set the appropriate headers before making the request. Here is an example of how to fetch articles dynamically on the client side.
import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag';
// This query is executed at run time by Apollo.
const APOLLO_QUERY = gql`
{
articles {
id
title
user {
id
name
}
}
}
`;
export default ({token}) => {
const { loading, error, data } = useQuery(APOLLO_QUERY, {context: {headers: {'Authorization': 'Bearer ' + token}}});
return (
<div>
{loading && <p>Loading...</p>}
{error && <p>Error: ${error.message}</p>}
{data && data.articles && data.articles.map((article) => {
return (
<div key={article.id}>{article.id} - {article.title}</div>
);
}
)}
</div>
);
};
If you try to access the articles page without logging in, you should be redirected to the login page. Now refresh the page to do a login and see if the articles are visible.
The second piece is to setup permissions for the users. Let's look at how that can be done using Hasura.
Hasura Role Based Access Control
Hasura has role based access control where data fetching can be limited to specified conditions based on what role the logged in user is. Now this can be used to restrict content that can be accessed by users of the app.
In the above app, we have an articles listing page which is protected using authentication. But now, let's restrict what content can be seen by each logged in user.
Before that, we will setup a select permission for role user restricting the user id to be equal to session variable x-hasura-user-id. (This will be set in the JWT token)
Head to the Hasura console to setup permissions like below.
The permissions have been set. Now try inserting some values for different users.
Refresh the page and you should be able to see only your own articles. This is because of the filter applied in the permissions for the role user.
In the above app, we started off with a simple static page which lists out the articles and proceeded to integrate Hasura as a data source for Gatsby. We were able to fetch articles from the database. Then we were able to restrict the content generated based on which user was logged in.
Here's the source repo for reference.
Deployment
The dynamic app is now ready! Let’s see how it can be deployed. As you know by now, despite sourcing content from a database it is a static site with HTML files for each page. It can be deployed on various providers.
But for the deployment provider to be JAMStack compliant, it needs to support Git automation where the source code lives in git and git push should trigger an automated build and deploy latest changes on the cloud.
Popular choices are Netlify, Github Pages and Heroku while Cloudflare is the popular choice for CDN. I would recommend using Netlify for deploying any JAMStack site.
Netlify Deployment
The deployment experience on Netlify is seamless. It comes with its own CDN as a bonus. Netlify supports Git automation. Just connect your git repo for automated builds on Netlify.
Other notable options are Heroku, Zeit Now. Static sites can also be hosted on AWS S3 with a CDN like Cloudfront.
Note that faster the compiler of static site generator, quicker and better the deployment experience.
Other static site generators
In case you are looking for the static site generators around React and Vue, here are a few options:
The frameworks based on React include react-static (Hasura Guide), apart from Gatsby.
The frameworks based on Vue include Nuxt.js (Hasura Guide) and Gridsome (Hasura Guide).
Some of the other popular static site generators include Jekyll, Hugo, Hexo, VuePress. They don’t leverage the power of frameworks or data source integrations but are still solid choices to get started.