tRPC vs GraphQL vs REST: Which API Style Should You Use? (2026)
The API style you choose shapes your entire development experience. REST is the established standard, GraphQL gives clients flexible queries, and tRPC eliminates the API layer entirely for TypeScript projects. Here's how to decide.
Quick Comparison
| Feature | tRPC | GraphQL | REST |
|---|---|---|---|
| Type safety | End-to-end (automatic) | With codegen | Manual (OpenAPI) |
| Schema | TypeScript types | SDL (Schema Definition Language) | OpenAPI/Swagger |
| Overfetching | N/A (you define returns) | Solved (client picks fields) | Common problem |
| Learning curve | Low (if you know TS) | Medium-high | Low |
| Tooling | VS Code IntelliSense | Apollo, Relay, Altair | Postman, curl, everything |
| Caching | Manual | Normalized (Apollo) | HTTP caching built-in |
| Public APIs | No | Yes | Yes |
| Language lock-in | TypeScript only | No | No |
| File uploads | Via separate route | Awkward | Simple (multipart) |
tRPC: End-to-End TypeScript
tRPC lets you call server functions directly from your React components with full type safety. No API definition, no code generation, no fetch calls.
How It Works
// Server: define a procedure
export const appRouter = router({
getUser: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.user.findUnique({ where: { id: input.id } });
}),
createPost: protectedProcedure
.input(z.object({ title: z.string(), content: z.string() }))
.mutation(async ({ input, ctx }) => {
return db.post.create({ data: { ...input, authorId: ctx.user.id } });
}),
});
// Client: call it with full type safety
const { data: user } = trpc.getUser.useQuery({ id: "123" });
// TypeScript knows exactly what `user` looks like
Strengths
- Zero API definition overhead. Change a server function → client types update instantly. No codegen step.
- Incredible DX. Autocomplete for every procedure, input, and output. Catch errors at compile time, not runtime.
- Tiny bundle. tRPC's client is ~2KB. No heavy GraphQL client library.
- Zod validation built-in. Input validation with Zod schemas that also generate types.
- React Query integration. Built on TanStack React Query — caching, refetching, optimistic updates all included.
- Subscriptions. WebSocket support for real-time features.
Weaknesses
- TypeScript only. If your frontend or backend isn't TypeScript, tRPC doesn't work.
- Monorepo preferred. Works best when client and server share a TypeScript project. Separate repos need type package publishing.
- Not for public APIs. External consumers can't use your tRPC API without the TypeScript client. No REST endpoints, no GraphQL playground.
- N+1 queries. No built-in batching/DataLoader pattern like GraphQL. Each procedure call is independent.
- Scaling complexity. Large procedure routers can become unwieldy. Needs good organization from the start.
Best For
Full-stack TypeScript projects (Next.js, T3 Stack) where frontend and backend are in the same repo or monorepo. Internal APIs that don't need external consumers.
GraphQL: Flexible Client Queries
GraphQL lets clients request exactly the data they need in a single query. The server defines a schema; clients write queries against it.
How It Works
# Schema (server)
type User {
id: ID!
name: String!
posts: [Post!]!
}
type Query {
user(id: ID!): User
}
# Query (client)
query GetUserWithPosts($id: ID!) {
user(id: $id) {
name
posts {
title
createdAt
}
}
}
Strengths
- No overfetching. Client specifies exactly which fields it needs. Mobile gets less data than desktop.
- Single endpoint. One POST request can fetch data from multiple "resources" — users, posts, and comments in one query.
- Strong typing. SDL schema is the contract between frontend and backend. Tools like GraphQL Code Generator produce TypeScript types.
- Introspection. Schema is self-documenting. GraphQL playgrounds let you explore the API interactively.
- Ecosystem maturity. Apollo, Relay, Urql — mature client libraries with caching, optimistic updates, and pagination.
- Language-agnostic. Any language can implement a GraphQL server or client.
Weaknesses
- Complexity. Resolvers, DataLoaders, schema stitching, federation — the GraphQL ecosystem has a steep learning curve.
- N+1 problem. Without DataLoader, nested queries create N+1 database queries. Solving this adds complexity.
- No HTTP caching. Everything is POST to one endpoint. CDN caching doesn't work without workarounds (persisted queries).
- File uploads are awkward. GraphQL doesn't handle multipart uploads natively.
- Over-engineering risk. For simple CRUD, GraphQL adds significant overhead for minimal benefit.
- Security surface. Deeply nested queries can cause performance issues (query depth limiting is essential).
Best For
Applications with complex, nested data relationships. Teams with separate frontend and backend teams. APIs consumed by multiple clients (web, mobile, third-party) with different data needs.
REST: The Universal Standard
REST uses HTTP methods (GET, POST, PUT, DELETE) on resource URLs. It's the most widely understood API style and works with every programming language, tool, and platform.
How It Works
GET /api/users/123 → Get user
POST /api/users → Create user
PUT /api/users/123 → Update user
DELETE /api/users/123 → Delete user
GET /api/users/123/posts → Get user's posts
Strengths
- Universal. Every language, framework, and tool supports REST. curl, Postman, browsers — it all just works.
- HTTP caching. GET requests are cacheable by CDNs, browsers, and proxies out of the box.
- Simple mental model. Resources + HTTP methods. Easy to learn, easy to teach.
- File uploads. Multipart form data is native and well-supported.
- Mature ecosystem. OpenAPI/Swagger for documentation, Postman for testing, every HTTP client library.
- Public API standard. If you're building a public API, REST is the expected format.
Weaknesses
- Overfetching. Endpoints return fixed data shapes. Mobile clients receive the same payload as desktop.
- Multiple requests. Getting a user with their posts and comments requires multiple HTTP calls (or custom endpoints that break REST conventions).
- No built-in type safety. You need OpenAPI/Swagger + codegen for type-safe clients.
- Versioning is painful. URL versioning (/v1, /v2) or header versioning — both have downsides.
- Bikeshedding. "Is it PUT or PATCH?" "Should we nest resources?" REST conventions invite endless debate.
Best For
Public APIs, simple CRUD applications, teams with mixed language stacks, and any situation where universal compatibility matters more than developer experience.
Decision Matrix
| Scenario | Recommendation |
|---|---|
| Full-stack TypeScript monorepo | tRPC |
| Multiple clients (web + mobile + API) | GraphQL or REST |
| Public/external API | REST |
| Complex nested data relationships | GraphQL |
| Simple CRUD app | REST or tRPC |
| Team with separate FE/BE teams | GraphQL or REST |
| Maximum type safety, minimum overhead | tRPC |
| Non-TypeScript backend | REST or GraphQL |
| Need HTTP caching | REST |
| Real-time subscriptions needed | GraphQL or tRPC |
Can You Combine Them?
Yes, and many teams do:
- tRPC + REST: Use tRPC for internal frontend-backend communication, expose REST endpoints for external consumers
- GraphQL + REST: GraphQL gateway federating REST microservices
- tRPC + GraphQL: Rare, but possible — tRPC for writes, GraphQL for complex reads
Performance
For most applications, performance differences are negligible. The real differences:
- REST: Fastest for simple requests (HTTP caching, CDN-friendly)
- GraphQL: Most efficient for complex data needs (fewer round trips)
- tRPC: Comparable to REST for speed, with near-zero serialization overhead
FAQ
Should I use tRPC or Server Actions in Next.js?
If you're on Next.js App Router, Server Actions handle mutations well. tRPC is better for queries, complex procedures, and when you want React Query's caching. Many projects use both.
Is GraphQL dying?
No, but its growth has slowed. The industry is recognizing that GraphQL's complexity isn't justified for every project. It remains the best choice for complex data needs.
Can I switch from REST to tRPC later?
Yes, incrementally. Add tRPC alongside REST and migrate routes one at a time. Both can coexist in the same application.
What about gRPC?
gRPC is excellent for service-to-service communication (microservices). It's not typically used for browser-to-server communication due to limited browser support. Different use case than the three compared here.
The Verdict
- tRPC for TypeScript monorepos. Best DX, least overhead, maximum type safety. The default for T3 Stack and similar setups.
- GraphQL for complex data needs and multi-client APIs. Worth the complexity when you genuinely need flexible queries.
- REST for public APIs, simple apps, and mixed-language stacks. The safe, universal choice.
In 2026, tRPC is the right choice for most new full-stack TypeScript projects. Use REST for public APIs and GraphQL when your data complexity demands it.