# Improve Your GraphQL Performance with Prisma Date: 2020-05-15 ยท 5 min read Tags: graphql, prisma, performance, typescript URL: https://ahmedelywa.com/blog/improve-graphql-performance-with-prisma --- If you've built a GraphQL API backed by Prisma, you've probably run into two common performance pitfalls: **the N+1 problem** and **over-fetching**. In this post, I'll walk through both issues and show you how I solved them with [PrismaSelect](https://paljs.com/plugins/select), a tool I built as part of the PalJS ecosystem. ## The Problem ### N+1 Queries Consider a simple schema with `User` and `Post` models. When a client queries for users and their posts, a naive resolver hits the database once for all users, then once _per user_ to fetch their posts. For 100 users, that's 101 queries. ```graphql query { users { id name posts { title published } } } ``` A typical resolver might look like this: ```typescript const resolvers = { Query: { users: () => prisma.user.findMany(), }, User: { posts: (parent) => prisma.post.findMany({ where: { authorId: parent.id } }), }, }; ``` Every time a `User` is resolved, an additional query fires for `posts`. This scales terribly. ### Over-Fetching Even when you use Prisma's `include` to eagerly load relations, you often fetch far more columns than the client actually requested. If the client only asks for `name` and `title`, Prisma still returns every column on both tables by default. ```typescript // Fetches ALL columns on user and post, even if the client only wants two fields const users = await prisma.user.findMany({ include: { posts: true }, }); ``` This wastes bandwidth, memory, and database I/O. ## The Solution: PrismaSelect PrismaSelect bridges GraphQL and Prisma by analyzing the incoming GraphQL query's `info` object (the fourth argument to every resolver) and generating a precise `select` object that tells Prisma exactly which fields and relations to fetch. ### Installation ```bash npm install @paljs/plugins ``` ### Basic Usage Wrap your Prisma call with `PrismaSelect` and pass the GraphQL `info` object: ```typescript import { PrismaSelect } from '@paljs/plugins'; const resolvers = { Query: { users: (_parent, _args, _ctx, info) => { const select = new PrismaSelect(info).value; return prisma.user.findMany({ ...select }); }, }, }; ``` That's it. PrismaSelect inspects the query AST and produces an object like: ```json { "select": { "id": true, "name": true, "posts": { "select": { "title": true, "published": true } } } } ``` Prisma then generates a single SQL query with a JOIN (or a minimal set of queries) that fetches only the requested data. No N+1, no over-fetching. ## How It Works Under the hood, PrismaSelect walks the GraphQL `info.fieldNodes` tree recursively. For each field, it checks whether it maps to a scalar column or a relation in your Prisma schema. Relations become nested `select` or `include` objects; scalars become `true` entries in the select map. The key insight is that GraphQL already knows exactly what the client wants. We just need to translate that knowledge into Prisma's query language. ### Handling Computed Fields If your GraphQL type has fields that don't exist in the database (computed fields), PrismaSelect won't include them in the select object by default, which is the correct behavior. But you can also make sure that the underlying database columns needed for computation are always included: ```typescript const select = new PrismaSelect(info, { defaultFields: { User: { id: true, firstName: true, lastName: true, // needed for computed `fullName` field }, }, }).value; ``` ### Working with Any GraphQL Server PrismaSelect works with any GraphQL server that gives you the standard `GraphQLResolveInfo` object. That includes Apollo Server, Mercurius, GraphQL Yoga, and Nexus-based setups. ```typescript // Apollo Server example const server = new ApolloServer({ typeDefs, resolvers: { Query: { users: (_parent, args, ctx, info) => { const select = new PrismaSelect(info).value; return ctx.prisma.user.findMany({ where: args.where, orderBy: args.orderBy, ...select, }); }, }, }, }); ``` ## Before and After Here is a concrete comparison. Without PrismaSelect, fetching a list of users with their posts and comments generates dozens of queries: ```typescript // Before: N+1 queries, all columns fetched // Query 1: SELECT * FROM "User" // Query 2..N+1: SELECT * FROM "Post" WHERE "authorId" = ? // Query N+2..M: SELECT * FROM "Comment" WHERE "postId" = ? const users = await prisma.user.findMany(); // Then resolve posts and comments in separate resolvers... ``` With PrismaSelect, it collapses to one or two optimized queries: ```typescript // After: Single query with precise column selection // SELECT "User"."id", "User"."name", // "Post"."title", "Post"."published", // "Comment"."text" // FROM "User" // LEFT JOIN "Post" ON ... // LEFT JOIN "Comment" ON ... const select = new PrismaSelect(info).value; const users = await prisma.user.findMany({ ...select }); ``` The performance difference can be dramatic. In a project I worked on, switching to PrismaSelect reduced query count by over 90% and cut API response times in half for deeply nested queries. ## Conclusion The combination of GraphQL's precise query language and Prisma's flexible query builder is powerful, but only if you connect them properly. PrismaSelect does that work for you automatically. If you're building a GraphQL API with Prisma, give [PalJS](https://paljs.com) a try. The `@paljs/plugins` package includes PrismaSelect along with other utilities for building full-stack Prisma applications.