tRPC vs REST vs GraphQL (2026)
Three ways to build APIs. REST: the universal standard. GraphQL: the flexible query language. tRPC: end-to-end type safety with zero schema. Each solves different problems.
Quick Comparison
| Feature | tRPC | REST | GraphQL |
|---|---|---|---|
| Type safety | Full (automatic) | Manual (OpenAPI + codegen) | Partial (codegen needed) |
| Schema definition | None (inferred from code) | OpenAPI/Swagger | SDL (Schema Definition Language) |
| Transport | HTTP (JSON) | HTTP | HTTP (usually POST) |
| Over/under-fetching | Handled by TypeScript | Common problem | Solved (query what you need) |
| Learning curve | Low (if you know TS) | Lowest | Highest |
| Caching | Manual | HTTP caching built-in | Complex |
| File uploads | Via separate endpoint | Native | Complex |
| Real-time | Via subscriptions | WebSockets/SSE | Subscriptions |
| Tooling | Growing | Massive | Strong |
| Language lock-in | TypeScript only | Any | Any |
| Best for | Full-stack TS apps | Public APIs, multi-client | Complex data, mobile apps |
tRPC: Type Safety Without the Schema
How It Works
Define a procedure on the server → call it on the client with full type safety. No schema files, no code generation, no API contracts to maintain.
Server:
const appRouter = router({
getUser: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return await db.user.findUnique({ where: { id: input.id } })
}),
createPost: publicProcedure
.input(z.object({ title: z.string(), body: z.string() }))
.mutation(async ({ input }) => {
return await db.post.create({ data: input })
})
})
Client:
const user = await trpc.getUser.query({ id: "123" })
// user is fully typed — TypeScript knows the exact shape
// Autocomplete works, typos are caught at compile time
Strengths
Zero schema maintenance. Change a return type on the server → the client immediately sees the change. No OpenAPI spec to update, no GraphQL schema to modify, no code generation to run.
Instant type errors. Return a number instead of a string? TypeScript catches it before you run the code. Rename a field? Every call site lights up with errors.
Zod validation built-in. Input validation with Zod schemas — validated at runtime and typed at compile time. One definition serves both purposes.
Simple mental model. Call server functions from the client. That's it. No HTTP methods to choose, no URL paths to design, no status codes to select.
React Query integration. tRPC wraps TanStack Query (React Query). Get caching, optimistic updates, infinite queries, and refetching for free.
Weaknesses
- TypeScript only. Both client and server must be TypeScript. If your mobile app is Swift or your backend is Python, tRPC doesn't work.
- Monorepo preferred. tRPC works best when client and server share a TypeScript project. Separate repos require publishing the router type.
- Not for public APIs. tRPC is an internal communication protocol. If external developers consume your API, they need REST or GraphQL.
- HTTP caching is harder. REST uses HTTP caching (ETags, Cache-Control) naturally. tRPC uses POST for mutations, making CDN caching less straightforward.
- Smaller ecosystem. Fewer tools, fewer tutorials, fewer community resources than REST or GraphQL.
REST: The Universal Standard
Strengths
Universal compatibility. Any language, any platform, any client can consume a REST API. curl, Python, Swift, Java — everything speaks HTTP.
HTTP caching. GET requests are cacheable by CDNs, browsers, and proxies. Built-in performance optimization with zero application code.
Simple and well-understood. Every developer knows REST. Hiring is easy. Documentation patterns (OpenAPI/Swagger) are standardized.
Mature tooling. Postman, Insomnia, Bruno for testing. OpenAPI for documentation. Thousands of libraries for every language.
Stateless. Each request contains everything the server needs. Simple to scale, simple to load balance, simple to cache.
Weaknesses
- Over-fetching.
GET /users/123returns the full user object even if you only need the name. Wastes bandwidth, especially on mobile. - Under-fetching. Need user + their posts + their comments? That's 3 requests. Or you build a custom endpoint, which defeats the purpose.
- No type safety by default. The API contract lives in documentation, not code. Mismatches between docs and implementation cause bugs.
- Versioning is awkward.
/api/v1/usersvs/api/v2/users. Breaking changes require new versions, old versions require maintenance. - Endpoint proliferation. Complex apps end up with hundreds of endpoints.
/users,/users/:id,/users/:id/posts,/users/:id/posts/:postId/comments...
GraphQL: Query What You Need
Strengths
No over-fetching. Request exactly the fields you need. Mobile clients request less data than web clients. One endpoint, infinite query shapes.
query {
user(id: "123") {
name
posts {
title
commentCount
}
}
}
Single request for complex data. User + posts + comments in one request. No waterfall of REST calls.
Strong typing. GraphQL schema defines types explicitly. Code generation produces typed clients. Changes to the schema are visible to all clients.
Introspection. Query the schema itself. Tools like GraphiQL let developers explore the API interactively. Self-documenting.
Evolution without versioning. Add fields without breaking clients. Deprecate fields with warnings. No /v2/ needed.
Weaknesses
- Complexity. Schema definition, resolvers, dataloaders, subscriptions — significant learning curve.
- Caching is hard. All requests are POST to one endpoint. HTTP caching doesn't work. Need application-level caching (Apollo Client, urql).
- N+1 problem. Naive implementations make N+1 database queries. DataLoader pattern is required but adds complexity.
- Security. Arbitrary query depth can create expensive operations. Need query complexity limits, depth limits, and rate limiting.
- Overkill for simple APIs. A CRUD app with 5 entities doesn't need GraphQL's complexity.
- File uploads are awkward. GraphQL doesn't handle file uploads natively. Need multipart extensions or separate upload endpoints.
Decision Framework
Choose tRPC If:
- Full-stack TypeScript (both client and server)
- Internal API (not consumed by external developers)
- Monorepo or shared TypeScript project
- You want maximum type safety with minimum effort
- Using Next.js or similar full-stack TS framework
- Small to medium team that moves fast
Choose REST If:
- Public API consumed by external developers
- Multi-language clients (mobile, web, third-party)
- Simple CRUD operations
- HTTP caching is important
- You want the widest compatibility and tooling
- Microservices communicating across language boundaries
Choose GraphQL If:
- Multiple clients with different data needs (web, mobile, watch)
- Complex, interconnected data (social graphs, e-commerce catalogs)
- Reducing over-fetching matters (mobile apps with limited bandwidth)
- You want one endpoint for all data access
- Team has GraphQL experience or willingness to learn
- Schema-first API design appeals to your team
Common Combinations
| Stack | API Choice | Why |
|---|---|---|
| Next.js full-stack | tRPC | Same TypeScript project, maximum type safety |
| SaaS with public API | REST (public) + tRPC (internal) | REST for external, tRPC for internal |
| Mobile + Web app | GraphQL | Different data needs per client |
| Microservices | REST or gRPC | Language-agnostic communication |
| Startup MVP | tRPC | Fastest to build, easiest to change |
FAQ
Can I use tRPC and REST together?
Yes. tRPC for internal frontend-to-backend communication. REST for public APIs, webhooks, and third-party integrations. Common pattern in production.
Is GraphQL dying?
No, but its growth has slowed. Many teams that adopted GraphQL for simple use cases are moving to tRPC (TypeScript apps) or staying with REST. GraphQL thrives in its sweet spot: complex data with multiple clients.
Which is fastest?
Performance differences are negligible for most applications. All three use HTTP/JSON. The bottleneck is your database and business logic, not the API protocol.
Can I switch later?
tRPC → REST: Moderate effort (expose tRPC procedures as REST endpoints). REST → GraphQL: Significant effort. tRPC → GraphQL: Significant effort. Choose based on your current needs, but starting with tRPC is the easiest to migrate from.
Bottom Line
tRPC for TypeScript full-stack apps. The best developer experience with automatic type safety. The default choice for Next.js projects in 2026.
REST for public APIs and multi-language environments. The universal standard that everything supports.
GraphQL for complex data requirements with multiple clients. Powerful but only worth the complexity when you genuinely need flexible queries.
The 2026 trend: tRPC for internal APIs, REST for external APIs. GraphQL for complex data-heavy applications. Most teams don't need all three.