Gatsby GraphQL Custom Field Resolver Related Types
March 8th 2020
We ran into a scenario recently where we wanted to relate an image file to a field in our JSON object definition. We were using Gatsby Transformer JSON to transform our JSON files into GraphQL Types that we can query using the Gatsby GraphQL and since we were using the Gatsby Source Filesystem we had access to the source image files but we needed a way to relate those to a field in our Type definitions. Today we’re going to do a quick walk through using a Gatsby’s createResolver API to add custom resolvers to fields to related an images
field in our JSON file to the appropriate file found in the File
definition type.
First we’re going to setup a few plugins to help us create the type definitions. First, Gatsby Source Filesystem for sourcing both our .json
files and .jpg
files. Second, we’re using Gatsby Transformer JSON to create our type definitions for our Project
type. We’re going to store the .json
files we want to reference in /src/data
folder and the images that we’re associating with our project in the /src/data/images
folder. We install the two plugins gatsby-source-filesystem
and gatsby-transformer-json
:
yarn add gatsby-source-filesystem gatsby-transformer-json
And configure them in our gatsby-config.js
file:
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `data`,
path: `src/data`,
ignore: [`**/\.*`], // ignore files starting with a dot
},
},
{
resolve: `gatsby-transformer-json`,
options: {
typeName: ({ node }) => node.name
},
},
]
}
Let’s walk through the plugin configuration options quick. For the Gatsby Source Filesystem we’re setting the path to look for files in src/data
folder to source the files. There will be a GraphQL type created called File
and allFile
for traversing file nodes. As for the Gatsby Transformer Json plugin we are setting the typeName
field to be the name of the “node” which the source file plugin creates and in our example the file: Project.json
the typeName would resolve to Project
With those two plugins setup we can turn our attention back to the solution. Our problem is that we need to create a custom resolver for an image
field on our Project
type to relate the File
type to our project. Let’s take a look at what our Project.json
file looks like:
[
{
"name": "Project One",
"url": "https://example.com",
"technologies": ["Nuxt.js", "Bootstrap CSS", "GSAP"],
"start": "2018-10-15",
"end": "2020-01-15",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
},
{
"name": "Project Two",
"url": "https://example.com",
"technologies": ["Nuxt.js", "Firebase", "Bulma CSS"],
"start": "2018-10-15",
"end": "2020-01-15",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
}
]
As you can see our Project.json
nodes do not contain a filed called image
as we will dynamically create this later using Gatsby’s createTypes
action. We can use the file gatsby-node.js
and the sourceNodes API to further define our Schema types for our Project
GraphQL type. The sourceNodes API exposes an action called createTypes
that we can use to extend, overwrite or define GraphQL types. In our case we’re going to overwrite the Project
type created by the Gatsby Transformer Json plugin so we have full control over the Type definition:
exports.sourceNodes = ({ actions }) => {
actions.createTypes(`
type Project implements Node @dontInfer {
id: ID!
name: String!
technologies: [String!]!
start: Date! @dateformat
end: Date! @dateformat
url: String!
slug: String!
image: File
description: String!
}`
)
}
Opting out of type inference
There are however advantages to providing full definitions for a node type, and bypassing the type inference mechanism altogether. With smaller scale projects inference is usually not a performance problem, but as projects grow the performance penalty of having to check each field type will become noticeable.
Gatsby allows to opt out of inference with the
gatsby.org/docs@dontInfer
type directive – which in turn requires that you explicitly provide type definitions for all fields.
We decided to use @dontInfer
to control the Type definitions of our Project
type. Note that we defined a new field in our Project
type called image
and set the type definition to File
. Now that we created our field definition we can use Gatsby’s createResolvers API to create a custom resolver for our image
field. We’re going to create a helper function that “slugifies” the name of the project and returns a string (which we will use to name the image we want to relate to our image
field).
exports.createResolvers = ({ createResolvers }) => {
//custom helper function to slugify a string
const slugify = str => {
return str
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)+/g, "")
}
}
Now that we have our helper function we can work on the createResolvers
definition:
exports.createResolvers = ({ createResolvers }) => {
//custom helper function to slugify a string
const slugify = str => {
return str
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)+/g, "")
}
createResolvers({
Project: {
image: {
type: "File",
resolve: (source, args, context) => {
return context.nodeModel.runQuery({
query: {
filter: {
name: {
eq: slugify(source.name)
}
}
},
type: "File",
firstOnly: true,
})
}
}
},
})
}
There’s a lot going on here but let’s walk through it. We are using the createResolvers
action to create a resolver for our Project Type image
field and we’re using the runQuery
to run a GraphQL query to search for File
types which name equals the slugified string of the name of our project. The resolve
callback receives the source
object in which we can get the Project’s name and pass that to our slugify function in order to use that as a filter in our runQuery
function. Let’s say we have a project named: “My Special Project” our runQuery
would look like:
return context.nodeModel.runQuery({
query: {
filter: {
name: {
eq: `my-special-project`
}
}
},
type: "File",
firstOnly: true,
})
Now all we have to do is put an image file named my-special-project.jpg
in our /src/data/images
directory and our runQuery
will attach our resolver
field allowing us to include our image in a GraphQL query such as:
query ($projectID: String!) {
project(id: { eq: $projectID }) {
name
description
technologies
url
id
image {
publicURL
}
start(formatString: "MMM Do YYYY")
end(formatString: "MMM Do YYYY")
}
}
And our data returned will look like:
{
"data": {
"project": {
"id": "e3182852-50f7-5a98-afab-0b2858a6b859",
"url": "https://myspecialproject.com",
"technologies": [
"Nuxt.js",
"Bootstrap CSS",
"GSAP"
],
"start": "2018-10-15",
"slug": "/project/my-special-project",
"name": "My Special Project",
"image": {
"publicURL": "/static/my-special-project-55083ac97b3a49cc4a60442c1bc5253b.jpg"
},
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Felis bibendum ut tristique et. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida cum. Ut sem viverra aliquet eget. At varius vel pharetra vel turpis nunc eget. Tristique senectus et netus et malesuada fames. Semper risus in hendrerit gravida rutrum quisque non tellus orci. Pulvinar etiam non quam lacus suspendisse faucibus. Posuere urna nec tincidunt praesent semper feugiat. Egestas dui id ornare arcu odio ut sem nulla. Porttitor eget dolor morbi non arcu risus. Lacus sed viverra tellus in hac habitasse platea. Augue eget arcu dictum varius duis. Nisi lacus sed viverra tellus. Suspendisse ultrices gravida dictum fusce ut placerat orci. Blandit turpis cursus in hac habitasse. Et ultrices neque ornare aenean.",
"end": "2020-01-15"
}
}
}
Note the image: { projectURL: "/static/my-special-project-55083ac97b3a49cc4a60442c1bc5253b.jpg" }
field and you can see we’ve successfully attached our image to our project!
We have successfully created a field resolver that uses runQuery
which executes a GraphQL query to resolve the image
field to filter type File
to the slugified Project
name in order to create a relationship between our Project
schema and our Files
schema.
Hopefully this will help in your next Gatsby project! Until next time, stay curious, stay creative!