Relay
The plugin integrates with @pothos/plugin-relay to make defining nodes
and connections backed by contract models easy and efficient.
Setup
Add RelayPlugin to your builder, then use builder.prismaNode to
register a model as a Relay node:
import RelayPlugin from '@pothos/plugin-relay';
import prismaNextPlugin from '@pothos/plugin-prisma-next';
const builder = new SchemaBuilder<{
PrismaNextContract: Contract;
Context: { db: typeof client };
}>({
plugins: [RelayPlugin, prismaNextPlugin],
relay: {},
prismaNext: { contract: contractJson as Contract },
});builder.prismaNode
builder.prismaNode('Post', {
id: { field: 'id' },
collection: (ctx) => ctx.db.orm.Post,
fields: (t) => ({
title: t.exposeString('title'),
author: t.relation('author'),
}),
});This registers a GraphQL type that implements the Relay Node
interface, along with the standard global-ID encoding/decoding.
Options
id.field: column name (string) or non-empty tuple for composite primary keys. Composite IDs JSON-encode the tuple before being passed to Relay'stoGlobalID.id.parse/id.resolve: optional overrides for ID parsing (string → IDShape) and serialization (parent → string|number).collection: the base orm-clientCollectionto load from. Accepts either a staticCollectionor a(ctx) => Collectioncallback. The callback form is what you'll usually want — it lets you scope the query to the per-request context.fields,isTypeOf,interfaces, …: same options asprismaObject. A user-providedisTypeOfis combined with the plugin's brand check; Relay's node-interface resolver calls into it.
Loading
Relay's node(id:) field calls the user-provided loadWithoutCache
under the hood — the plugin installs one that:
- Batches concurrent same-schema-path lookups via a microtask
queue. All
node(id:)calls at the same query path coalesce into onecollection.where(idIn).all()— equivalent to a DataLoader, scoped per path. - Auto-includes nested relations from the GraphQL selection set
(just like
t.prismaField). - Brands loaded rows with the node type so abstract-position resolveType works.
You don't need to write a load callback — the plugin handles it from
id.field + collection.
Connections
Use t.prismaConnection for top-level connections and
t.relatedConnection for connections nested on a prisma object. See
Connections for the full reference.
Custom isTypeOf for polymorphism
If you have multiple node types backed by the same model
(discriminated by a column), pass isTypeOf:
builder.prismaNode('Post', {
id: { field: 'id' },
collection: (ctx) => ctx.db.orm.Post,
isTypeOf: (row) => (row as { published: number }).published === 1,
fields: (t) => ({ ... }),
});
builder.prismaNode('Post', {
variant: 'DraftPost',
id: { field: 'id' },
collection: (ctx) => ctx.db.orm.Post,
isTypeOf: (row) => (row as { published: number }).published === 0,
fields: (t) => ({ ... }),
});Both nodes back the Post model but resolve as different GraphQL types
based on the row. The plugin's brand fallback only fires when the
user-provided isTypeOf returns false — so user-supplied predicates
win for polymorphic discrimination.