Schema connections
This documentation isn’t up to date with latest schema customization changes. Help Gatsby by making a PR to update this documentation!
What are schema connections?
So far in schema generation, we have covered how GraphQL types are inferred, how query arguments for types are created, and how sift resolvers work. But all of these only allow querying down to a single node of a type. Schema connections is the ability to query over collections of nodes of a type. For example, if we want to query all markdown nodes by some criteria, it will allow us to write queries such as:
{
allMarkdownRemark(filter: {frontmatter: {tags: {in: "wordpress"}}}) {
edges {
node {
...
}
}
}
}
Other features covered by schema connections are aggregators and reducers such as distinct
, group
and totalCount
, edges
, skip
, limit
, and more.
Connection/Edge
A connection is an abstraction that describes a collection of nodes of a type, and how to query and navigate through them. In the above example query, allMarkdownRemark
is a Connection Type. Its field edges
is analogous to results
. Each Edge points at a node
(in the collection of all markdownRemark nodes), but it also points to the logical next
and previous
nodes, relative to the node
in the collection (meaningful if you provided a sort
arg).
Fun Fact: This stuff is all based on relay connections concepts
The ConnectionType also defines input args to perform paging using the skip/limit
pattern. The actual logic for paging is defined in the graphql-skip-limit library in arrayconnection.js. It is invoked as the last part of the run-sift function. To aid in paging, the ConnectionType also defines a pageInfo
field with a hasNextPage
field.
The ConnectionType is defined in the graphql-skip-limit connection.js file. Its construction function takes a Type, and uses it to create a connectionType. E.g passing in MarkdownRemark
Type would result in a MarkdownRemarkConnection
type whose edges
field would be of type MarkdownRemarkEdge
.
GroupConnection
A GroupConnection is a Connection with extended functionality. Instead of simply providing the means to access nodes in a collection, it allows you to group those nodes by one of its fields. It is a Connection
Type itself, but with 3 new fields: field
, fieldValue
, and totalCount
. It adds a new input argument to ConnectionType
whose value can be any (possibly nested) field on the original type.
The creation of the GroupConnection is handled in build-connection-fields.js. It’s added as the group
field to the top level type connection. This is most easily shown in the below diagram.
Let’s see this in practice. Say we were trying to group all markdown nodes by their author. We would query the top level MarkdownRemarkConnection
(allMarkdownRemark
) which would return a MarkdownRemarkConnection
with this new group input argument, which would return a MarkdownRemarkGroupConnectionConnection
field. E.g:
{
allMarkdownRemark {
group(field: frontmatter___author) {
fieldValue
edges {
node {
frontmatter {
title
}
}
}
}
}
}
Field enum value
The frontmatter___author
value is interesting. It describes a nested field. I.e, we want to group all markdown nodes by their frontmatter.author
field. The author field in each frontmatter subobject. So why not use a period? The problem is that GraphQL doesn’t allow periods in fields names, so we instead use ___
, and then in the resolver, we convert it back to a period.
The second interesting thing is that frontmatter___author
is not a string, but rather a GraphQL enum. You can verify this by using intellisense in GraphiQL to see all possible values. This implies that Gatsby has generated all possible field names. Which is true! To do this, we create an exampleValue and then use the flat library to flatten the nested object into string keys, using ___
delimiters. This is handled by the data-tree-utils.js/buildFieldEnumValues function.
Note, the same enum mechanism is used for creation of distinct
fields
Group Resolver
The resolver for the Group type is created in build-connection-fields.js. It operates on the result of the core connection query (e.g allMarkdownRemark
), which is a Connection
object with edges. From these edges, we retrieve all the nodes (each edge has a node
field). And now we can use lodash to group those nodes by the fieldname argument (e.g field: frontmatter___author
).
If sorting was specified (see below), we sort the groups by fieldname, and then apply any skip/limit
arguments using the graphql-skip-limit library. Finally we are ready to fill in our field
, fieldValue
, and totalCount
fields on each group, and we can return our resolved node.
Input filter creation
Just like in gql type input filters, we must generate standard input filters on our connectiontype arguments. As a reminder, these allow us to query any fields by predicates such as { eq: "value" }
, or { glob: "foo*" }
. This is covered by the same functions (in infer-graphql-object-type.js), except that we’re passing in Connection types instead of basic types. The only difference is that we use the sort
field (see below)
Sorting
A sort
argument can be added to the Connection
type (not the GroupConnection
type). You can sort by any (possibly nested) field in the connection results. These are enums that are created via the same mechanism described in enum fields. Except that the inference of these enums occurs in infer-graphql-input-type.js.
The Sort Input Type itself is created in build-node-connections.js and implemented by create-sort-field.js. The actual sorting occurs in run-sift (below).
Connection Resolver (sift)
Finally, we’re ready to define the resolver for our Connection type (in build-node-connections.js). This is where we come up with the name all${type}
(e.g allMarkdownRemark
) that is so common in Gatsby queries. The resolver is fairly simple. It uses the sift.js library to query across all nodes of the same type in redux. The big difference is that we supply the connection: true
parameter to run-sift.js
which is where sorting, and pagination is actually executed. See Querying with Sift for how this actually works.
Edit this page on GitHub