Gatsby Data Relationships With Foreign-Key Fields
June 3rd 2020
If you have worked with Gatsby you know that Gatsby relies on GraphQL schema and generation for it’s data layer. If you are developer coming from a more traditional MERN stack than wrapping your head around GraphQL can be a little daunting. Thus, jumping into Gatsby’s data layer can be a little intimidating. Building a website and/or web application the data layer is going to be an important aspect of the development process, so understanding GraphQL in your Gatsby application is important. This tutorial walks through relating two different content types/models in our GraphQL schema.
Gatsby uses GraphQL to enable page and StaticQuery components to declare what data they and their sub-components need. Then, Gatsby makes that data available in the browser when needed by your components.
https://www.gatsbyjs.org/docs/graphql-concepts/
Gatsby Source JSON Data
We will walk through sourcing data from two files: author.json
and post.json
. These files will represent our data such data would look like the data you could get from a mongo database or another NoSQL database. Inside our files our data structure would look like:
author.json
[
{
"name": "John Doe",
"email": "[email protected]",
"username": "jdoe"
}
]
This is a simple JSON Array which contains objects that hold data for each one of our authors. Inside our post.json
file we will also have similar data structure for our post data.
post.json
[
{
"title": "Post One",
"date": "2020-05-30",
"content": "Post one lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
"tags": [
"gatsby", "react", "graphql"
],
"author": "jdoe"
},
{
"title": "Post Two",
"date": "2020-05-31",
"content": "Post two lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
"tags": [
"gatsby", "react", "graphql"
],
"author": "jdoe"
}
]
As you can see we have a field inside our post objects called author
which is the username for the author of the post.
In order to source this data into our GraphQL data layer we will need to use two Gatsby plugins: gatsby-source-filesystem
and gatsby-transform-json
. Let’s add these plugins to our Gatsby project:
yarn add gatsby-source-filesystem gatsby-transform-json
With the two package dependencies installed we can add the plugins to our gatsby-config.js
file such as:
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `data`,
path: `${__dirname}/src/data`
}
},
{
resolve: `gatsby-transformer-json`,
options: {
typeName: ({ node }) => node.name.charAt(0).toUpperCase() + node.name.slice(1)
}
}
]
}
We are adding options
to each of our plugins. First, we’re adding path
option to our gatsby-source-filesystem
which specifies the path to our JSON files. Secondly, inside the options for our gatsby-transformer-json
we are specifying an option for our typeName
. The typeName
option allows us to specify the GraphQL Object Type Name that will be generated for us. By default the gatsby-transformer-json
generates the typeName based on the filename plus a Json
extension. So for instance, if we did not provide the typeName
option our GraphQL Schema for our author and post data would be authorJson
and postJson
to query single content objects or allAuthorJson
and allPostJson
to query array of data objects. Since we want to remove the Json
addition of our typeName
we are grabbing the filename from node.name
and we’re capitalizing the first letter so our typeName
will be Author
and Post
respectively – following GraphQL Type Name Conventions:
Type names should use
www.apollographql.comsPascalCase
. This matches how classes are defined in the languages mentioned above.
Gatsby GraphiQL Playground
Now that we have our plugins installed we can start up our Gatsby development server and navigate to our GraphiQL Playground. In your project folder run yarn develop
and when the development build is finished you can navigate to http://localhost:8000/___graphql
We can run a query to get our authors and posts from our GraphQL layer:
query {
allAuthor {
nodes {
username
name
email
}
}
allPost {
nodes {
title
content
date
tags
}
}
}
We should be able to see the returned data generated from our query:
You can see that we got the expected result to return from both of our files: author.json
and post.json
. But what if we want to related these two pieces of content together? For instance, it would make sense to want to get all of the posts that an author has published such as:
query getAuthorPosts {
allAuthor(filter: {username: {eq: "jdoe"}}) {
nodes {
name
email
username
posts {
title
date
content
tags
}
}
}
}
Likewise, it would be great if we could get all of the author’s information like the author’s name and email on each of the post objects when we query the posts:
query getPosts {
allPost {
nodes {
title
date
content
tags
author {
name
username
email
}
}
}
}
In order to query for our author’s posts and also get our post by author we need to create a relationship in Gatsby so our GraphQL data layer can include the related content in our queries.
Gatsby GraphQL Schema Customization
Gatsby, out of the box, has type inference for data which automatically generates our GraphQL schema for us. We saw this by installing our gatsby-source-filesystem
and gatsby-transformer-json
plugins and then making the query for our allPost
and allAuthor
data. We didn’t have to write any GraphQL type definitions or define any field types in our schema objects. Gatsby did all of this for us with it’s automatic type inference!
Gatsby is able to automatically infer a GraphQL Schema from your data, and in many cases, this is really all you need. There are however situations when you either want to explicitly define the data shape, or add custom functionality to the query layer – this is what Gatsby’s Schema Customization API provides.
https://www.gatsbyjs.org/docs/schema-customization/
However, in our case we want to create a relationship between our author.json
data and our post.json
data so we need to be able to manipulate the GraphQL Schema that is created by Gatsby. Fortunately, Gatsby offers a hook for customizing Gatsby’s GraphQL data: createSchemaCustomization which can be called from gatsby-node.js
Actions to customize Gatsby’s schema generation are made available in the
https://www.gatsbyjs.org/docs/schema-customization/createSchemaCustomization
(available in Gatsby v2.12 and above), andsourceNodes
APIs.
Let’s take a look at how we can use the createSchemaCustomization hook to customize our GraphQL schema. First, we will need to define the hook in our gatsby-node.js
file which expects a function that receives the actions
argument such as:
exports.createSchemaCustomization = ({ actions }) => {
{ createTypes } = actions
}
As you can see inside our actions
object we have a function called createTypes
as the name implies can be used to create GraphQL schema type definitions or to customize schema type definitions that are already defined from plugins (such as our Author
and Post
object types). You are able to update types that are already defined because Gatsby merges type definitions with inferred field types:
by default, explicit type definitions from
createTypes
will be merged with inferred field types
What this means, is that if we already have a GraphQL type called Author
and inside our createSchemaCustomization hook we create a type definition called Author
the field definitions in our hook will be merged with the type that was created in our plugins.
Let’s continue inside our gatsby-node.js
file with our exports.createSchemaCustomization
hook definition by defining our GraphQL Schema Definition Language (SDL) which we can pass to the createTypes
function.
Gatsby @link Directive
Gatsby has a few different ways to create our relationship between our Author
and Post
type, but one common way to do this is through the @link
directive which we can define in our SDL (schema definition language) and pass to our createTypes
action. Let’s look at what creating a relationship on our Post
schema to include the author’s information in our GraphQL query would look like:
exports.createSchemaCustomization = ({ actions }) => {
{ createTypes } = actions
const typeDefs = `
type Post implements Node {
author: Author @link(by: "username")
}
// implement this soon
// Author implements Node {}
`
createTypes(typeDefs)
}
Gatsby provides a special directive @link which creates a custom field resolver for us. If we specify the by
argument Gatsby will look for a field name on our source node (Post
node) called author
and pass the field value (for each node created) to the field name defined in by on our target node (Author
node). If you remember in our source node Post
data structure we specified the author
field such as:
{
"title": "Post One",
"date": "2020-05-30",
"content": "Post one lorem ipsum dolor sit amet, consectetur ...",
"tags": [
"gatsby", "react", "graphql"
],
"author": "jdoe"
}
And in our Author
data structure we have a field name called username
:
{
"name": "John Doe",
"email": "[email protected]",
"username": "jdoe"
}
Gatsby will use @link
to create a foreign-key relationship from our source node to our target node using the by argument we defined.
Now we are able to restart our GraphiQL server by running yarn develop
and we can navigate to http://localhost:8000/___graphql and run the query:
query getPosts {
allPost {
nodes {
title
date
content
tags
author {
name
username
email
}
}
}
}
As you can see we are including our author
field which is now a Node
type and not a String
and since it’s a Node type we can include the related fields such as name
, username
and email
. This is a great step! But what if we wanted to create a back reference from our Author
type to our Post
type in order to get all posts for a certain author?
query getAuthorPosts {
allAuthor(filter: {username: {eq: "jdoe"}}) {
nodes {
name
email
username
posts {
title
date
content
tags
}
}
}
}
This might be handy if you are on the author’s profile page and you want to see all of the post that they have written. We can easily do this by adding another type definition to our createTypes
action and using the @link
directive to create a back-link reference:
exports.createSchemaCustomization = ({ actions }) => {
{ createTypes } = actions
const typeDefs = `
type Post implements Node {
author: Author @link(by: "username")
}
type Author implements Node {
posts: [Post] @link(by: "author.username", from: "username")
}
`
createTypes(typeDefs)
}
The from
argument allows us to create a back-link reference for our Author
nodes.
The optional
https://www.gatsbyjs.org/docs/schema-customization/#foreign-key-fieldsfrom
argument allows getting the field on the current type which acts as the foreign-key to the field specified inby
. In other words, youlink
onfrom
toby
. This makesfrom
especially helpful when adding a field for back-linking.
Since our Post
node field author
is no longer a String
and is now an object, we have to use by: "author.username"
with object dot notation to get the value of our username
argument. Now, with any luck we are able to restart GraphiQL server and run our query:
query getAuthorPosts {
allAuthor(filter: {username: {eq: "jdoe"}}) {
nodes {
name
email
username
posts {
title
date
content
tags
}
}
}
}
And we should see the expected output with all of our post data included!
We have successfully created a two-way relationship between our Post
nodes and our Author
nodes by using the special @link
directive in our createSchemaCustomization hook! This can open a whole new world of possibilities when shaping and forming our data structure in Gatsby. Until next time, stay curious, stay creative.