orpc-fullstack

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

oRPC — Typesafe APIs Made Simple

oRPC — 让类型安全API变得简单

oRPC combines RPC with OpenAPI for end-to-end type-safe APIs (v1.12+). It supports Zod, Valibot, Arktype, or any Standard Schema library.
oRPC将RPC与OpenAPI相结合,实现端到端的类型安全API(v1.12+)。它支持Zod、Valibot、Arktype或任何标准Schema库。

When to Apply

适用场景

Reference these guidelines when:
  • Creating or modifying API procedures (contracts, routers, handlers)
  • Setting up middleware (auth, authorization guards, logging)
  • Integrating oRPC with Hono or other server frameworks
  • Building data fetching hooks with TanStack Query
  • Implementing real-time features with event iterators / SSE
  • Configuring client links (fetch, WebSocket, batch, retry)
  • Handling errors (server-side ORPCError, client-side typed errors)
在以下场景中可参考这些指南:
  • 创建或修改API过程(契约、路由、处理程序)
  • 设置中间件(认证、授权守卫、日志)
  • 将oRPC与Hono或其他服务器框架集成
  • 使用TanStack Query构建数据获取钩子
  • 利用事件迭代器/SSE实现实时功能
  • 配置客户端链接(fetch、WebSocket、批量处理、重试)
  • 错误处理(服务端ORPCError、客户端类型化错误)

Core Concepts

核心概念

ConceptImportPurpose
Contract
oc
from
@orpc/contract
Define API shape without handlers
Procedure
os
from
@orpc/server
Function with validation + middleware + DI
RouterPlain objectCompose procedures into a tree
Middleware
os.middleware()
Intercept, inject context, guard access
Handler
OpenAPIHandler
/
RPCHandler
Serve procedures over HTTP/WS
Client
createORPCClient
+ link
Type-safe client from contract/router
TanStack Query
createTanstackQueryUtils
React hooks for queries/mutations
概念导入方式用途
Contract
oc
from
@orpc/contract
定义API结构,无需处理程序
Procedure
os
from
@orpc/server
包含验证、中间件和依赖注入的函数
Router普通对象将过程组合为树形结构
Middleware
os.middleware()
拦截请求、注入上下文、守卫访问权限
Handler
OpenAPIHandler
/
RPCHandler
通过HTTP/WS提供过程服务
Client
createORPCClient
+ link
基于契约/路由的类型安全客户端
TanStack Query
createTanstackQueryUtils
用于查询/突变的React钩子

Project Structure

项目结构

Organize by domain modules with paired contract + router files:
src/
  index.ts                    # Hono app + handler setup
  middlewares/
    auth-middleware.ts         # Session validation -> injects user
  modules/
    contract.ts               # Root barrel: all contracts
    router.ts                 # Root barrel: all routers
    health/
      health.contract.ts
      health.router.ts
    user/
      user.contract.ts
      user.router.ts
Root barrels compose modules:
export default { health, user }
.
领域模块组织,搭配契约和路由文件:
src/
  index.ts                    # Hono应用 + 处理程序设置
  middlewares/
    auth-middleware.ts         # 会话验证 -> 注入用户信息
  modules/
    contract.ts               # 根入口:所有契约
    router.ts                 # 根入口:所有路由
    health/
      health.contract.ts
      health.router.ts
    user/
      user.contract.ts
      user.router.ts
根入口组合各模块:
export default { health, user }

Contract-First Development

契约优先开发

Contracts define API shape. Routers implement them with TypeScript enforcement.
ts
// Contract — define shape
import { oc } from "@orpc/contract";
import { z } from "zod/v4";

const userContract = oc
  .route({ tags: ["user"] })
  .errors({ UNAUTHORIZED: {} });

const searchUser = userContract
  .route({ method: "POST", path: "/user/search" })
  .input(z.object({ query: z.string() }))
  .output(z.array(userSchema));

export default { searchUser };
ts
// Router — implement contract
import { implement } from "@orpc/server";
import contract from "./user.contract";

const router = implement(contract).$context<{ headers: Headers }>();

const searchUser = router.searchUser
  .use(authMiddleware)
  .handler(async ({ input, context }) => { /* ... */ });

export default { searchUser };
契约定义API结构,路由通过TypeScript约束来实现它们。
ts
// Contract — define shape
import { oc } from "@orpc/contract";
import { z } from "zod/v4";

const userContract = oc
  .route({ tags: ["user"] })
  .errors({ UNAUTHORIZED: {} });

const searchUser = userContract
  .route({ method: "POST", path: "/user/search" })
  .input(z.object({ query: z.string() }))
  .output(z.array(userSchema));

export default { searchUser };
ts
// Router — implement contract
import { implement } from "@orpc/server";
import contract from "./user.contract";

const router = implement(contract).$context<{ headers: Headers }>();

const searchUser = router.searchUser
  .use(authMiddleware)
  .handler(async ({ input, context }) => { /* ... */ });

export default { searchUser };

Procedures

过程(Procedures)

ts
import { os } from "@orpc/server";

const example = os
  .use(aMiddleware)                         // Middleware
  .input(z.object({ name: z.string() }))   // Validate input
  .output(z.object({ id: z.number() }))    // Validate output (recommended)
  .handler(async ({ input, context }) => {  // Handler
    return { id: 1 };
  });
  • .handler
    is the only required step
  • Specifying
    .output
    improves TypeScript inference speed
  • Create reusable bases:
    const protectedProcedure = os.$context<Ctx>().use(authMiddleware)
ts
import { os } from "@orpc/server";

const example = os
  .use(aMiddleware)                         // Middleware
  .input(z.object({ name: z.string() }))   // Validate input
  .output(z.object({ id: z.number() }))    // Validate output (recommended)
  .handler(async ({ input, context }) => {  // Handler
    return { id: 1 };
  });
  • .handler
    是唯一必填步骤
  • 指定
    .output
    可提升TypeScript推断速度
  • 创建可复用基础:
    const protectedProcedure = os.$context<Ctx>().use(authMiddleware)

Middleware

中间件

Auth middleware injects user into context:
ts
export const authMiddleware = os
  .$context<{ headers: Headers }>()
  .middleware(async ({ context, next }) => {
    const session = await auth.api.getSession({ headers: context.headers });
    if (!session) throw new ORPCError("UNAUTHORIZED");
    return next({ context: { ...context, user: session.user } });
  });
Input-aware middleware for authorization guards:
ts
export const membershipGuard = os
  .$context<{ user: User }>()
  .middleware(async ({ context, next }, input: { uuid: string }) => {
    // Check membership using input.uuid + context.user.id
    if (!member) throw new ORPCError("FORBIDDEN");
    return next();
  });
Stack middleware left-to-right:
.use(auth).use(guard).handler(...)
.
Built-ins:
onStart
,
onSuccess
,
onError
,
onFinish
,
dedupeMiddleware
.
认证中间件将用户信息注入上下文:
ts
export const authMiddleware = os
  .$context<{ headers: Headers }>()
  .middleware(async ({ context, next }) => {
    const session = await auth.api.getSession({ headers: context.headers });
    if (!session) throw new ORPCError("UNAUTHORIZED");
    return next({ context: { ...context, user: session.user } });
  });
感知输入的中间件用于授权守卫:
ts
export const membershipGuard = os
  .$context<{ user: User }>()
  .middleware(async ({ context, next }, input: { uuid: string }) => {
    // Check membership using input.uuid + context.user.id
    if (!member) throw new ORPCError("FORBIDDEN");
    return next();
  });
按从左到右顺序堆叠中间件:
.use(auth).use(guard).handler(...)
内置中间件:
onStart
,
onSuccess
,
onError
,
onFinish
,
dedupeMiddleware

Error Handling

错误处理

ts
// Server — throw errors
throw new ORPCError("NOT_FOUND");
throw new ORPCError("BAD_REQUEST", { message: "Invalid input" });

// Contract-defined typed errors
const contract = oc.errors({
  RATE_LIMITED: { data: z.object({ retryAfter: z.number() }) },
});

// Handler uses typed factory
const proc = implement(contract).handler(async ({ errors }) => {
  throw errors.RATE_LIMITED({ data: { retryAfter: 60 } });
});

// Client — handle errors
const [error, data] = await safe(client.doSomething({ id: "123" }));
if (isDefinedError(error)) { /* typed from contract */ }
ts
// Server — throw errors
throw new ORPCError("NOT_FOUND");
throw new ORPCError("BAD_REQUEST", { message: "Invalid input" });

// Contract-defined typed errors
const contract = oc.errors({
  RATE_LIMITED: { data: z.object({ retryAfter: z.number() }) },
});

// Handler uses typed factory
const proc = implement(contract).handler(async ({ errors }) => {
  throw errors.RATE_LIMITED({ data: { retryAfter: 60 } });
});

// Client — handle errors
const [error, data] = await safe(client.doSomething({ id: "123" }));
if (isDefinedError(error)) { /* typed from contract */ }

Hono Integration

Hono集成

ts
import { OpenAPIHandler } from "@orpc/openapi/fetch";
import { Hono } from "hono";

const handler = new OpenAPIHandler(router, { /* plugins, interceptors */ });

const app = new Hono()
  .basePath("/api")
  .use("/rpc/*", async (c, next) => {
    const { matched, response } = await handler.handle(c.req.raw, {
      prefix: "/api/rpc",
      context: { headers: c.req.raw.headers },
    });
    if (matched) return c.newResponse(response.body, response);
    await next();
  });
ts
import { OpenAPIHandler } from "@orpc/openapi/fetch";
import { Hono } from "hono";

const handler = new OpenAPIHandler(router, { /* plugins, interceptors */ });

const app = new Hono()
  .basePath("/api")
  .use("/rpc/*", async (c, next) => {
    const { matched, response } = await handler.handle(c.req.raw, {
      prefix: "/api/rpc",
      context: { headers: c.req.raw.headers },
    });
    if (matched) return c.newResponse(response.body, response);
    await next();
  });

TanStack Query

TanStack Query

ts
import { createTanstackQueryUtils } from "@orpc/tanstack-query";
const orpc = createTanstackQueryUtils(client);

// Queries
useQuery(orpc.user.search.queryOptions({ input: { query } }));

// Mutations
useMutation(orpc.vehicle.add.mutationOptions());

// Infinite queries
useInfiniteQuery(orpc.feed.list.infiniteOptions({
  input: (pageParam) => ({ cursor: pageParam, limit: 20 }),
  initialPageParam: undefined,
  getNextPageParam: (lastPage) => lastPage.nextCursor,
}));

// Keys for invalidation
orpc.vehicle.key()                           // All vehicle queries
queryClient.invalidateQueries({ queryKey: orpc.vehicle.key() });
ts
import { createTanstackQueryUtils } from "@orpc/tanstack-query";
const orpc = createTanstackQueryUtils(client);

// Queries
useQuery(orpc.user.search.queryOptions({ input: { query } }));

// Mutations
useMutation(orpc.vehicle.add.mutationOptions());

// Infinite queries
useInfiniteQuery(orpc.feed.list.infiniteOptions({
  input: (pageParam) => ({ cursor: pageParam, limit: 20 }),
  initialPageParam: undefined,
  getNextPageParam: (lastPage) => lastPage.nextCursor,
}));

// Keys for invalidation
orpc.vehicle.key()                           // All vehicle queries
queryClient.invalidateQueries({ queryKey: orpc.vehicle.key() });

Event Iterator (SSE / Streaming)

事件迭代器(SSE / 流处理)

ts
// Server — async generator
const live = os
  .output(eventIterator(z.object({ message: z.string() })))
  .handler(async function* ({ signal }) {
    for await (const payload of publisher.subscribe("topic", { signal })) {
      yield payload;
    }
  });

// Client — consume
for await (const event of await client.live()) {
  console.log(event.message);
}
Use
EventPublisher
for typed pub/sub between handlers.
ts
// Server — async generator
const live = os
  .output(eventIterator(z.object({ message: z.string() })))
  .handler(async function* ({ signal }) {
    for await (const payload of publisher.subscribe("topic", { signal })) {
      yield payload;
    }
  });

// Client — consume
for await (const event of await client.live()) {
  console.log(event.message);
}
使用
EventPublisher
实现处理程序之间的类型化发布/订阅。

Client Setup

客户端设置

ts
import { RPCLink } from "@orpc/client/fetch";
import { createORPCClient } from "@orpc/client";

const link = new RPCLink({
  url: "http://localhost:3000/api/rpc",
  headers: () => ({ Authorization: `Bearer ${getToken()}` }),
});

export const client = createORPCClient(link);
WebSocket:
import { RPCLink } from "@orpc/client/websocket"
.
ts
import { RPCLink } from "@orpc/client/fetch";
import { createORPCClient } from "@orpc/client";

const link = new RPCLink({
  url: "http://localhost:3000/api/rpc",
  headers: () => ({ Authorization: `Bearer ${getToken()}` }),
});

export const client = createORPCClient(link);
WebSocket:
import { RPCLink } from "@orpc/client/websocket"

Detailed Reference

详细参考

For comprehensive examples, advanced patterns, and full API coverage see:
  • Full Reference — Complete documentation with detailed code examples covering contracts, routers, middleware, server handlers, client setup, TanStack Query, event iterators, plugins, file uploads, WebSocket, AI SDK integration, and metadata.
如需完整示例、高级模式和全面API覆盖,请查看:
  • 完整参考 — 包含契约、路由、中间件、服务器处理程序、客户端设置、TanStack Query、事件迭代器、插件、文件上传、WebSocket、AI SDK集成和元数据的详细代码示例的完整文档。