Reputation: 1174
I'm using Prisma to implement a GraphQL interface to expose some data stored in a PostgreSQL database. My code is inspired by the GraphQL Tools (SDL-first) example. This logic is pretty inefficient though and I'd like to improve it.
Here is a minimal piece of code to show the problem and ask for a solution. My real code is of course more complicated.
type Query {
allUsers: [User!]!
}
type User {
name: String!
posts: [Post!]!
}
type Post {
text: String!
author: User!
}
const resolvers = {
Query: {
allUsers: ()=>prisma.users.findMany()
},
User: {
posts: (user)=>prisma.posts.findMany({where:{author:user.id}})
}
};
This code works but it's inefficient. Imagine you're running the query {allUsers{posts{text}}}
:
My code runs N+1 queries against PostgreSQL to fetch the whole result: one to fetch the list of the users, then other N: one for each user. A single query, using a JOIN, should be enough.
My code selects every column from every table it queries, even though I only need user.id
and don't need user.name
or anything else.
I know that Prisma supports nested searches (include
and select
options) which could fix both problems. However I don't know how to configure the options object using the GraphQL query.
How can I extract from the GraphQL query the list of fields that are requested? And how can I use these to create to options object to perform an optimal nested-search with Prisma?
Upvotes: 1
Views: 1468
Reputation: 1383
This package can help you parse the request info: https://www.npmjs.com/package/graphql-parse-resolve-info
Then you need to transform it to a usable parameter that you can use in your ORM.
Here is an example with NestJS:
import {createParamDecorator, ExecutionContext} from '@nestjs/common';
import {GqlExecutionContext} from '@nestjs/graphql';
import {GraphQLResolveInfo} from 'graphql';
import {parseResolveInfo, ResolveTree} from 'graphql-parse-resolve-info';
export type PrismaSelect = {
select: {
[key: string]: true | PrismaSelect;
};
};
export const Relations = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const info = GqlExecutionContext.create(ctx).getInfo<GraphQLResolveInfo>();
const ast = parseResolveInfo(info);
return astToPrisma(Object.values((ast as ResolveTree).fieldsByTypeName)[0]);
},
);
export const astToPrisma = (ast: {
[str: string]: ResolveTree;
}): PrismaSelect => {
return {
select: Object.fromEntries(
Object.values(ast).map(field => [
field.name,
Object.keys(field.fieldsByTypeName).length === 0
? true
: astToPrisma(Object.values(field.fieldsByTypeName)[0]),
]),
),
};
};
Then you do:
import {Parent, Query, ResolveField, Resolver} from '@nestjs/graphql';
import {PrismaService} from '../services/prisma.service';
import {User} from '../entities/user.entity';
import {Relations} from 'src/decorators/relations.decorator';
import {Prisma} from '@prisma/client';
@Resolver(() => User)
export class UserResolver {
constructor(public prisma: PrismaService) {}
@Query(() => [User])
async usersWithRelationsResolver(
@Relations() relations: {select: Prisma.UserSelect},
): Promise<Partial<User>[]> {
return this.prisma.user.findMany({
...relations,
});
}
Alternatively, if you want to solve the N+1 problem you can use Prisma built-in findUnique
method. See https://www.prisma.io/docs/guides/performance-and-optimization/query-optimization-performance#solving-the-n1-problem
Upvotes: 0