oRPC vs tRPC v11 vs Hono RPC: Type-Safe APIs in TypeScript 2026
oRPC vs tRPC v11 vs Hono RPC: Type-Safe APIs in TypeScript 2026
TL;DR
Type-safe RPC frameworks eliminate the manual contract between your TypeScript backend and frontend — the client knows the exact types of every procedure. tRPC v11 is the established leader — deep React Query integration, huge ecosystem of adapters, and the standard choice for Next.js fullstack apps. oRPC is the modern challenger — brings OpenAPI output (tRPC can't), standard schema support (Zod, Valibot, ArkType), and OpenAPI-first design while keeping tRPC-like DX. Hono RPC is the edge-native option — the hc typed client works in Cloudflare Workers, Vercel Edge Functions, and anywhere Hono runs; no overhead for lightweight edge APIs. For Next.js fullstack apps with React Query: tRPC v11. For APIs that need OpenAPI spec output: oRPC. For edge-first APIs on Cloudflare Workers: Hono RPC.
Key Takeaways
- tRPC v11 released with TanStack Query integration —
useTRPCQuerynow wraps TanStack Query v5 - oRPC generates OpenAPI 3.1 spec — documentation and SDK generation tRPC can't do
- Hono RPC's typed client
hcworks in browser, Node.js, and edge runtimes - oRPC supports standard schemas — Zod, Valibot, ArkType all work (not Zod-only like tRPC v10)
- tRPC GitHub stars: 35k — the most adopted type-safe RPC framework
- Hono's
app.route()for RPC composes cleanly with REST routes in the same server - oRPC supports middleware with type-safe context — very similar to tRPC middleware patterns
The Type-Safe API Problem
REST APIs without contracts require manual synchronization:
// Without type-safe RPC — fragile
// Backend changes return type
// Frontend doesn't know — silent runtime error
// api/users.ts (backend)
export async function getUser(id: string) {
return { id, name: "Alice", email: "alice@example.com" }; // Added email
}
// frontend/UserCard.tsx — doesn't know email was added
const user = await fetch(`/api/users/${id}`).then(r => r.json());
// user is 'any' — TypeScript can't help
Type-safe RPC solutions:
// With tRPC — end-to-end types
// Backend return type automatically inferred on client
const user = await trpc.user.get.query({ id }); // user.email is typed
tRPC v11: The Established Standard
tRPC v11 brings first-class TanStack Query v5 integration and server-side calling patterns for Next.js App Router.
Installation
npm install @trpc/server @trpc/client @trpc/react-query @trpc/next
npm install @tanstack/react-query zod
Server Setup
// server/trpc.ts
import { initTRPC, TRPCError } from "@trpc/server";
import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
import superjson from "superjson";
import { z } from "zod";
// Context — available in all procedures
export type Context = {
userId: string | null;
db: Database;
};
export async function createContext(opts: CreateNextContextOptions): Promise<Context> {
const token = opts.req.headers.authorization?.split(" ")[1];
const userId = token ? await verifyJwt(token) : null;
return { userId, db: getDatabase() };
}
const t = initTRPC.context<Context>().create({
transformer: superjson, // Supports Date, Map, Set in serialization
});
// Reusable middleware
const isAuthenticated = t.middleware(({ ctx, next }) => {
if (!ctx.userId) throw new TRPCError({ code: "UNAUTHORIZED" });
return next({ ctx: { ...ctx, userId: ctx.userId } }); // Narrows userId to string
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthenticated);
Router and Procedures
// server/routers/users.ts
import { router, publicProcedure, protectedProcedure } from "../trpc";
import { z } from "zod";
export const usersRouter = router({
// Public procedure
getById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const user = await ctx.db.users.findById(input.id);
if (!user) throw new TRPCError({ code: "NOT_FOUND" });
return user;
}),
// Protected procedure — ctx.userId is string (not null)
updateProfile: protectedProcedure
.input(z.object({
name: z.string().min(1).max(100),
bio: z.string().max(500).optional(),
}))
.mutation(async ({ ctx, input }) => {
return ctx.db.users.update(ctx.userId, input);
}),
// Subscription
onNewMessage: protectedProcedure
.subscription(({ ctx }) => {
return observable<Message>((emit) => {
const unsubscribe = subscribeToMessages(ctx.userId, (message) => {
emit.next(message);
});
return unsubscribe;
});
}),
});
// Root router
export const appRouter = router({
users: usersRouter,
posts: postsRouter,
});
export type AppRouter = typeof appRouter;
React Client
// app/providers.tsx
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createTRPCReact } from "@trpc/react-query";
import { httpBatchLink } from "@trpc/client";
import superjson from "superjson";
import type { AppRouter } from "@/server/routers";
export const trpc = createTRPCReact<AppRouter>();
export function TRPCProvider({ children }: { children: React.ReactNode }) {
const queryClient = new QueryClient();
const trpcClient = trpc.createClient({
links: [httpBatchLink({ url: "/api/trpc", transformer: superjson })],
});
return (
<QueryClientProvider client={queryClient}>
<trpc.Provider client={trpcClient} queryClient={queryClient}>
{children}
</trpc.Provider>
</QueryClientProvider>
);
}
// app/users/[id]/page.tsx
function UserProfile({ id }: { id: string }) {
const { data: user, isLoading } = trpc.users.getById.useQuery({ id });
if (isLoading) return <Spinner />;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
oRPC: OpenAPI-First Type-Safe RPC
oRPC (OpenRPC) is a newer framework that adds OpenAPI spec generation to the tRPC-style DX. It supports multiple schema libraries and generates documentation automatically.
Installation
npm install @orpc/server @orpc/client @orpc/react-query
npm install zod # Or valibot, arktype
Server Setup
// server/orpc.ts
import { os } from "@orpc/server";
import { z } from "zod";
// Create a base procedure
export const pub = os; // Public procedures
// Middleware for authentication
export const authed = os.use(async ({ context, next }, input) => {
const userId = await getAuthenticatedUserId(context.req);
if (!userId) throw new ORPCError({ code: "UNAUTHORIZED" });
return next({ context: { ...context, userId } });
});
Procedures with OpenAPI Metadata
// server/routers/users.ts
import { os } from "@orpc/server";
import { z } from "zod";
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
createdAt: z.date(),
});
export const usersRouter = {
getById: pub
.input(z.object({ id: z.string().describe("User ID") }))
.output(UserSchema)
// OpenAPI metadata — generates documentation automatically
.route({ method: "GET", path: "/users/{id}", tags: ["Users"] })
.handler(async ({ input, context }) => {
const user = await db.users.findById(input.id);
if (!user) throw new ORPCError({ code: "NOT_FOUND" });
return user;
}),
create: authed
.input(z.object({
name: z.string().min(1),
email: z.string().email(),
}))
.output(UserSchema)
.route({ method: "POST", path: "/users", tags: ["Users"] })
.handler(async ({ input, context }) => {
return db.users.create({ ...input, createdAt: new Date() });
}),
};
// Root router
export const appRouter = {
users: usersRouter,
posts: postsRouter,
};
export type AppRouter = typeof appRouter;
OpenAPI Spec Generation
// This is oRPC's killer feature — tRPC cannot do this
import { OpenAPIGenerator } from "@orpc/openapi";
import { appRouter } from "./routers";
const generator = new OpenAPIGenerator({
info: {
title: "My API",
version: "1.0.0",
description: "Auto-generated from oRPC router definitions",
},
servers: [{ url: "https://api.example.com" }],
});
const spec = generator.generate(appRouter);
// spec is a full OpenAPI 3.1 JSON document
// Serve as /api/openapi.json
app.get("/api/openapi.json", (req, res) => {
res.json(spec);
});
// Use with Scalar UI for interactive docs
import { ApiReference } from "@scalar/express-api-reference";
app.use("/api/docs", ApiReference({ url: "/api/openapi.json" }));
React Client (same as tRPC)
import { createORPCReactQueryUtils } from "@orpc/react-query";
import { createTanstackQueryUtils } from "@orpc/tanstack-query";
import type { AppRouter } from "@/server/routers";
const client = createORPCClient<AppRouter>({
baseURL: "/api/orpc",
});
const orpc = createTanstackQueryUtils(client);
function UserProfile({ id }: { id: string }) {
const { data: user } = useQuery(orpc.users.getById.queryOptions({ id }));
return <div>{user?.name}</div>;
}
Hono RPC: Edge-Native Typed Client
Hono's RPC feature (hc) generates a fully typed client from your Hono routes. It's the lightest option and runs natively in Cloudflare Workers.
Installation
npm install hono
Server with Typed Routes
// server/app.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
});
const app = new Hono()
.get("/users/:id",
async (c) => {
const id = c.req.param("id");
const user = await getUser(id);
return c.json(user satisfies z.infer<typeof UserSchema>);
}
)
.post("/users",
zValidator("json", z.object({
name: z.string().min(1),
email: z.string().email(),
})),
async (c) => {
const body = c.req.valid("json"); // Fully typed
const user = await createUser(body);
return c.json(user, 201);
}
)
.delete("/users/:id", async (c) => {
await deleteUser(c.req.param("id"));
return c.json({ success: true });
});
export type AppType = typeof app;
export default app;
Typed Client with hc
// client/api.ts
import { hc } from "hono/client";
import type { AppType } from "@/server/app";
// Create fully typed client
const client = hc<AppType>("https://api.example.com");
// All calls are typed — TypeScript knows the response shape
const response = await client.users[":id"].$get({ param: { id: "123" } });
const user = await response.json();
// user is typed: { id: string, name: string, email: string }
// POST with validated body
const createResponse = await client.users.$post({
json: { name: "Alice", email: "alice@example.com" },
});
const newUser = await createResponse.json();
Using Hono RPC in React
import { useQuery, useMutation } from "@tanstack/react-query";
import { client } from "@/client/api";
function UserProfile({ id }: { id: string }) {
const { data: user } = useQuery({
queryKey: ["user", id],
queryFn: async () => {
const res = await client.users[":id"].$get({ param: { id } });
return res.json();
},
});
return <div>{user?.name}</div>;
}
Feature Comparison
| Feature | tRPC v11 | oRPC | Hono RPC |
|---|---|---|---|
| OpenAPI output | ❌ | ✅ Native | ❌ |
| React Query integration | ✅ First-class | ✅ | Manual |
| Edge runtime | ✅ | ✅ | ✅ Native |
| Schema libraries | Zod (primary) | Zod, Valibot, ArkType | Zod (validator) |
| Subscriptions/WebSocket | ✅ | Partial | Via Hono WS |
| File upload | Plugin | Plugin | ✅ Native |
| Middleware | ✅ Typed | ✅ Typed | ✅ |
| REST routes mix | ❌ (RPC only) | ✅ | ✅ Natural |
| Bundle size | Medium | Small | ✅ Very small |
| GitHub stars | 35k | 2k | 20k (Hono) |
| Next.js integration | ✅ Official | ✅ | Via Hono Next.js |
| Documentation | ✅ Excellent | Good | ✅ Excellent |
When to Use Each
Choose tRPC v11 if:
- You're building a Next.js fullstack app and want the most mature ecosystem
- TanStack Query integration for caching and optimistic updates is important
- You're already familiar with tRPC and want the proven path
- Real-time subscriptions (WebSocket) are in your feature set
Choose oRPC if:
- You need to expose an OpenAPI spec for external consumers (mobile apps, third-party integrations)
- Multiple schema libraries (not just Zod) need to work together in your codebase
- Auto-generated API documentation from router definitions is a priority
- You're building a public API that other developers will consume
Choose Hono RPC if:
- You're deploying to Cloudflare Workers, Vercel Edge, or Deno Deploy
- You need typed REST routes alongside your RPC procedures in the same server
- Bundle size matters — Hono's
hcclient is tiny - You're already using Hono for your backend and want to add type safety
Methodology
Data sourced from GitHub repositories (star counts as of February 2026), official tRPC, oRPC, and Hono documentation, npm weekly download statistics (January 2026), and community discussions from the tRPC Discord, Hono Discord, and TypeScript Weekly newsletter. oRPC comparison data from official oRPC documentation comparison pages.
Related: tRPC v11 vs ts-rest for a deeper tRPC vs contract-first comparison, or Hono vs Elysia vs Nitro for the full Hono ecosystem context.