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'
2
3const schema = makeSchema({
4 // ... types, etc,
5 plugins: [
6 // ... other plugins
7 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'
2
3export 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.length
6 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 id
5 }
6 pageInfo {
7 endCursor
8 hasNextPage
9 }
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 logic
4 },
5})
6
7// or
8
9t.connectionField('users', {
10 // ...
11 validateArgs: (args, info) => {
12 // custom validate logic
13 },
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})
6
7t.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'
2
3const 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})
Edit this page on Github