Selections
Control which columns and relations Prisma loads with include, select, and field-level selections.
By default the plugin loads a model's full row and pre-loads only the relations a query touches. include and select on a prismaObject let you tune that: pre-load a relation every time, or narrow a wide table down to the columns you actually expose.
Always include a relation
Add include to a prismaObject to pre-load a relation whenever the type is loaded. This lets a field read from a related table without declaring the relation in GraphQL. Deeply nested relations can be included the same way:
builder.prismaObject('Player', {
// stats are always loaded with a Player.
include: {
stats: true,
},
fields: (t) => ({
id: t.exposeID('id'),
name: t.exposeString('name'),
totalGoals: t.int({
// player is now typed with `stats`, so the field can read from it.
resolve: (player) => player.stats.reduce((sum, s) => sum + s.goals, 0),
}),
}),
});Select instead of include
By default the plugin uses include, which loads every column of a table. That's usually fine, but for tables with many columns or a few heavy payloads you may want to load only what you expose. Add a select to the prismaObject to switch that type into select mode:
builder.prismaObject('Player', {
select: {
id: true,
},
fields: (t) => ({
id: t.exposeID('id'),
name: t.exposeString('name'),
}),
});In select mode, t.expose* and t.relation automatically add their columns and relations to the selection when the field is queried, so only the requested columns leave the database. Other fields can add their own selections with a select option:
builder.prismaObject('Player', {
select: {
id: true,
},
fields: (t) => ({
id: t.exposeID('id'),
name: t.exposeString('name'),
totalGoals: t.int({
// stats are selected only when totalGoals is queried.
select: {
stats: {
select: { goals: true },
},
},
resolve: (player) => player.stats.reduce((sum, s) => sum + s.goals, 0),
}),
}),
});Selections from arguments or context
select can be a function of the field's arguments and context, so a selection can respond to input. This field takes a date and selects only the stats recorded since then:
builder.prismaObject('Player', {
fields: (t) => ({
name: t.exposeString('name'),
recentGoals: t.int({
args: {
since: t.arg({ type: 'Date', required: true }),
},
select: (args) => ({
stats: {
where: {
game: { playedAt: { gt: args.since } },
},
},
}),
resolve: (player) => player.stats.reduce((sum, s) => sum + s.goals, 0),
}),
}),
});Optimized queries without t.prismaField
Sometimes you need the plugin's computed query for a field that can't be a t.prismaField, because it combines with another plugin, or the field doesn't return a Prisma object directly. queryFromInfo builds that query from the resolver's info. A common case is a mutation that wraps a Prisma object in a result type:
import type { Player } from '@prisma/client';
const PlayerType = builder.prismaObject('Player', {
fields: (t) => ({
id: t.exposeID('id'),
name: t.exposeString('name'),
}),
});
const SignPlayerResult = builder
.objectRef<{ success: boolean; player?: Player }>('SignPlayerResult')
.implement({
fields: (t) => ({
success: t.exposeBoolean('success'),
player: t.field({
type: PlayerType,
nullable: true,
resolve: (result) => result.player,
}),
}),
});
builder.mutationField('signPlayer', (t) =>
t.field({
type: SignPlayerResult,
args: {
name: t.arg.string({ required: true }),
teamId: t.arg.id({ required: true }),
},
resolve: async (_root, args, context, info) => {
if (!args.name) {
return { success: false };
}
const player = await prisma.player.create({
...queryFromInfo({
context,
info,
// Nested path where the selections for the Player type live.
path: ['player'],
// Optional initial selection, in case the field at `path` selects nothing.
select: { stats: true },
}),
data: {
name: args.name,
number: 0,
teamId: Number(args.teamId),
},
});
return { success: true, player };
},
}),
);path points queryFromInfo at where in the selection the Prisma object appears (here the player field of the result). select (or include) seeds an initial selection, useful when the field at path may not be selected at all, leaving the selection set empty.