GraphQL

Revisiting an Old GraphQL Friend

Ethan Glover

Recently Apollo released official support for NextJS 13 App Router. Naturally I decided to give it a try with a new project. I was struggling to figure out how to properly handle new NextJS features and on-demand ISR. With RSC, now I can make calls to the database directly inside of component! Just one problem, you can't add revalidation tags to anything other than fetch. So if I make a mutation with a server action, I can't refetch data from another request. Basically, there is no easy “revalidate query” option like React Query and Apollo Client have. So if React Query doesn't yet support RSC, and Apollo Client does, it's time to bring in GraphQL.

I have said before that I do not generate GraphQL API's, I believe this is the wrong way to go. I tried initially to use Postgraphile and the results were from from what I wanted. This is no different than my experiences with every other GraphQL codegen tool. Which means, it's back to writing it by hand, the right way.

So I revisited an old blog post talking about how I wrote integrations between Prisma and GraphQL for performance. It took awhile to get up an running again. Fundamentally, it is impossible to automatically generate GraphQL schema from Prisma generated types as Prisma types are far more complex. Which means writing a lot of types by hand that look something like this.

1type Query {
1input PersonWhereInput {

But the results are phenomenal. GraphQL becomes a fully customizable ORM that allows you to properly establish relationships between different data sources. GraphQL as it was intended. However, there's one major downside. And that's getting Prisma and GraphQL to play well together.

Prisma by itself is very performant. Recently 9x more performant. It takes a lot work out of manually writing SQL queries where other ORM's essentially just act as string builders. But with GraphQL, when you're making multiple nested calls Prisma loses the context for everything it's doing and can't build the most efficient queries. On top of the fact that Prisma does not batch many requests.

This is explained better in my original post on GraphQL. But the point here is that as I was working with it this time around, I found that the optimizations, especially around “one-to-many” queries, weren't being used. In many cases, the resolution was failing to find database relationships and falling back on N+1 queries.

So after a day of debugging here are my three functions to optimize Prisma with GraphQL.

The simple select to get rid of the dreaded `select *` and only select what was given to the GraphQL API.

1import { PrismaSelect } from '@paljs/plugins'

Resolve arguments. This takes nested calls and turns them into new Prisma requests with relationships taken into account. The primary feature here is using relationship fields from a parent GraphQL query to make the next request, and yelling at you when you don't include it in that parent request select.

1import type { Prisma } from '@prisma/client';

And the famous resolveFindMany which uses the Prisma fluent API and flips one-to-many requests around to allow for query batching.

1import type { GraphQLResolveInfo } from 'graphql';

Ultimately, I will be removing GraphQL from the project as well as avoiding NextJS server actions in favor of route handlers. The issue with Apollo Client here is that the server side client and client side client don't share a cache in the way that app router does with fetch. And Apollo's “refetchQueries” does nothing to invalidate the NextJS cache.

The only way to achieve this is to put all queries and mutations in route handlers and make fetch requests. This means revalidation tags can be added to GET requests. And the only place you can use revalidateTag() is in POST requests. Until third parties have an answer to this, I will have to stick with creating endpoints.