API
plugin
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'23export 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 resolveFn8 if (typeof node === 'string') {9 const fieldResolve: FieldResolver<any, any> = (root, args, ctx, info) => {10 return `${info.parentType.name}:${root[node]}`11 }12 resolveFn = fieldResolve13 } else {14 resolveFn = node15 }16 t.implements('Node')17 t.nonNull.id('id', {18 resolve: resolveFn,19 })20 }21 },22})
Usage:
1const User = objectType({2 name: 'User',3 node: 'id', // adds `id` field4 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 return6 }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 value13 }14 },15})
onAddOutputField
Called when the .addField
is called internally in the builder, before constructing the field.
See the "declarativeWrapping" plugin for an example use.
onAddInputField
Called when the .addField
is called internally in the builder, before constructing the field.
See the "declarativeWrapping" plugin for an example use.
onAddArg
Called just before a Nexus arg is constructed into an GraphQLArgumentConfig. See the "declarativeWrapping" plugin for an example use.
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 type3 *4 * type OrganizationResourceResponse {5 * ok: Boolean!6 * resource: Organization!7 * query: Query!8 * }9 *10 * @param resource11 */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}2223const ResourceTypePlugin = plugin({24 name: 'onMissingTypeExample',25 onMissingType(typeName, builder) {26 if (/(.*?)ResourceResponse/.test(typeName)) {27 return resourceResponse(typeName.slice(0, -16))28 }29 return null30 },31})
Builder Object
🚧 Work in progress.