Blue Nebula
Blue Nebula

Reputation: 1174

How can I use the fields in a GraphQL query to perform nested reads with Prisma?

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.

My GraphQL schema
type Query {
  allUsers: [User!]!
}
type User {
  name: String!
  posts: [Post!]!
}
type Post {
  text: String!
  author: User!
}
My resolver object, in the Node.JS code
const resolvers = {
  Query: {
    allUsers: ()=>prisma.users.findMany()
  },
  User: {
    posts: (user)=>prisma.posts.findMany({where:{author:user.id}})
  }
};
Problems

This code works but it's inefficient. Imagine you're running the query {allUsers{posts{text}}}:

  1. 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.

  2. 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.

Question

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

Answers (1)

Almaju
Almaju

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

Related Questions