Plugins

Nexus ships with a plugin API which allows you to define your own abstractions when building out a GraphQL schema. The plugin layer allow you to:

  • Define new options for types and fields in a type-safe manner
  • Layer runtime execution before and after a resolver
  • Modify schema configuration
  • Customize the emit behavior of TypeScript type generation (coming soon)

We also ship with several plugins out of the box, and will have more in the near future:

Defining a sample plugin:

1import { plugin } from '@nexus/schema'
2
3export const myErrorGuardPlugin = plugin({
4 name: 'MyErrorGuardPlugin',
5 description: 'Catches errors and logs them to Sentry, ',
6})

Plugin Config:

name

Every plugin is required to have a unique name identifying the plugin. This is used in error-messages and warnings.

description

A string describing the plugin. Currently not used for anything, but it could be used in the future to automatically create documentation for the stack of plugins used in Nexus.

onInstall(builder)

The "onInstall" hook is used as it sounds - it is invoked once before any of the types are processed for the schema. This hook is primarily useful in allowing a plugin to add "dynamics fields" to augment the API of the definition block.

1plugin({
2 name: 'onInstallExample',
3 onInstall(builder) {},
4})

The builder option provided has several properties, which allow you to influence the schema via the plugin.

onBeforeBuild(builder)

The "onBeforeBuild" is called after all onInstall, but just before the schema is constructed.

1plugin({
2 name: 'onBeforeBuildExample',
3 onBeforeBuild(builder) {},
4})

onAfterBuild(schema)

The "onAfterBuild" hook is provided the schema, and is useful to validate contract of the schema with the expectations of the plugin. The nullabilityGuard

1plugin({
2 name: 'onAfterBuildExample',
3 onAfterBuild(schema) {},
4})

onObjectDefinition(t, objectConfig)

The "onObjectDefinition" hook is called when an objectType is created, and is provided t, the object passed into the definition block, as well as the config of the object.

1export const NodePlugin = plugin({
2 name: 'NodePlugin',
3 description: 'Allows us to designate the field used to determine the "node" interface',
4 objectTypeDefTypes: `node?: string | core.FieldResolver<TypeName, any>`,
5 onObjectDefinition(t, { node }) {
6 if (node) {
7 let resolveFn
8 if (typeof node === 'string') {
9 const fieldResolve: FieldResolver<any, any> = (root, args, ctx, info) => {
10 return `${info.parentType.name}:${root[node]}`
11 }
12 resolveFn = fieldResolve
13 } else {
14 resolveFn = node
15 }
16 t.implements('Node')
17 t.id('id', {
18 nullable: false,
19 resolve: resolveFn,
20 })
21 }
22 },
23})

Usage:

1const User = objectType({
2 name: 'User',
3 node: 'id', // adds `id` field
4 definition(t) {
5 t.string('name')
6 },
7})

onCreateFieldResolver(config)

Every ObjectType, whether they are defined via Nexus' objectType api, or elsewhere is given a resolver. The defaultFieldResolver is provided if none is specified by the field definition.

When the resolver is created for a type, this can optionally return a "middleware" which wraps the resolve behavior of the field. Here's an example of writing a "LogMutationTimePlugin", which logs how long it takes a mutation to complete:

1const LogMutationTimePlugin = plugin({
2 name: 'LogMutationTimePlugin',
3 onCreateFieldResolver(config) {
4 if (config.parentTypeConfig.name !== 'Mutation') {
5 return
6 }
7 return async (root, args, ctx, info, next) => {
8 const startTimeMs = new Date().valueOf()
9 const value = await next(root, args, ctx, info)
10 const endTimeMs = new Date().valueOf()
11 console.log(`Mutation ${info.operation.name} took ${endTimeMs - startTimeMs} ms`)
12 return value
13 }
14 },
15})

onMissingType

The "onMissingType" hook occurs when a type is provided as a string, but was never explicitly defined. This can be helpful in situations where you have boilerplate types which can be constructed generically / programmatically based on their name.

Here is an example of a plugin which creates a "ResourceResponse" type whenever you see a string:

1/**
2 * Creates a ____ResourceResponse type
3 *
4 * type OrganizationResourceResponse {
5 * ok: Boolean!
6 * resource: Organization!
7 * query: Query!
8 * }
9 *
10 * @param resource
11 */
12export function resourceResponse(resource: string) {
13 return objectType({
14 name: `${resource}ResourceResponse`,
15 definition(t) {
16 t.boolean('ok', () => true)
17 t.field('resource', { type: resource as any })
18 t.field('query', { type: 'Query', resolve: () => ({}) })
19 },
20 })
21}
22
23const ResourceTypePlugin = plugin({
24 name: 'onMissingTypeExample',
25 onMissingType(typeName, builder) {
26 if (/(.*?)ResourceResponse/.test(typeName)) {
27 return resourceResponse(typeName.slice(0, -16))
28 }
29 return null
30 },
31})

Builder Object

🚧 Work in progress.

Edit this page on Github