oRPC
OpenAPI-compatible, Zod-first RPC for any frontend — type-safe procedures that double as a documented REST API.
Overview
oRPC is a type-safe RPC library built around Zod and the OpenAPI standard. Every procedure you define is automatically a typed, validating endpoint — and you can optionally generate a full OpenAPI spec from your router with no extra work.
Fusion Stack scaffolds a complete oRPC setup tailored to your chosen frontend and backend.
Scaffolded Structure
Note: Paths below use the default
src/layout. If you chose the root layout (--no-src-dir), drop thesrc/prefix — e.g.server/instead ofsrc/server/.
src/
server/
orpc.ts # Base procedure builder (exports `pub`)
routers/
index.ts # Root appRouter with a sample hello procedure
app/
api/
orpc/
[...orpc]/
route.ts # Catch-all App Router handler
lib/
orpc/
client.ts # Typed oRPC client
provider.tsx # ORPCProvider — wraps TanStack QueryClientProviderSetup
1. Wrap your root layout
Add ORPCProvider to src/app/layout.tsx:
import { ORPCProvider } from '@/lib/orpc/provider'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<ORPCProvider>{children}</ORPCProvider>
</body>
</html>
)
}2. Call procedures from React components
oRPC's React Query utilities create hooks bound to the typed client:
'use client'
import { useQuery } from '@tanstack/react-query'
import { orpc } from '@/lib/orpc/client'
export function Hello() {
const { data } = useQuery(orpc.hello.queryOptions({ name: 'fusion-stack' }))
return <p>{data?.greeting}</p>
}3. Call from Server Components
Use the typed client directly for server-side calls:
import { orpc } from '@/lib/orpc/client'
export default async function Page() {
const data = await orpc.hello({ name: 'world' })
return <p>{data.greeting}</p>
}Adding Procedures
Define new procedures in src/server/routers/index.ts:
import { z } from 'zod'
import { pub } from '../orpc'
export const appRouter = {
hello: pub
.input(z.object({ name: z.string().optional() }))
.handler(({ input }) => {
return { greeting: `Hello, ${input.name ?? 'world'}!` }
}),
createPost: pub
.input(z.object({ title: z.string(), body: z.string() }))
.handler(async ({ input }) => {
const post = await db.post.create({ data: input })
return post
}),
}
export type AppRouter = typeof appRouterMiddleware
Add reusable middleware to the base procedure builder:
// src/server/orpc.ts
import { os } from '@orpc/server'
// Authenticated procedure builder
export const pub = os
export const authed = os.use(async ({ context, next }) => {
const user = await getSessionUser()
if (!user) throw new ORPCError('UNAUTHORIZED')
return next({ context: { ...context, user } })
})Use it in your router:
import { authed } from '../orpc'
export const appRouter = {
me: authed.handler(({ context }) => context.user),
}OpenAPI Spec Generation (optional)
oRPC can generate a full OpenAPI 3.1 spec from your router — useful for SDK generation or documentation:
// scripts/generate-openapi.ts
import { generateOpenAPI } from '@orpc/openapi'
import { appRouter } from '../src/server/routers/index'
const spec = generateOpenAPI({
router: appRouter,
info: { title: 'My API', version: '1.0.0' },
})
console.log(JSON.stringify(spec, null, 2))Install the optional package:
pnpm add @orpc/openapiTanStack Start
When your frontend is TanStack Start, oRPC mounts via createAPIFileRoute:
// app/routes/api/orpc.ts
import { createAPIFileRoute } from "@tanstack/start/api"
import { RPCHandler } from "@orpc/server/fetch"
import { appRouter } from "../../server/routers/index"
const handler = new RPCHandler(appRouter)
export const APIRoute = createAPIFileRoute("/api/orpc/$")({
GET: ({ request }) => handler.handle(request, { prefix: "/api/orpc", context: {} }).then(r => r.response ?? new Response("Not found", { status: 404 })),
POST: ({ request }) => handler.handle(request, { prefix: "/api/orpc", context: {} }).then(r => r.response ?? new Response("Not found", { status: 404 })),
})Wrap app/routes/__root.tsx with ORPCProvider from @/app/lib/orpc/provider.
Vite React
When your frontend is Vite React, oRPC is served via the Hono backend on port :3001. Use the orpc-hono.ts helper in src/server/:
import { orpcHonoHandler } from "./orpc-hono"
app.use("/api/orpc/*", (c) => orpcHonoHandler(c))Wrap src/main.tsx with ORPCProvider from @/lib/orpc/provider.
Compatibility
- ✅ Works with: Next.js API Routes, TanStack Start (built-in), Hono, Clerk, WorkOS, Better Auth, shadcn/ui
- ❌ Not compatible with: Convex backend (Convex has its own type-safe RPC system)