Choosing a TypeScript API framework in 2026

Updated 2026-08-04 · Disclosure: we build Nifra, one of the options below. The framework-agnostic part - the three levels of "typed" - applies no matter what you pick.

Every framework claims TypeScript support. The claims mean three very different things, and knowing which level you are buying is the whole decision. Level 1: your handlers are typed. Level 2: your client is typed from the server. Level 3: the types are enforced at runtime too. Most production incidents blamed on "TypeScript didn't catch it" are a missing level, not a missing annotation.

The three levels of "typed"

  • Level 1 - typed handlers. Express with @types/express, Koa, most classic frameworks. The compiler checks your server code against itself. Nothing checks the caller.
  • Level 2 - inferred contract. The client's request and response types derive from the server's types: tRPC, Elysia's Eden, Hono's hc, Nifra's client<typeof app>(), ts-rest. A renamed field breaks the frontend build instead of production. The differentiators inside this level: does it need codegen (ts-rest and OpenAPI generators do; tRPC/Eden/hc/nifra don't), and does it survive non-TypeScript callers (tRPC's RPC shape doesn't map cleanly to plain HTTP; the others stay REST-shaped).
  • Level 3 - runtime validation. Types are erased at runtime; a public endpoint typed { age: number } still receives { age: "99; DROP TABLE" } unless something checks. Fastify (JSON Schema), Elysia (TypeBox), NestJS (class-validator), and Nifra (Standard Schema - Zod, Valibot, ArkType, or hand-rolled) validate declared schemas at the boundary. tRPC validates if you attach a schema per procedure; Hono via middleware. The question to ask: is validation the default path or a discipline?

The field, scored

FrameworkTyped clientCodegen neededRuntime validationNode req/s (our run, validated POST)
NifraInferred, REST-shapedNoDefault (Standard Schema)59,764
FastifyNo (add OpenAPI + generator)Yes, for a clientDefault (JSON Schema)53,442
ElysiaInferred (Eden)NoDefault (TypeBox)44,130 (on Node; stronger on Bun)
tRPCInferred, RPC-shapedNoPer-procedure schemasn/a (rides another server)
HonoInferred (hc)NoOpt-in middleware32,332
NestJSNo (OpenAPI + generator)Yes, for a clientOpt-in decorators(slowest of the set in public benchmarks)

Throughput from our published harness (oha @ 50 conns, identical semantics; all rows + methodology on benchmarks). tRPC is a contract layer over an HTTP server rather than a server itself, so it has no row - pair its column with whichever host you'd run.

How to choose

  • Internal tools, TS on both ends, RPC feel is fine: tRPC remains excellent. Its weakness is the boundary with anything that isn't your TypeScript monorepo - mobile teams, partners, curl.
  • Backend-only, maximum battle-testing: Fastify - accept the codegen step if you need a typed client.
  • Bun-first backend: Elysia. Everything-runtimes minimal: Hono. (head-to-head.)
  • All three levels, REST-shaped, plus a frontend: Nifra - the inferred client extends beyond fetch calls into SSR loaders, pages, and server functions for React/Vue/Svelte/Solid/Preact, validation is the default, and the API surface ships as a live MCP server so AI agents write against your real contract. Fastest of the set in our Node runs, and the same app moves to Bun for ~2x (measured).

The test to run before committing

Whatever you shortlist: rename one response field on the server and see what breaks. If the answer is "nothing until runtime", you are at Level 1 with extra steps. Then POST a malformed body at a typed endpoint. If it reaches your handler, you are missing Level 3. Ten minutes, and it filters the field faster than any comparison table - including this one. (bunx create-nifra my-app if you want to run it on ours first.)