tRPC vs oRPC: choose by client reach and contract needs
Compare tRPC and oRPC by typing model, client frameworks, OpenAPI needs, and deployment fit, with the Better Fullstack command that generates each.

tRPC and oRPC solve the same first problem: a TypeScript client calls server procedures with inferred types and no code generation step. Both projects describe that goal in their own documentation (tRPC, oRPC).
The differences appear at the edges of the API. Which clients call it? Does anything outside your TypeScript code need to call it? Should the contract exist before the implementation? Answer those three questions and the choice usually follows.
The shared ground
Both libraries give you:
- procedures written as TypeScript functions on the server;
- runtime input validation with schema libraries such as Zod, Valibot, and ArkType (tRPC validators, oRPC overview);
- client types inferred from the server router;
- a TanStack Query integration.
Neither one replaces authorization checks inside procedures, input size limits, or error responses that a client can act on. Those stay your job with either library.
Typing model and contracts
With tRPC, the router type is the contract. The server exports AppRouter, the client imports that type, and changes flow through TypeScript inference. This works well when server and client live in one repository and ship together.
oRPC uses the same inference by default. It also supports a contract-first style: the @orpc/contract package describes input, output, errors, and metadata without business logic, and the implementation must match it. That matters when a contract is reviewed before anyone writes a handler, or when separate teams own each side.
Who calls the API
This is usually the deciding criterion.
tRPC's official client integrations center on React, through its TanStack React Query integration and Next.js. For a frontend without an official integration, the tRPC docs point to the framework-agnostic vanilla client.
oRPC can also serve the same router as a REST API with OpenAPI metadata. Procedures without explicit routing metadata are exposed as POST endpoints whose paths follow the router structure. If partners, scripts, or clients written in other languages need the API, that is a concrete reason to start with oRPC. tRPC's documentation centers on its own client protocol, so confirm how you would produce an OpenAPI description before choosing it for that case.
Runtime and deployment
Both mount inside the server you already run. oRPC documents adapters for Node.js, Bun, Deno, and Cloudflare Workers. tRPC can run standalone or be mounted as an endpoint on an existing server through its adapters. Pick the backend and runtime first, then check the adapter for that exact host.
What Better Fullstack generates
The two commands below differ only in --api:
bun create better-fullstack@latest my-app \
--ecosystem typescript \
--frontend tanstack-router \
--backend hono \
--runtime bun \
--database sqlite \
--orm drizzle \
--auth better-auth \
--api trpcbun create better-fullstack@latest my-app \
--ecosystem typescript \
--frontend tanstack-router \
--backend hono \
--runtime bun \
--database sqlite \
--orm drizzle \
--auth better-auth \
--api orpcAdd --dry-run to preview either project without writing files. The generated projects differ in these places:
| Concern | --api trpc | --api orpc |
|---|---|---|
| Procedure builder | initTRPC in packages/api/src/index.ts | os from @orpc/server in packages/api/src/index.ts |
| Auth guard | protectedProcedure throws TRPCError UNAUTHORIZED | protectedProcedure throws ORPCError("UNAUTHORIZED") |
| Hono mount | @hono/trpc-server on /trpc/* | RPCHandler on /rpc, plus OpenAPIHandler on /api-reference |
| API reference | none | OpenAPIReferencePlugin with the Zod JSON Schema converter |
| Web client | httpBatchLink with createTRPCOptionsProxy | RPCLink with createTanstackQueryUtils |
Better Fullstack also enforces the client reach difference. tRPC is offered only with React-based frontends. With Svelte, Nuxt, Solid, or SolidStart, oRPC is the option of the two that the CLI accepts; --frontend svelte --api trpc is rejected with a suggestion to use oRPC. This command generates a SvelteKit client that uses @orpc/tanstack-query with @tanstack/svelte-query:
bun create better-fullstack@latest my-app \
--ecosystem typescript \
--frontend svelte \
--backend hono \
--runtime bun \
--database sqlite \
--orm drizzle \
--auth better-auth \
--api orpcThese are generated projects, not runtime-verified deployments. Install, build, and exercise the slice you care about before committing to either.
Decision table
| If this is the stronger constraint | Start with |
|---|---|
| React client in the same repository, nothing else | Either; the one your team already knows |
| Svelte, Nuxt, Solid, or SolidStart client | oRPC |
| OpenAPI description from the same router | oRPC |
| Contract reviewed before implementation | oRPC with @orpc/contract |
| Existing tRPC routers in production | tRPC, unless a requirement above rules it out |
Compare real generated stacks
- TanStack Router + Hono + tRPC + Drizzle
- TanStack Router + Hono + oRPC + Drizzle
- SvelteKit + Hono + Drizzle with oRPC
The Hono tRPC Drizzle guide and Hono Better Auth guide show both layers in a full project. If the API mostly serves one web app, read self backend vs separate API before adding a separate server at all.