Plugins
Relay Connection
Connection Plugin
The connection plugin provides a new method on the object definition builder, enabling paginated associations between types, following the Relay Connection Specification. It provides simple ways to customize fields available on the Connection
, Edges
, or PageInfo
types.
To install, add the connectionPlugin
to the makeSchema.plugins
array, along with any other plugins
you'd like to include:
1import { makeSchema, connectionPlugin } from 'nexus'23const schema = makeSchema({4 // ... types, etc,5 plugins: [6 // ... other plugins7 connectionPlugin(),8 ],9})
By default, the plugin will install a t.connectionField
method available on the object definition builder:
1export const User = objectType({2 name: "User",3 definition(t) {4 t.connectionField(...);5 },6});
You can change the name of this field by specifying the nexusFieldName
in the plugin config.
Usage
There are two main ways to use the connection field, with a nodes
property, or a resolve
property:
With resolve
If you have custom logic you'd like to provide in resolving the connection, we allow you to instead specify a resolve
field, which will make not assumptions about how the edges
, cursor
, or pageInfo
are defined.
You can use this with helpers provided via graphql-relay-js.
1import { connectionFromArray } from 'graphql-relay'23export const usersQueryField = queryField((t) => {4 t.connectionField('users', {5 type: User,6 async resolve(root, args, ctx, info) {7 return connectionFromArray(await ctx.resolveUserNodes(), args)8 },9 })10})
With nodes
When providing a nodes
property, we make some assumptions about the structure of the connection. We only
require you return a list of rows to resolve based on the connection, and then we will automatically infer the hasNextPage
, hasPreviousPage
, and cursor
values for you.
1t.connectionField('users', {2 type: User,3 nodes(root, args, ctx, info) {4 // [{ id: 1, ... }, ..., { id: 10, ... }]5 return ctx.users.resolveForConnection(root, args, ctx, info)6 },7})
One limitation of the nodes
property, is that you cannot paginate backward without a cursor
, or without defining a cursorFromNode
property on either the field or plugin config. This is because we can't know how long the connection list may be to begin paginating backward.
1t.connectionField('usersConnectionNodes', {2 type: User,3 cursorFromNode(node, args, ctx, info, { index, nodes }) {4 if (args.last && !args.before) {5 const totalCount = USERS_DATA.length6 return `cursor:${totalCount - args.last! + index + 1}`7 }8 return connectionPlugin.defaultCursorFromNode(node, args, ctx, info, {9 index,10 nodes,11 })12 },13 nodes() {14 // ...15 },16})
Including a nodes
field:
If you want to include a nodes
field, which includes the nodes of the connection flattened into an array similar to how GitHub does in their GraphQL API, set includeNodesField
to true
1connectionPlugin({2 includeNodesField: true,3})
1query IncludeNodesFieldExample {2 users(first: 10) {3 nodes {4 id5 }6 pageInfo {7 endCursor8 hasNextPage9 }10 }11}
Top level connection field
The queryField
or mutationField
helpers may accept a function rather than a field name, which will be shorthand for the query builder:
1export const usersField = queryField((t) => {2 t.connectionField('users', {3 type: Users,4 nodes(root, args, ctx, info) {5 return ctx.users.forConnection(root, args)6 },7 })8})
There are properties on the plugin to help configure this including, cursorFromNode
, which allows you to customize how the cursor is created, or pageInfoFromNodes
to customize how hasNextPage
or hasPreviousPage
are set.
Pagination Arguments
Modifying arguments
You may specify additionalArgs
on either the plugin or the field config, to add additional arguments to the connection:
1t.connectionField('userConnectionAdditionalArgs', {2 type: User,3 disableBackwardPagination: true,4 additionalArgs: {5 isEven: booleanArg({6 description: 'If true, filters the users with an odd pk',7 }),8 },9 resolve() {10 // ...11 },12})
If you have specified args on the field, they will overwrite any custom args defined on the plugin config, unless inheritAdditionalArgs
is set to true.
Disabling forward/backward pagination
By default we assume that the cursor can paginate in both directions. This is not always something every
API needs or supports, so to turn them off, you can set disableForwardPagination
, or disableBackwardPagination
to
true on either the paginationConfig
, or on the fieldConfig
.
When we disable the forward or backward pagination args, by default we set the remaining first
or last
to required.
If you do not want this to happen, specify strictArgs: false
in the plugin or field config.
Argument validation
By default, the connection field validates that a first
or a last
must be provided, and not both. If you wish to provide your own validation, supply a validateArgs
property to either the connectionPlugin
config, or to the field configuration directly.
1connectionPlugin({2 validateArgs(args, info) {3 // ... custom validate logic4 },5})67// or89t.connectionField('users', {10 // ...11 validateArgs: (args, info) => {12 // custom validate logic13 },14})
Extending Connection / Edge types
There are two ways to extend the connection type, one is by providing extendConnection
on the connectionPlugin
configuration, the other is to add an extendConnection
or extendEdge
definition block on the field config.
Globally
1connectionPlugin({2 extendConnection: {3 totalCount: { type: 'Int' },4 },5})67t.connectionField('users', {8 type: User,9 nodes: () => {10 // ...11 },12 totalCount() {13 return ctx.users.totalCount(args)14 },15})
One-off / per-field
1t.connectionField('users', {2 extendConnection(t) {3 t.int('totalCount', {4 resolve: (source, args, ctx) => ctx.users.totalCount(args),5 })6 },7})
The field-level customization approach will result in a custom connection type specific to that type/field, e.g. QueryUsers_Connection
, since the modification is specific to the individual field.
Multiple Connection Types
You can create multiple field connection types with varying defaults, available under different connections builder methods. A typePrefix
property should be supplied to configure the name
Custom Usage:
1import { makeSchema, connectionPlugin } from 'nexus'23const schema = makeSchema({4 // ... types, etc,5 plugins: [6 connectionPlugin({7 typePrefix: 'Analytics',8 nexusFieldName: 'analyticsConnection',9 extendConnection: {10 totalCount: { type: 'Int' },11 avgDuration: { type: 'Int' },12 },13 }),14 connectionPlugin({}),15 ],16})
Custom names for Connection / Edge types
You can provide a function to generate a custom name for connection and edge types. The function will receive the field and parent type names.
Globally
1connectionPlugin({2 getConnectionName(fieldName, parentTypeName) {3 return `${parentTypeName}${upperFirst(fieldName)}Connection`4 },5 getEdgeName(fieldName, parentTypeName) {6 return `${parentTypeName}${upperFirst(fieldName)}Edge`7 },8})
One-off / per-field
1t.connectionField('users', {2 getConnectionName(fieldName, parentTypeName) {3 return `${parentTypeName}${upperFirst(fieldName)}Connection`4 },5 getEdgeName(fieldName, parentTypeName) {6 return `${parentTypeName}${upperFirst(fieldName)}Edge`7 },8})