tRPC vs REST
Type-safe tRPC versus traditional REST API: end-to-end TypeScript, developer experience, public API exposure, and architecture fit comparison.
Zero codegen, end-to-end TypeScript type safety
Schema-first, universal query language for multiple clients
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.
| Category | tRPC | GraphQL |
|---|---|---|
| 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 |
// 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-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 } } }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 ConsultationIf 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.