Blog
3 min read

tRPC vs REST: End-to-End Type Safety or a Standard API?

tRPC lets a TypeScript front end call back-end functions with full type safety and no API schema to maintain; REST is the universal, language-agnostic standard. How they differ, a tRPC example, when tRPC shines (TypeScript monorepos), when REST wins (public APIs, mobile, other languages), and where Server Actions fit.

If your front end and back end are both TypeScript, you have a choice REST alone used to make for you. tRPC lets the client call server functions directly, with types flowing end to end — change a field on the server and the front end shows a type error immediately.

REST in one paragraph

Resources at URLs, HTTP methods for actions, JSON in and out: GET /api/invoices/42, POST /api/invoices. Any language can call it, caches and proxies understand it, and tools like OpenAPI describe it. But the types on the client and server are separate — you keep them in sync by hand or with code generation. (Designing a REST API, OpenAPI vs Swagger)

tRPC in one example

// server/router.ts
import { initTRPC } from '@trpc/server'
import { z } from 'zod'

const t = initTRPC.context<{ userId: string | null }>().create()

export const appRouter = t.router({
  invoice: t.router({
    byId: t.procedure
      .input(z.object({ id: z.string() }))
      .query(({ input, ctx }) => db.invoice.findFirst({ where: { id: input.id, ownerId: ctx.userId } })),
    create: t.procedure
      .input(z.object({ amount: z.number().positive() }))
      .mutation(({ input, ctx }) => db.invoice.create({ data: { ...input, ownerId: ctx.userId } })),
  }),
})

export type AppRouter = typeof appRouter
// client
const invoice = await trpc.invoice.byId.query({ id: '42' })
//    ^? fully typed from the server's return type

There's no schema file and no code generation: the client imports the router's type (not its code), and TypeScript does the rest. Input is validated with Zod at runtime. It integrates with TanStack Query for caching. (Validating input with Zod, TanStack Query vs useEffect)

Side by side

tRPC REST
Type safety across the network Automatic Manual, or generated from OpenAPI
Client languages TypeScript Any
API contract The TypeScript router URLs + docs/OpenAPI
Public / third-party API Poor fit Standard
HTTP caching, CDN Limited Natural for GET
Refactoring Rename and the compiler finds every use Search and hope, or regenerate
Learning curve Low if you know TS Low

When tRPC shines

  • Full-stack TypeScript in one repository (or a monorepo), one team.
  • Internal APIs that only your own front end calls.
  • Fast iteration — refactors are safe and quick.
  • AI-assisted development — type errors give coding agents immediate feedback when they change one side. (Why TypeScript makes AI code safer)

When REST wins

  • Public APIs or partners integrating with you.
  • Mobile apps in Swift/Kotlin, or back ends in Python, Go or others.
  • Webhooks — providers send standard HTTP. (Handling webhooks reliably)
  • Caching at the HTTP layer matters.
  • Long-lived APIs that need versioning and documentation. (API versioning)

Many apps use both: tRPC (or Server Actions) for the app's own UI, REST for webhooks and the public API.

Where Server Actions fit

In Next.js, Server Actions give a similar "call a server function with types" experience for mutations, built into the framework. If you're all-in on the Next.js App Router, they cover much of what tRPC offers for forms and buttons; tRPC still adds a structured router, middleware, and works outside Next.js. (Next.js Server Actions)

And GraphQL?

GraphQL lets clients ask for exactly the fields they need across related data, with a typed schema. Great for many different clients with varied needs; more machinery than most small apps want. (GraphQL vs REST)

Security is the same either way

tRPC procedures, like REST endpoints, are callable by anyone with any input. Check authentication and authorisation in every procedure (tRPC middleware helps), and enforce ownership in the query. (IDOR explained, API authentication methods)


EasySpawn runs your full-stack TypeScript app — tRPC, REST or both — on one server with Postgres alongside, and Claude Code can lean on the types to refactor safely. See how it works or join the waitlist.

Related: Designing a REST API · GraphQL vs REST · Next.js Server Actions · OpenAPI vs Swagger

Keep reading