Selections
Includes on types
In some cases, you may want to always pre-load certain relations. This can be helpful for defining fields directly on type where the underlying data may come from a related table.
builder.prismaObject('User', {
// This will always include the profile when a user object is loaded. Deeply nested relations can
// also be included this way.
include: {
profile: true,
},
fields: (t) => ({
id: t.exposeID('id'),
email: t.exposeString('email'),
bio: t.string({
// The profile relation will always be loaded, and user will now be typed to include the
// profile field so you can return the bio from the nested profile relation.
resolve: (user) => user.profile.bio,
}),
}),
});
Select mode for types
By default, the prisma plugin will use include
when including relations, or generating fallback
queries. This means we are always loading all columns of a table when loading it in a
t.prismaField
or a t.relation
. This is usually what we want, but in some cases, you may want to
select specific columns instead. This can be useful if you have tables with either a very large
number of columns, or specific columns with large payloads you want to avoid loading.
To do this, you can add a select
instead of an include to your prismaObject
:
builder.prismaObject('User', {
select: {
id: true,
},
fields: (t) => ({
id: t.exposeID('id'),
email: t.exposeString('email'),
}),
});
The t.expose*
and t.relation
methods will all automatically add selections for the exposed
fields WHEN THEY ARE QUERIED, ensuring that only the requested columns will be loaded from the
database.
In addition to the t.expose
and t.relation
, you can also add custom selections to other fields:
builder.prismaObject('User', {
select: {
id: true,
},
fields: (t) => ({
id: t.exposeID('id'),
email: t.exposeString('email'),
bio: t.string({
// This will select user.profile.bio when the the `bio` field is queried
select: {
profile: {
select: {
bio: true,
},
},
},
resolve: (user) => user.profile.bio,
}),
}),
});
Using arguments or context in your selections
The following is a slightly contrived example, but shows how arguments can be used when creating a selection for a field:
const PostDraft = builder.prismaObject('Post', {
fields: (t) => ({
title: t.exposeString('title'),
commentFromDate: t.string({
args: {
date: t.arg({ type: 'Date', required: true }),
},
select: (args) => ({
comments: {
take: 1,
where: {
createdAt: {
gt: args.date,
},
},
},
}),
resolve: (post) => post.comments[0]?.content,
}),
}),
});
Optimized queries without t.prismaField
In some cases, it may be useful to get an optimized query for fields where you can't use
t.prismaField
.
This may be required for combining with other plugins, or because your query does not directly
return a PrismaObject
. In these cases, you can use the queryFromInfo
helper. An example of this
might be a mutation that wraps the prisma object in a result type.
const Post = builder.prismaObject('Post', {...});
builder.objectRef<{
success: boolean;
post?: Post
}>('CreatePostResult').implement({
fields: (t) => ({
success: t.boolean(),
post: t.field({
type: Post,
nullable:
resolve: (result) => result.post,
}),
}),
});
builder.mutationField(
'createPost',
{
args: (t) => ({
title: t.string({ required: true }),
...
}),
},
{
resolve: async (parent, args, context, info) => {
if (!validateCreatePostArgs(args)) {
return {
success: false,
}
}
const post = prisma.city.create({
...queryFromInfo({
context,
info,
// nested path where the selections for this type can be found
path: ['post']
// optionally you can pass a custom initial selection, generally you wouldn't need this
// but if the field at `path` is not selected, the initial selection set may be empty
select: {
comments: true,
},
}),
data: {
title: args.input.title,
...
},
});
return {
success: true,
post,
}
},
},
);