id | title | sidebar_label |
---|---|---|
plugin-connection |
Connection Plugin |
Connections |
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:
import { makeSchema, connectionPlugin } from "@nexus/schema";
const schema = makeSchema({
// ... types, etc,
plugins: [
// ... other plugins
connectionPlugin(),
],
});
By default, the plugin will install a t.connectionField
method available on the object definition builder:
export const User = objectType({
name: "User",
definition(t) {
t.connectionField(...);
},
});
You can change the name of this field by specifying the nexusFieldName
in the plugin config.
There are two main ways to use the connection field, with a nodes
property, or a resolve
property:
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.
import { connectionFromArray } from "graphql-relay";
export const usersQueryField = queryField((t) => {
t.connectionField("users", {
type: User,
async resolve(root, args, ctx, info) {
return connectionFromArray(await ctx.resolveUserNodes(), args);
},
});
});
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.
t.connectionField("users", {
type: User,
nodes(root, args, ctx, info) {
// [{ id: 1, ... }, ..., { id: 10, ... }]
return ctx.users.resolveForConnection(root, args, ctx, info);
},
});
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.
t.connectionField("usersConnectionNodes", {
type: User,
cursorFromNode(node, args, ctx, info, { index, nodes }) {
if (args.last && !args.before) {
const totalCount = USERS_DATA.length;
return `cursor:${totalCount - args.last! + index + 1}`;
}
return connectionPlugin.defaultCursorFromNode(node, args, ctx, info, {
index,
nodes,
});
},
nodes() {
// ...
},
});
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
connectionPlugin({
includeNodesField: true,
});
query IncludeNodesFieldExample {
users(first: 10) {
nodes {
id
}
pageInfo {
endCursor
hasNextPage
}
}
}
The queryField
or mutationField
helpers may accept a function rather than a field name, which will be shorthand for the query builder:
export const usersField = queryField((t) => {
t.connectionField("users", {
type: Users,
nodes(root, args, ctx, info) {
return ctx.users.forConnection(root, args);
},
});
});
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.
You may specify additionalArgs
on either the plugin or the field config, to add additional arguments to the connection:
t.connectionField("userConnectionAdditionalArgs", {
type: User,
disableBackwardPagination: true,
additionalArgs: {
isEven: booleanArg({
description: "If true, filters the users with an odd pk",
}),
},
resolve() {
// ...
},
});
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.
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.
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.
connectionPlugin({
validateArgs(args, info) {
// ... custom validate logic
},
});
// or
t.connectionField("users", {
// ...
validateArgs: (args, info) => {
// custom validate logic
},
});
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.
connectionPlugin({
extendConnection: {
totalCount: { type: "Int" },
},
});
t.connectionField("users", {
type: User,
nodes: () => {
// ...
},
totalCount() {
return ctx.users.totalCount(args);
},
});
t.connectionField("users", {
extendConnection(t) {
t.int("totalCount", {
resolve: (source, args, ctx) => ctx.users.totalCount(args),
});
},
});
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.
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:
import { makeSchema, connectionPlugin } from "@nexus/schema";
const schema = makeSchema({
// ... types, etc,
plugins: [
connectionPlugin({
typePrefix: "Analytics",
nexusFieldName: "analyticsConnection",
extendConnection: {
totalCount: { type: "Int" },
avgDuration: { type: "Int" },
},
}),
connectionPlugin({}),
],
});