>_fusion-stack
API Layer

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 the src/ prefix — e.g. server/ instead of src/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 QueryClientProvider

Setup

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 appRouter

Middleware

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/openapi

TanStack 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)

On this page