← Back to articles

tRPC vs GraphQL vs REST (2026 Comparison)

Three approaches to building APIs. REST is the standard. GraphQL is the flexible query language. tRPC is end-to-end TypeScript magic. Here's when to use each in 2026.

Quick Verdict

  • tRPC — Best for full-stack TypeScript apps (one team, one repo)
  • GraphQL — Best for complex data requirements and multiple clients
  • REST — Best for public APIs, simple services, and maximum compatibility

How They Work

REST

GET    /api/users          → List users
GET    /api/users/123      → Get user
POST   /api/users          → Create user
PUT    /api/users/123      → Update user
DELETE /api/users/123      → Delete user

Resources at URLs. HTTP methods for operations. Simple. Universal.

GraphQL

query {
  user(id: "123") {
    name
    email
    posts {
      title
      comments { text }
    }
  }
}

One endpoint. Client requests exactly the data it needs. No over-fetching.

tRPC

// Server
const appRouter = router({
  user: router({
    get: publicProcedure
      .input(z.object({ id: z.string() }))
      .query(async ({ input }) => {
        return db.users.findUnique({ where: { id: input.id } })
      }),
  }),
})

// Client (auto-typed from server!)
const user = await trpc.user.get.query({ id: '123' })
// user is fully typed — no codegen, no runtime validation

Direct function calls between client and server. Full type safety. No API layer.

The Comparison

tRPCGraphQLREST
Type safety✅ Automatic (best)✅ With codegen❌ Manual
Learning curveLow (just TypeScript)High (schema, resolvers)Low
Over-fetchingNo (return what you want)No (client specifies)Yes (fixed responses)
Under-fetchingNoNoYes (need multiple calls)
Multiple clients❌ TypeScript only✅ Any language✅ Any language
CachingManualNormalized cacheHTTP caching (best)
File uploads❌ Workarounds❌ Workarounds✅ Native
Real-time✅ Subscriptions✅ Subscriptions❌ (need WebSockets)
ToolingGood (growing)ExcellentBest (universal)
PerformanceExcellentGood (resolver overhead)Excellent

Developer Experience

tRPC: Zero API Layer

The killer feature — change the server function, and the client gets updated types instantly:

// Server: add a field
.query(async ({ input }) => {
  return { name: user.name, email: user.email, plan: user.plan } // Added plan
})

// Client: instantly available
const user = await trpc.user.get.query({ id: '123' })
console.log(user.plan) // TypeScript knows this exists!

No API docs to update. No types to sync. No codegen to run.

GraphQL: Flexible but Complex

# Schema definition
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Query {
  user(id: ID!): User
}
// Resolver
const resolvers = {
  Query: {
    user: (_, { id }) => db.users.findUnique({ where: { id } }),
  },
  User: {
    posts: (parent) => db.posts.findMany({ where: { authorId: parent.id } }),
  },
}

Powerful but requires: schema definition, resolvers, codegen for types, and client setup.

REST: Simple but Verbose

// Server
app.get('/api/users/:id', async (req, res) => {
  const user = await db.users.findUnique({ where: { id: req.params.id } })
  res.json(user)
})

// Client (no type safety by default)
const res = await fetch('/api/users/123')
const user = await res.json() // any type 😢

Simple to understand. No type safety without extra work (Zod, OpenAPI codegen).

When to Use Each

Choose tRPC When

  • Full-stack TypeScript (Next.js, Remix)
  • One team owns client and server
  • Monorepo or single codebase
  • Rapid prototyping and iteration
  • Internal tools and dashboards
  • Type safety is priority #1

Choose GraphQL When

  • Multiple clients (web, mobile, third-party)
  • Complex, nested data relationships
  • Clients need to fetch different data shapes
  • Large team with separate frontend/backend teams
  • Need real-time subscriptions

Choose REST When

  • Public API (consumed by external developers)
  • Simple CRUD operations
  • Need HTTP caching
  • File uploads and downloads
  • Maximum compatibility (any language, any client)
  • Microservices communicating with each other

The Practical Answer

Solo developer building a SaaS with Next.js?tRPC. Full type safety with zero overhead.

Building a platform with mobile + web + API consumers?REST for the public API, tRPC or GraphQL internally.

Complex data requirements with multiple frontends?GraphQL. Flexible queries prevent over-fetching.

Simple CRUD API?REST. Don't overcomplicate it.

Can You Mix Them?

Yes! Common patterns:

Public API:  REST (maximum compatibility)
Internal:    tRPC (type safety for your team)
Mobile:      GraphQL (flexible queries for different screens)

FAQ

Is GraphQL dying?

No, but it's less hyped. GraphQL is perfect for its use cases (complex data, multiple clients). It's overkill for simple APIs.

Can tRPC work with mobile apps?

Only if the mobile app is TypeScript/React Native. tRPC doesn't work with Swift, Kotlin, or other languages.

Should I use REST for a new project?

If your API is public or consumed by non-TypeScript clients, yes. For internal full-stack TypeScript: tRPC is better.

What about RSC (React Server Components)?

Server Components reduce the need for client-side API calls. But you still need tRPC/REST/GraphQL for mutations and client-side data fetching.

Bottom Line

tRPC for full-stack TypeScript (best DX, zero overhead). GraphQL for complex data requirements and multiple clients. REST for public APIs and maximum compatibility. In 2026, most Next.js SaaS apps use tRPC internally and REST for public APIs.

Get AI tool guides in your inbox

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