← Back to articles

tRPC vs GraphQL vs REST: Best API Approach (2026)

APIs are the backbone of every app. REST has been the default for 20 years, GraphQL offered flexibility, and tRPC brings end-to-end type safety with zero overhead. Here's how they compare in 2026.

Quick Verdict

tRPCGraphQLREST
Best forTypeScript fullstack appsMulti-client APIs, mobileSimple CRUD, public APIs
Type safety⚡ Full end-to-end🟡 With codegen❌ Manual
PerformanceExcellentGood (with care)Excellent
Learning curveLow (if you know TS)HighLow
ClientsTypeScript onlyAny languageAny language
SchemaTypeScript types (Zod)SDL (schema definition)OpenAPI (optional)

The Core Problem

REST:    Client guesses what the API returns. Types drift. Bugs happen at runtime.
GraphQL: Schema is the contract. But you need codegen to get TypeScript types.
tRPC:    Server types ARE the client types. Zero drift. Errors at compile time.

tRPC: Type Safety Without the Ceremony

tRPC removes the API layer entirely — your server functions become directly callable from the client with full TypeScript inference:

// Server — define your procedures
import { initTRPC } from '@trpc/server'
import { z } from 'zod'

const t = initTRPC.create()

export const appRouter = t.router({
  getUser: t.procedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      return await db.users.findUnique({ where: { id: input.id } })
    }),

  createUser: t.procedure
    .input(z.object({
      name: z.string().min(1),
      email: z.string().email(),
    }))
    .mutation(async ({ input }) => {
      return await db.users.create({ data: input })
    }),
})

export type AppRouter = typeof appRouter
// Client — fully typed, no codegen
import { createTRPCClient } from '@trpc/client'
import type { AppRouter } from './server'

const trpc = createTRPCClient<AppRouter>({ /* config */ })

const user = await trpc.getUser.query({ id: '123' })
// user is fully typed — hover to see the exact shape
// Typo in field name? TypeScript catches it instantly

Why Teams Choose tRPC

  • Zero codegen: Types flow from server to client automatically
  • Instant feedback: Change a server response → client shows errors immediately
  • Zod validation: Runtime validation and TypeScript types from one source
  • React Query integration: @trpc/react-query wraps TanStack Query perfectly
  • Subscriptions: WebSocket support built in

tRPC Limitations

  • TypeScript only: Both client and server must be TypeScript
  • Monorepo preferred: Type sharing works best in a monorepo
  • Not for public APIs: External consumers need REST or GraphQL
  • Tight coupling: Client and server evolve together

GraphQL: Flexible Querying

GraphQL lets clients request exactly the data they need:

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

type Query {
  user(id: ID!): User
  users(limit: Int): [User!]!
}
// Client query — ask for exactly what you need
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
      posts {
        title
        publishedAt
      }
    }
  }
`

Why Teams Choose GraphQL

  • Flexible queries: Mobile gets minimal data, web gets everything
  • Strong schema: Self-documenting API contract
  • Ecosystem: Apollo, Relay, urql — mature tools
  • Multi-platform: Any language can query a GraphQL API
  • Federation: Combine multiple services into one graph

GraphQL Pain Points

  • N+1 queries: Need DataLoader or careful resolver design
  • Codegen required: graphql-codegen for TypeScript types
  • Over-engineering: Simple CRUD doesn't need GraphQL
  • Caching complexity: HTTP caching doesn't work out of the box
  • Security: Malicious queries can be expensive (need depth limiting)

REST: The Universal Standard

REST is simple, well-understood, and works everywhere:

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

// Client
const res = await fetch('/api/users/123')
const user = await res.json()
// No type safety — user is 'any' unless you manually type it

Why REST Still Works

  • Universal: Every language, every platform
  • HTTP caching: CDN-friendly, cache headers just work
  • Simple: No special client libraries needed
  • Proven: 20+ years of best practices
  • Public APIs: The standard for external consumers

REST Pain Points

  • Over-fetching: Get all fields even when you need two
  • Under-fetching: Need 3 requests for one screen (user + posts + comments)
  • No type contract: Swagger/OpenAPI helps but types drift
  • Versioning headaches: /v1/users, /v2/users...
  • Custom endpoints: Every new UI need creates a new endpoint

Decision Framework

Choose tRPC When

  • Full-stack TypeScript (Next.js, Remix, SvelteKit)
  • Monorepo or same-repo client/server
  • Team wants maximum type safety
  • Internal APIs (not public-facing)
  • Rapid iteration speed matters most

Choose GraphQL When

  • Multiple clients (web, mobile, partner APIs)
  • Deeply nested relational data
  • Teams work independently on frontend/backend
  • Need a public API with flexible querying
  • Already have GraphQL expertise

Choose REST When

  • Building a public API
  • Simple CRUD operations
  • Non-TypeScript clients
  • Need maximum HTTP caching
  • Team is small and pragmatic

The Hybrid Approach (2026 Best Practice)

Most production apps in 2026 use multiple approaches:

Internal frontend ↔ BFF:     tRPC (type safety, speed)
Mobile apps ↔ API:            GraphQL (flexible queries)
External/partner ↔ API:       REST (universal, cacheable)
Service ↔ Service:            gRPC (performance)

FAQ

Can I switch from REST to tRPC?

Yes, and it's straightforward if you're already using TypeScript. tRPC endpoints map 1:1 to REST routes. You can migrate incrementally.

Is GraphQL dying?

No, but its use case has narrowed. In 2026, teams choose GraphQL specifically for multi-client scenarios rather than as a default.

Does tRPC work with Next.js App Router?

Yes. tRPC v11 has first-class support for React Server Components and Next.js App Router with server-side calling.

What about OpenAPI?

OpenAPI is the REST equivalent of a schema. Tools like ts-rest and Hono's OpenAPI integration give you type safety with REST. It's a middle ground between REST and tRPC.

Bottom Line

tRPC if you're full-stack TypeScript — it's the fastest way to build type-safe APIs with zero overhead. GraphQL for multi-platform APIs with complex data needs. REST for public APIs and simple services.

Our pick: tRPC for TypeScript teams — the developer experience is unmatched.

Get AI tool guides in your inbox

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