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)50,539
FastifyNo (add OpenAPI + generator)Yes, for a clientDefault (JSON Schema)41,467
ElysiaInferred (Eden)NoDefault (TypeBox)40,682 (on Node; stronger on Bun)
tRPCInferred, RPC-shapedNoPer-procedure schemasn/a (rides another server)
HonoInferred (hc)NoOpt-in middleware29,356
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 with the same app delivering about 1.8x as many GET /users/:id requests per second on Bun as Node (measured on the same code).

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. (bun create nifra my-app if you want to run it on ours first.)