tRPC vs GraphQL Comparison

Zero codegen, end-to-end TypeScript type safety

VS
GraphQL

Schema-first, universal query language for multiple clients

16 min readBackend

Quick Verdict

It depends — "type safety" alone isn't a reason to pick GraphQL. If you're on a single TypeScript monorepo with a single web client, tRPC almost always means less work — zero codegen, and a server change surfaces as a TS error on the client instantly. If you have a mobile app, a public API for external developers, or need multi-team federation, GraphQL's language-agnostic schema contract and Apollo Federation ecosystem give you a structural advantage.

tRPCGraphQL
Read the full verdict

Score Comparison

Loading chart...

Detailed Scoring

Detailed Scoring: tRPC and GraphQL — category-by-category scores out of 10
CategorytRPCGraphQL
Performance
7/10
6/10
Ease of Learning
8/10
5/10
Ecosystem
6/10
9/10
Community
7/10
8/10
Job Market
5/10
8/10
Future-Proof
7/10
8/10

Pros & Cons

tRPC

Pros

  • No code generation or build step — server types flow directly to the client
  • When you change something on the server, the client sees the TypeScript error before you even save the file
  • Official adapters exist for React, Next.js, Express, Fastify, AWS Lambda, Solid, and Svelte
  • Native integration with TanStack Query — queryKey/queryOptions factories come built in
  • Companies like Netflix and Pleo use it in production, according to the official FAQ
  • Even the TanStack Query dependency is optional — a plain vanilla client is enough
  • Subscriptions are supported over WebSocket or SSE; SSE setup is recommended as simpler
  • MIT licensed, fully open source — no paid enterprise tier

Cons

  • Type sharing depends on the same TypeScript monorepo — a native mobile client loses this advantage
  • No official public/REST API surface; you need a third-party package like `trpc-to-openapi`
  • No schema introspection — it doesn't give external developers a self-documenting contract
  • No field-level selection — every procedure has a fixed return type
  • Can't be used when the backend is in a language other than JavaScript/TypeScript
  • No official federation/multi-service schema composition standard

Best For

Products with a single TypeScript monorepo and a single web clientFull-stack TS projects based on Next.js, Express, or FastifySmall-to-medium teams that want to iterate quicklyInternal tools and admin panels with no external consumerProjects that already use TanStack Query

GraphQL

Pros

  • The client selects only the fields it needs — over/under-fetching is solved structurally
  • Language-agnostic schema contract — natively serves iOS, Android, web, and any third-party client
  • Self-documenting via introspection; explorable with tools like GraphiQL
  • Deprecation mechanism enables schema evolution without breaking changes
  • The official `graphql/dataloader` package solves the N+1 problem with batching and caching
  • Apollo Federation provides official composition support for multi-team/multi-service architectures
  • Governed by the GraphQL Foundation under the Linux Foundation since 2018
  • Official clients like Relay use a normalized cache to avoid unnecessary re-renders

Cons

  • No HTTP-cache-friendly URL structure — requires global IDs and a normalized client-cache
  • Schema design, resolver architecture, and dataloaders raise the learning curve
  • Federation is still being standardized — the Composite Schema Working Group is actively working on it
  • Enterprise-scale layers like Apollo GraphOS are paid (Developer plan: $5 per million requests)
  • Leaving the N+1 risk unaddressed without a dataloader is a commonly encountered class of bug
  • Requires more boilerplate (schema + resolver) than tRPC or REST for simple CRUD scenarios

Best For

APIs with mobile (iOS/Android) or third-party consumersArchitectures that need federation across multi-team/multi-service organizationsPublic APIs meant to be exposed to external developersApplications that query complex, deeply nested data graphsLarge-scale systems that need enterprise SLAs and schema governance

Code Comparison

tRPC
// tRPC — router definition (server)
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
// db = your Prisma/ORM client (imported from a separate file)

const t = initTRPC.create();

export const appRouter = t.router({
  getUser: t.procedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      return db.user.findUnique({ where: { id: input.id } });
    }),
});

export type AppRouter = typeof appRouter;

// Client — server type is inferred automatically, no codegen
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

const client = createTRPCClient<AppRouter>({
  links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
});

const user = await client.getUser.query({ id: '1' });
// user: { id: string; name: string; ... } — full type safety at compile time
GraphQL
// graphql-js + @apollo/server — schema, resolver, and server in one file
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
// db = your Prisma/ORM client (imported from a separate file)

const typeDefs = `#graphql
  type Post {
    id: ID!
    title: String!
  }

  type User {
    id: ID!
    name: String!
    posts: [Post!]!
  }

  type Query {
    user(id: ID!): User
  }
`;

const resolvers = {
  Query: {
    user: (_parent, { id }, { db }) => db.user.findUnique({ where: { id } }),
  },
  User: {
    posts: (user, _args, { db }) =>
      db.post.findMany({ where: { authorId: user.id } }),
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

const { url } = await startStandaloneServer(server, {
  context: async () => ({ db }),
  listen: { port: 4000 },
});
console.log(`Server ready: ${url}`);

// Client query — only the needed fields are selected:
// query { user(id: "1") { name posts { title } } }

Conclusion

It depends — "type safety" alone isn't a reason to pick GraphQL. If you're on a single TypeScript monorepo with a single web client, tRPC almost always means less work — zero codegen, and a server change surfaces as a TS error on the client instantly. If you have a mobile app, a public API for external developers, or need multi-team federation, GraphQL's language-agnostic schema contract and Apollo Federation ecosystem give you a structural advantage.

Get Free Consultation
FAQ

Frequently Asked Questions

If you're working in a single TypeScript monorepo with a single web client, tRPC requires less code and config (zero codegen, end-to-end TS inference — trpc.io). If you have a mobile app, a public API for external developers, or several independent services, GraphQL's schema contract and federation ecosystem (Apollo Federation) provide a better-suited abstraction.

Related Blog Posts

View All Posts
All Comparisons