← Back to articles

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

FeaturetRPCRESTGraphQL
Type safetyFull (automatic)Manual (OpenAPI + codegen)Partial (codegen needed)
Schema definitionNone (inferred from code)OpenAPI/SwaggerSDL (Schema Definition Language)
TransportHTTP (JSON)HTTPHTTP (usually POST)
Over/under-fetchingHandled by TypeScriptCommon problemSolved (query what you need)
Learning curveLow (if you know TS)LowestHighest
CachingManualHTTP caching built-inComplex
File uploadsVia separate endpointNativeComplex
Real-timeVia subscriptionsWebSockets/SSESubscriptions
ToolingGrowingMassiveStrong
Language lock-inTypeScript onlyAnyAny
Best forFull-stack TS appsPublic APIs, multi-clientComplex 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/123 returns 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/users vs /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

StackAPI ChoiceWhy
Next.js full-stacktRPCSame TypeScript project, maximum type safety
SaaS with public APIREST (public) + tRPC (internal)REST for external, tRPC for internal
Mobile + Web appGraphQLDifferent data needs per client
MicroservicesREST or gRPCLanguage-agnostic communication
Startup MVPtRPCFastest 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.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.