Movatterモバイル変換


[0]ホーム

URL:


Migrate to Netlify Today

Netlify announces the next evolution of Gatsby Cloud.Learn more

SupportLog In

One of Gatsby’s main strengths is the ability to query data from a variety ofsources in a uniform way with GraphQL. For this to work, a GraphQL Schema mustbe generated that defines the shape of the data.

Gatsby is able to automatically infer a GraphQL Schema from your data, and inmany cases, this is really all you need. There are however situations when youeither want to explicitly define the data shape, or add custom functionality tothe query layer - this is what Gatsby’s Schema Customization API provides.

The following guide walks through some examples to showcase the API.

This guide is aimed at plugin authors, users trying to fix GraphQL schemascreated by automatic type inference, developers optimizing builds for largersites, and anyone interested in customizing Gatsby’s schema generation.As such, the guide assumes that you’re somewhat familiar with GraphQL typesand with using Gatsby’s Node APIs. For a higher level approach to usingGatsby with GraphQL, refer to theAPI reference.

Explicitly defining data types

The example project is a blog that gets its data from local Markdown files whichprovide the post contents, as well as author information in JSON format. There are alsooccasional guest contributors whose info is kept in a separate JSON file.

To be able to query the contents of these files with GraphQL, they need to first beloaded into Gatsby’s internal data store. This is what source and transformerplugin accomplish - in this casegatsby-source-filesystem andgatsby-transformer-remark plusgatsby-transformer-json. Every markdown postfile is hereby transformed into a “node” object in the internal data store witha uniqueid and a typeMarkdownRemark. Similarly, an author will berepresented by a node object of typeAuthorJson, and contributor info will betransformed into node objects of typeContributorJson.

The Node interface

This data structure is represented in Gatsby’s GraphQL schema with theNodeinterface, which describes the set of fields common to node objects created bysource and transformer plugins (id,parent,children, as well as a coupleofinternal fields liketype). In GraphQL Schema Definition Language (SDL),it looks like this:

Types created by source and transformer plugins implement this interface. Forexample, the node type created bygatsby-transformer-json forauthors.jsonwill be represented in the GraphQL schema as:

Automatic type inference

It’s important to note that the data inauthor.json does not provide typeinformation of the Author fields by itself. In order to translate the datashape into GraphQL type definitions, Gatsby has to inspect the contents ofevery field and check its type. In many cases this works very well and it isstill the default mechanism for creating a GraphQL schema.

There are however two problems with this approach: (1) it is quitetime-consuming and therefore does not scale very well and (2) if the values on afield are of different types Gatsby cannot decide which one is the correct one.A consequence of this is that if your data sources change, type inference couldsuddenly fail.

Both problems can be solved by providing explicit type definitions for Gatsby’sGraphQL schema.

Creating type definitions

Look at the latter case first. Assume a new author joins the team, but in thenew author entry there is a typo on thejoinedAt field: “201-04-02” which isnot a valid Date.

This will confuse Gatsby’s type inference since thejoinedAtfield will now have both Date and String values.

Fixing field types

To ensure that the field will always be of Date type, you can provide explicittype definitions to Gatsby with thecreateTypes action.It accepts type definitions in GraphQL Schema Definition Language:

Note that the rest of the fields (name,firstName etc.) don’t have to beprovided, they will still be handled by Gatsby’s type inference.

Actions to customize Gatsby’s schema generation are made available in thecreateSchemaCustomization(available in Gatsby v2.12 and above),andsourceNodes APIs.

Opting out of type inference

There are however advantages to providing full definitions for a node type, andbypassing the type inference mechanism altogether. With smaller scale projectsinference is usually not a performance problem, but as projects grow theperformance penalty of having to check each field type will become noticeable.

Gatsby allows to opt out of inference with the@dontInfer type directive - whichin turn requires that you explicitly provide type definitions for all fieldsthat should be available for querying:

Note that you don’t need to explicitly provide the Node interface fields (id,parent, etc.), Gatsby will automatically add them for you.

If you wonder about the exclamation marks - those allowspecifying nullabilityin GraphQL, i.e. if a field value is allowed to benull or not.

Defining media types

You can specify the media types handled by a node type using the@mimeTypes extension:

The types passed in are used to determine child relations of the node.

Defining child relations

The@childOf extension can be used to explicitly define what node types or media types a node is a child of and immediately addchild[MyType] andchildren[MyType] fields on the parent.

Thetypes argument takes an array of strings and determines what node types the node is a child of:

ThemimeTypes argument takes an array of strings and determines what media types the node is a child of:

ThemimeTypes andtypes arguments can be combined as follows:

Nested types

So far, the example project has only been dealing with scalar values (String andDate;GraphQL also knowsID,Int,Float,Boolean andJSON). Fields canhowever also contain complex object values. To target those fields in GraphQL SDL, youcan provide a full type definition for the nested type, which can be arbitrarilynamed (as long as the name is unique in the schema). In the example project, thefrontmatter field on theMarkdownRemark node type is a good example. Say youwant to ensure thatfrontmatter.tags will always be an array of strings.

Note that withcreateTypes you cannot directly target aFrontmatter typewithout also specifying that this is the type of thefrontmatter field on theMarkdownRemark type, The following would fail because Gatsby would have no wayof knowing which field theFrontmatter type should be applied to:

It is useful to think about your data, and the corresponding GraphQL schema, byalways starting from the Node types created by source and transformer plugins.

Note that theFrontmatter type must not implement the Node interface sinceit is not a top-level type created by source or transformer plugins: it has noid field, and is there to describe the data shape on a nested field.

Gatsby Type Builders

In many cases, GraphQL SDL provides a succinct way to provide type definitionsfor your schema. If however you need more flexibility,createTypes alsoaccepts type definitions provided with the help of Gatsby Type Builders, whichare more flexible than SDL syntax but less verbose thangraphql-js. They areaccessible on theschema argument passed to Node APIs.

Gatsby Type Builders allow referencing types as simple strings, and accept fullfield configs (type,args,resolve). When defining top-level types, don’t forgetto passinterfaces: ['Node'], which does the same for Type Builders as addingimplements Node does for SDL-defined types. It is also possible to opt out of typeinference with Type Builders by setting theinfer type extension tofalse:

Type Builders also exist for Input, Interface Union, Enum and Scalar types:buildInputObjectType,buildInterfaceType,buildUnionType,buildEnumType andbuildScalarType.Note that thecreateTypes action also acceptsgraphql-js types directly,but usually either SDL or Type Builders are the better alternatives.

Foreign-key fields

In the example project, thefrontmatter.author field onMarkdownRemark nodes to expand the provided field value to a fullAuthorJson node.For this to work, there has to be provided a custom field resolver. (see below formore info oncontext.nodeModel)

What is happening here is that you provide a custom field resolver that asksGatsby’s internal data store for the full node object with the specifiedid andtype.

Because creating foreign-key relations is such a common use case, Gatsbyluckily also provides a much easier way to do this — with the help ofextensions or directives. It looks like this:

This example assumes that your markdown frontmatter is in the shape of:

And your author JSON looks like this:

You provide a@link directive on a field and Gatsby will internallyadd a resolver that is quite similar to the one written manually above. If noargument is provided, Gatsby will use theid field as the foreign-key,otherwise the foreign-key has to be provided with theby argument. Theoptionalfrom argument allows getting the field on the current type which acts as the foreign-key to the field specified inby.In other words, youlinkonfromtoby. This makesfrom especially helpful when adding a field for back-linking.

For the above example you can read@link this way: Use the value from the fieldFrontmatter.reviewers and match it by the fieldAuthorJson.email.

Keep in mind that in the example above, the link ofposts inAuthorJson works by defining a path to a node becausefrontmatter andauthor are both objects. If, for example, theFrontmatter type had a list ofauthors instead (frontmatter.authors.email), you would need to define it like this withelemMatch:

You can also provide a custom resolver withGatsby Type Builders orcreateResolvers API to link arrays.

Extensions and directives

Out of the box, Gatsby providesfour extensions that allow adding custom functionality to fields without having to manually write field resolvers:

  1. Thelink extension has already been discussed above
  2. dateformat allows adding date formatting options
  3. fileByRelativePath is similar tolink but will resolve relative paths when linking toFile nodes
  4. proxy is helpful when dealing with data that contains field names withcharacters that are invalid in GraphQL or to alias fields

To add an extension to a field you can either use a directive in SDL, or theextensions property when using Gatsby Type Builders:

The above example addsdate formatting optionsto theAuthorJson.joinedAt and theMarkdownRemark.frontmatter.publishedAtfields. Those options are available as field arguments when querying those fields:

publishedAt is also provided a defaultformatString which will be usedwhen no explicit formatting options are provided in the query.

If the JSON would contain keys you’d want toproxy to other names, you could do it like this:

You can also combine multiple extensions (built-in and custom ones).

Aliasing fields

You can use the@proxy directive to alias (nested) fields to another field on the same node. This is helpful if e.g. you want to keep the shape you have to query flat or if you need it to keep things backwards compatible.

If you’d add a new field usingcreateNodeField to theMarkdownRemark nodes (change this check if you use another source/type) like this:

TheHello World would be queryable at:

To be able to querysomeInformation like this instead you have to alias thefields.someInformation field.

Setting default field values

For setting default field values, Gatsby currently does not (yet) provide anout-of-the-box extension, so resolving a field to a default value (instead ofnull) requires manually adding a field resolver. For example, to add a defaulttag to every blog post:

Creating custom extensions

With thecreateFieldExtension actionit is possible to define custom extensions as a way to add reusable functionalityto fields. Say you want to add afullName field toAuthorJsonandContributorJson.

You could write afullNameResolver, and use it in two places:

However, to make this functionality available to other plugins as well, and makeit usable in SDL, you can register it as a field extension.

A field extension definition requires a name, and anextend function, whichshould return a (partial) field config (an object, withtype,args,resolve)which will be merged into the existing field config.

This approach becomes a lot more powerful when plugins provide custom fieldextensions. Avery basic markdown transformer plugin could for example providean extension to convert markdown strings into HTML:

It can then be used in anycreateTypes call by adding the directive/extensionto the field:

Note that in the above example, there have been additional provided configuration optionswithargs. This is e.g. useful to provide default field arguments:

Also note that field extensions can decide themselves if an existing field resolvershould be wrapped or overwritten. The above examples have all decided to returna newresolve function. Because theextend function receives the current fieldconfig as its second argument, an extension can also decide to wrap an existing resolver:

If multiple field extensions are added to a field, resolvers are processed in this order:first a custom resolver added withcreateTypes (orcreateResolvers) runs, then fieldextension resolvers execute from left to right.

Finally, note that in order to get the currentfieldValue, you usecontext.defaultFieldResolver.

createResolvers API

While it is possible to directly passargs andresolvers along the typedefinitions using Gatsby Type Builders, an alternative approach specificallytailored towards adding custom resolvers to fields is thecreateResolvers NodeAPI.

Note thatcreateResolvers allows adding new fields to types, modifyingargsandresolver — but not overriding the field type. This is becausecreateResolvers is run last in schema generation, and modifying a field typewould mean having to regenerate corresponding input types (filter,sort),which you want to avoid. If possible, specifying field types should be done withthecreateTypes action.

Accessing Gatsby’s data store from field resolvers

As mentioned above, Gatsby’s internal data store and query capabilities areavailable to custom field resolvers on thecontext.nodeModel argument passedto every resolver. Accessing node(s) byid (and optionaltype) is possiblewithgetNodeById andgetNodesByIds. To get all nodes, or allnodes of a certain type, usefindAll.And running a query from inside your resolver functions can be accomplishedwithfindAll as well, which acceptsfilter andsort query arguments.

You could for example add a field to theAuthorJson type that lists all recentposts by an author:

When usingfindAll to sort query results, be aware that bothsort.fieldsandsort.order areGraphQLList fields. Also, nested fields onsort.fieldshave to be provided in dot-notation (not separated by triple underscores).For example:

Custom query fields

One powerful approach enabled bycreateResolvers is adding custom root queryfields. While the default root query fields added by Gatsby (e.g.markdownRemark andallMarkdownRemark) provide the whole range of queryoptions, query fields designed specifically for your project can be useful. Forexample, you can add a query field for all external contributors to the example blogwho have received their swag:

Because you might also be interested in the reverse - which contributors haven’treceived their swag yet - why not add a (required) custom query arg?

It is also possible to provide more complex custom input types which can be defineddirectly inline in SDL. You could for example add a field to theContributorJsontype that counts the number of posts by a contributor, and then add a custom rootquery fieldcontributors which acceptsmin ormax arguments to only returncontributors who have written at leastmin, or at mostmax number of posts:

Taking care of hot reloading

When creating custom field resolvers, it is important to ensure that Gatsbyknows about the data a page depends on for hot reloading to work properly. Whenyou retrieve nodes from the store withcontext.nodeModel methods,it is usually not necessary to do anything manually, because Gatsby will registerdependencies for the query results automatically. If you want to customize this,you can add a page data dependency either programmatically withcontext.nodeModel.trackPageDependencies, or with:

Custom Interfaces and Unions

Finally, say you want to have a page on the example blog that lists allteam members (authors and contributors). What you could do is have two queries,one forallAuthorJson and one forallContributorJson and manually mergethose. GraphQL however provides a more elegant solution to these kinds ofproblems with “abstract types” (Interfaces and Unions). Since authors andcontributors actually share most of the fields, you can abstract those up intoaTeamMember interface and add a custom query field for all team members(as well as a custom resolver for full names):

To use the newly added root query field in a page query to get the full names ofall team members, you could write:

Queryable interfaces

Since Gatsby 3.0.0, you can use interface inheritance to achieve the same thing as above:TeamMember implements Node. This will treat the interface like a normal top-level type thatimplements theNode interface, and thus automatically add root query fields for the interface.

When querying, use inline fragments for the fields that are specific to the typesimplementing the interface (i.e. fields that are not shared):

Including the__typename introspection field allows to check the node type when iteratingover the query results in your component:

Note: All types implementing a queryable interface must also implement theNode interface

Start building today on Netlify!

Gatsby is powered by the amazing Gatsby
community and Gatsby, the company.


[8]ページ先頭

©2009-2025 Movatter.jp