
tRPC vs GraphQL: Type-Safe API Design in TypeScript Monorepos
Sharing types between frontend and backend applications is critical for reducing runtime bugs. Traditionally, developers achieved this by documenting API formats using OpenAPI (Swagger) or building schema-first architectures using GraphQL.
For teams developing applications inside TypeScript Monorepos, a new framework has emerged: tRPC (TypeScript Remote Procedure Call).
While GraphQL represents a universal, language-agnostic data query language, tRPC represents absolute TypeScript integration with zero schema compilation.
In this guide, we will compare tRPC and GraphQL, analyze their type sharing mechanisms, and guide you on selecting the right API layer.
1. What is GraphQL? (The Universal API Spec)
GraphQL is a runtime engine and query language developed by Facebook. It allows clients to query for specific data fields in a single HTTP request.
To share types between a GraphQL server and a TypeScript frontend, you typically follow a Schema-First workflow:
- You write a schema declaring your data structures using the GraphQL Schema Definition Language (SDL).
- You write resolvers to fetch database records.
- You run a client utility like GraphQL Code Generator to scan your schemas, query files, and output static TypeScript type definitions.
The Downside of GraphQL
While powerful, the tooling overhead is high. Every minor database column addition requires editing the schema, updating the resolvers, rebuilding the code-gen files, and re-running compiling stages.
2. What is tRPC? (Zero-Schema TypeScript Sharing)
tRPC allows you to build type-safe APIs without writing schemas or executing code generators. It works by sharing TypeScript Type Declarations from the server directly to the client at compile time.
Instead of writing a query schema, you declare a backend router using standard TypeScript objects and validation parsers (like Zod). You export the type only (without exposing server implementation code) to your frontend.
A Basic tRPC Configuration
On the Server:
// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
export const appRouter = t.router({
getUser: t.procedure
.input(z.string())
.query(({ input }) => {
// Direct database query with typed inputs
return { id: input, name: 'Alex' };
}),
});
// Export the TYPE signature of the router, not the code itself
export type AppRouter = typeof appRouter;On the Client:
// client/index.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({ url: 'http://localhost:3000/trpc' }),
],
});
// Full type safety and autocomplete suggestions in your IDE
const user = await client.getUser.query('42');
console.log(user.name); // Correctly typed as a stringIf you modify the output type on the server, the client code immediately raises compile errors in your IDE without running build tasks.
Key Differences: Tooling and Ecosystems
1. Language Portability
- GraphQL is completely language-agnostic. The server can be built in Python, while clients are written in Swift (iOS), Kotlin (Android), and React.
- tRPC is strictly bound to TypeScript. If you need to integrate a native Android application or allow external third-party companies to query your endpoints, tRPC is not viable.
2. Query Flexibility
- GraphQL allows the client client to specify exact fields, optimizing payloads dynamically (e.g., fetching only titles instead of full posts).
- tRPC relies on fixed procedure returns. It operates similarly to standard REST endpoints, where the server determines the response payload layout.
Feature Summary Comparison
| Metric | GraphQL | tRPC |
| Type Source | Explicit Schema file (SDL) | Direct TypeScript types |
| Code Generation? | Yes (required for TypeScript) | No (uses compiler inferences) |
| Language Support | Universal (any language) | TypeScript only |
| Query Engine | Client-driven selections | Server-defined endpoints |
| Performance | Network optimized | Code-compiling optimized |
Which Should You Choose?
Choose tRPC if:
- Your application is written entirely in TypeScript (frontend and backend).
- You utilize a Monorepo setup (using pnpm workspaces, Turborepo, or Nx).
- You want a fast development cycle with autocomplete and instant compiler error feedback.
Choose GraphQL if:
- You are building public API platforms for external integrations.
- Your codebase spans multiple languages (e.g., Go/Python microservices communicating with web and mobile platforms).
- You require client-side flexibility to request nested relational data trees dynamically.
Conclusion
tRPC and GraphQL solve type-safe API delivery in different contexts. tRPC is the top choice for TypeScript monorepos, providing type-safety without build-time compilation overheads. GraphQL is the standard choice for language-independent, client-driven architectures that require data selection flexibility across mobile, web, and external platforms.