Indirect relations
Selecting fields from a nested GraphQL field
By default, the nestedSelection function will return selections based on the type of the current
field. nestedSelection can also be used to get a selection from a field nested deeper inside other
fields. This is useful if the field returns a type that is not a prismaObject, but a field nested
inside the returned type is.
const PostRef = builder.prismaObject('Post', {
fields: (t) => ({
title: t.exposeString('title'),
content: t.exposeString('content'),
author: t.relation('author'),
}),
});
const PostPreview = builder.objectRef<Post>('PostPreview').implement({
fields: (t) => ({
post: t.field({
type: PostRef,
resolve: (post) => post,
}),
preview: t.string({
nullable: true,
resolve: (post) => post.content?.slice(10),
}),
}),
});
builder.prismaObject('User', {
fields: (t) => ({
id: t.exposeID('id'),
postPreviews: t.field({
select: (args, ctx, nestedSelection) => ({
posts: nestedSelection(
{
// limit the number of postPreviews to load
take: 2,
},
// Look at the selections in postPreviews.post to determine what relations/fields to select
['post'],
// (optional) If the field returns a union or interface, you can pass a typeName to get selections for a specific object type
'Post',
),
}),
type: [PostPreview],
resolve: (user) => user.posts,
}),
}),
});nestedSelection returns the relation query for the type it selected, which for a Post is
{ select?, include?, where?, orderBy?, take?, skip?, cursor? }. Any keys you pass in are kept as
they were given, so a select passed to nestedSelection will still narrow the parent's shape.
With no argument, or with true, it returns the planned selection on its own.
Pinning a type in the path
The path is followed through fragments, so a segment is found whether the field is selected
directly, or under a fragment on an implementation of the field's type. When several
implementations share the same field name, a segment can be written as { name, type } to name the
implementation the field must be found under. Only selections of that field under a fragment on
that type, or one of its subtypes, will be planned:
builder.prismaObject('User', {
fields: (t) => ({
entries: t.field({
type: [Entry],
select: (args, ctx, nestedSelection) => ({
// Plan what `post` selects under `... on PostEntry`, not under other implementations
posts: nestedSelection({ take: 2 }, [{ name: 'post', type: 'PostEntry' }]),
}),
resolve: (user) => user.posts.map((post) => ({ kind: 'post', post })),
}),
}),
});The same segments can be used in queryFromInfo's path and paths options. The type is exported
as PathSegment.
Selecting as a specific type
When the field returns an interface or union, the third argument names the object type the selection should be read as. Its type-level selection and the fields selected under a fragment on it are planned, and fragments on other types are left out. With an empty path, this applies to the field's own return type:
// Activity is a union of Post and Comment
builder.prismaObject('User', {
fields: (t) => ({
recentActivity: t.field({
type: [Activity],
select: (args, ctx, nestedSelection) => ({
// What the query selects under `... on Post`, as a query for the posts relation
posts: nestedSelection({ take: 5 }, [], 'Post'),
// and under `... on Comment`, for the comments relation
comments: nestedSelection({ take: 5 }, [], 'Comment'),
}),
resolve: (user) => [...user.posts, ...user.comments],
}),
}),
});Indirect relations (eg. Join tables)
If you want to define a GraphQL field that directly exposes data from a nested relationship (many to
many relations using a custom join table is a common example of this) you can use the
nestedSelection function passed to select.
Given a prisma schema like the following:
model Post {
id Int @id @default(autoincrement())
title String
content String
media PostMedia[]
}
model Media {
id Int @id @default(autoincrement())
url String
posts PostMedia[]
uploadedBy User @relation(fields: [uploadedById], references: [id])
uploadedById Int
}
model PostMedia {
id Int @id @default(autoincrement())
post Post @relation(fields: [postId], references: [id])
media Media @relation(fields: [mediaId], references: [id])
postId Int
mediaId Int
}You can define a media field that can pre-load the correct relations based on the graphql query:
const PostDraft = builder.prismaObject('Post', {
fields: (t) => ({
title: t.exposeString('title'),
media: t.field({
select: (args, ctx, nestedSelection) => ({
media: {
select: {
// This will look at what fields are queried on Media
// and automatically select uploadedBy if that relation is requested
media: nestedSelection(
// This argument is the default query for the media relation
// It could be something like: `{ select: { id: true } }` instead
true,
),
},
},
}),
type: [Media],
resolve: (post) => post.media.map(({ media }) => media),
}),
}),
});
const Media = builder.prismaObject('Media', {
select: {
id: true,
},
fields: (t) => ({
url: t.exposeString('url'),
uploadedBy: t.relation('uploadedBy'),
}),
});