orpc-guide

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

oRPC Guide

oRPC指南

oRPC is a type-safe RPC framework that combines end-to-end type safety with OpenAPI compliance. It supports procedures, routers, middleware, context injection, error handling, file uploads, streaming (SSE), server actions, and contract-first development across 20+ framework adapters.
Scope: This guide is specifically for the oRPC library (
@orpc/*
packages). It is not a general RPC/gRPC guide, not for tRPC-only projects (unless migrating to oRPC), and not for generic TypeScript API development without oRPC. For tRPC-to-oRPC migration, see
references/contract-first.md
.
oRPC是一款兼具端到端类型安全与OpenAPI合规性的类型安全RPC框架。它支持过程、路由、中间件、上下文注入、错误处理、文件上传、流处理(SSE)、服务器操作以及契约优先开发,并且适配20+种框架。
适用范围:本指南专门针对oRPC库(
@orpc/*
包)。它不是通用RPC/gRPC指南,不适用于仅使用tRPC的项目(除非是迁移到oRPC),也不适用于不涉及oRPC的通用TypeScript API开发。关于从tRPC迁移到oRPC的内容,请参阅
references/contract-first.md

Quick Start

快速开始

Install

安装

sh
npm install @orpc/server@latest @orpc/client@latest
For OpenAPI support, also install:
sh
npm install @orpc/openapi@latest
sh
npm install @orpc/server@latest @orpc/client@latest
如需OpenAPI支持,还需安装:
sh
npm install @orpc/openapi@latest

Prerequisites

前置要求

  • Node.js 18+ (20+ recommended) | Bun | Deno | Cloudflare Workers
  • TypeScript project with strict mode recommended
  • Supports Zod, Valibot, ArkType, and any Standard Schema library
  • Node.js 18+(推荐20+)| Bun | Deno | Cloudflare Workers
  • 推荐启用严格模式的TypeScript项目
  • 支持Zod、Valibot、ArkType及任何标准Schema库

Define Procedures and Router

定义过程与路由

ts
import { ORPCError, os } from '@orpc/server'
import * as z from 'zod'

const PlanetSchema = z.object({
  id: z.number().int().min(1),
  name: z.string(),
  description: z.string().optional(),
})

export const listPlanet = os
  .input(z.object({
    limit: z.number().int().min(1).max(100).optional(),
    cursor: z.number().int().min(0).default(0),
  }))
  .handler(async ({ input }) => {
    return [{ id: 1, name: 'Earth' }]
  })

export const findPlanet = os
  .input(PlanetSchema.pick({ id: true }))
  .handler(async ({ input }) => {
    return { id: 1, name: 'Earth' }
  })

export const createPlanet = os
  .$context<{ headers: Headers }>()
  .use(({ context, next }) => {
    const user = parseJWT(context.headers.get('authorization')?.split(' ')[1])
    if (user) return next({ context: { user } })
    throw new ORPCError('UNAUTHORIZED')
  })
  .input(PlanetSchema.omit({ id: true }))
  .handler(async ({ input, context }) => {
    return { id: 1, name: input.name }
  })

export const router = {
  planet: { list: listPlanet, find: findPlanet, create: createPlanet },
}
ts
import { ORPCError, os } from '@orpc/server'
import * as z from 'zod'

const PlanetSchema = z.object({
  id: z.number().int().min(1),
  name: z.string(),
  description: z.string().optional(),
})

export const listPlanet = os
  .input(z.object({
    limit: z.number().int().min(1).max(100).optional(),
    cursor: z.number().int().min(0).default(0),
  }))
  .handler(async ({ input }) => {
    return [{ id: 1, name: 'Earth' }]
  })

export const findPlanet = os
  .input(PlanetSchema.pick({ id: true }))
  .handler(async ({ input }) => {
    return { id: 1, name: 'Earth' }
  })

export const createPlanet = os
  .$context<{ headers: Headers }>()
  .use(({ context, next }) => {
    const user = parseJWT(context.headers.get('authorization')?.split(' ')[1])
    if (user) return next({ context: { user } })
    throw new ORPCError('UNAUTHORIZED')
  })
  .input(PlanetSchema.omit({ id: true }))
  .handler(async ({ input, context }) => {
    return { id: 1, name: input.name }
  })

export const router = {
  planet: { list: listPlanet, find: findPlanet, create: createPlanet },
}

Create Server (Node.js)

创建服务器(Node.js)

ts
import { createServer } from 'node:http'
import { RPCHandler } from '@orpc/server/node'
import { CORSPlugin } from '@orpc/server/plugins'
import { onError } from '@orpc/server'

const handler = new RPCHandler(router, {
  plugins: [new CORSPlugin()],
  interceptors: [onError((error) => console.error(error))],
})

const server = createServer(async (req, res) => {
  const { matched } = await handler.handle(req, res, {
    prefix: '/rpc',
    context: { headers: new Headers(req.headers as Record<string, string>) },
  })
  if (!matched) {
    res.statusCode = 404
    res.end('Not found')
  }
})

server.listen(3000)
ts
import { createServer } from 'node:http'
import { RPCHandler } from '@orpc/server/node'
import { CORSPlugin } from '@orpc/server/plugins'
import { onError } from '@orpc/server'

const handler = new RPCHandler(router, {
  plugins: [new CORSPlugin()],
  interceptors: [onError((error) => console.error(error))],
})

const server = createServer(async (req, res) => {
  const { matched } = await handler.handle(req, res, {
    prefix: '/rpc',
    context: { headers: new Headers(req.headers as Record<string, string>) },
  })
  if (!matched) {
    res.statusCode = 404
    res.end('Not found')
  }
})

server.listen(3000)

Create Client

创建客户端

ts
import type { RouterClient } from '@orpc/server'
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'

const link = new RPCLink({
  url: 'http://127.0.0.1:3000/rpc',
  headers: { Authorization: 'Bearer token' },
})

const client: RouterClient<typeof router> = createORPCClient(link)

// Fully typed calls
const planets = await client.planet.list({ limit: 10 })
const planet = await client.planet.find({ id: 1 })
ts
import type { RouterClient } from '@orpc/server'
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'

const link = new RPCLink({
  url: 'http://127.0.0.1:3000/rpc',
  headers: { Authorization: 'Bearer token' },
})

const client: RouterClient<typeof router> = createORPCClient(link)

// Fully typed calls
const planets = await client.planet.list({ limit: 10 })
const planet = await client.planet.find({ id: 1 })

Server-Side Client (No HTTP)

服务端客户端(无HTTP)

Call procedures directly without HTTP overhead — essential for SSR in Next.js, Nuxt, SvelteKit, etc.
ts
import { call, createRouterClient } from '@orpc/server'

// Single procedure call
const result = await call(router.planet.find, { id: 1 }, { context: {} })

// Router client (multiple procedures)
const serverClient = createRouterClient(router, {
  context: async () => ({ headers: await headers() }),
})
const planets = await serverClient.planet.list({ limit: 10 })
Use
.callable()
for individual procedures:
ts
const getPlanet = os
  .input(z.object({ id: z.string() }))
  .handler(async ({ input }) => ({ id: input.id }))
  .callable({ context: {} })

const result = await getPlanet({ id: '123' })
See
references/api-reference.md
for full server-side calling patterns.
无需HTTP开销即可直接调用过程——这在Next.js、Nuxt、SvelteKit等框架的SSR中至关重要。
ts
import { call, createRouterClient } from '@orpc/server'

// Single procedure call
const result = await call(router.planet.find, { id: 1 }, { context: {} })

// Router client (multiple procedures)
const serverClient = createRouterClient(router, {
  context: async () => ({ headers: await headers() }),
})
const planets = await serverClient.planet.list({ limit: 10 })
使用
.callable()
调用单个过程:
ts
const getPlanet = os
  .input(z.object({ id: z.string() }))
  .handler(async ({ input }) => ({ id: input.id }))
  .callable({ context: {} })

const result = await getPlanet({ id: '123' })
有关完整的服务端调用模式,请参阅
references/api-reference.md

Core Concepts

核心概念

Procedure Chain

过程链

ts
const example = os
  .use(middleware)              // Apply middleware
  .input(z.object({...}))      // Validate input (Zod/Valibot/ArkType)
  .output(z.object({...}))     // Validate output (recommended for perf)
  .handler(async ({ input, context }) => { ... })  // Required
  .callable()                  // Make callable as regular function
  .actionable()                // Server Action compatibility
Only
.handler()
is required. All other chain methods are optional.
ts
const example = os
  .use(middleware)              // Apply middleware
  .input(z.object({...}))      // Validate input (Zod/Valibot/ArkType)
  .output(z.object({...}))     // Validate output (recommended for perf)
  .handler(async ({ input, context }) => { ... })  // Required
  .callable()                  // Make callable as regular function
  .actionable()                // Server Action compatibility
.handler()
是必填项。所有其他链式方法均为可选。

Router

路由

Routers are plain objects of procedures. They can be nested and support lazy loading:
ts
const router = {
  ping: os.handler(async () => 'pong'),
  planet: os.lazy(() => import('./planet')),  // Code splitting
}
Apply middleware to all procedures in a router:
ts
const router = os.use(authMiddleware).router({ ping, pong })
路由是由过程组成的普通对象。它们可以嵌套,并且支持懒加载:
ts
const router = {
  ping: os.handler(async () => 'pong'),
  planet: os.lazy(() => import('./planet')),  // Code splitting
}
为路由中的所有过程应用中间件:
ts
const router = os.use(authMiddleware).router({ ping, pong })

Middleware

中间件

ts
const authMiddleware = os
  .$context<{ headers: Headers }>()
  .middleware(async ({ context, next }) => {
    const user = await getUser(context.headers)
    if (!user) throw new ORPCError('UNAUTHORIZED')
    return next({ context: { user } })
  })
Built-in lifecycle middlewares:
onStart
,
onSuccess
,
onError
,
onFinish
.
ts
const authMiddleware = os
  .$context<{ headers: Headers }>()
  .middleware(async ({ context, next }) => {
    const user = await getUser(context.headers)
    if (!user) throw new ORPCError('UNAUTHORIZED')
    return next({ context: { user } })
  })
内置生命周期中间件:
onStart
onSuccess
onError
onFinish

Context

上下文

Two types: Initial Context (provided at handler creation) and Execution Context (injected by middleware at runtime). See
references/api-reference.md
.
上下文分为两种:初始上下文(在创建处理器时提供)和执行上下文(在运行时由中间件注入)。详情请参阅
references/api-reference.md

Error Handling

错误处理

ts
// Normal approach
throw new ORPCError('NOT_FOUND', { message: 'Planet not found' })

// Type-safe approach
const base = os.errors({
  NOT_FOUND: { message: 'Not found' },
  RATE_LIMITED: { data: z.object({ retryAfter: z.number() }) },
})
Warning:
ORPCError.data
is sent to the client. Never include sensitive information.
ts
// Normal approach
throw new ORPCError('NOT_FOUND', { message: 'Planet not found' })

// Type-safe approach
const base = os.errors({
  NOT_FOUND: { message: 'Not found' },
  RATE_LIMITED: { data: z.object({ retryAfter: z.number() }) },
})
警告
ORPCError.data
会发送给客户端。切勿在其中包含敏感信息。

Event Iterator (SSE/Streaming)

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

ts
const streaming = os
  .output(eventIterator(z.object({ message: z.string() })))
  .handler(async function* ({ input, lastEventId }) {
    while (true) {
      yield { message: 'Hello!' }
      await new Promise(r => setTimeout(r, 1000))
    }
  })
ts
const streaming = os
  .output(eventIterator(z.object({ message: z.string() })))
  .handler(async function* ({ input, lastEventId }) {
    while (true) {
      yield { message: 'Hello!' }
      await new Promise(r => setTimeout(r, 1000))
    }
  })

File Upload/Download

文件上传/下载

ts
const upload = os
  .input(z.file())
  .handler(async ({ input }) => {
    console.log(input.name)  // File name
    return { success: true }
  })
For uploads >100MB, use a dedicated upload solution or extend the body parser.
ts
const upload = os
  .input(z.file())
  .handler(async ({ input }) => {
    console.log(input.name)  // File name
    return { success: true }
  })
对于大于100MB的文件上传,请使用专用的上传解决方案或扩展体解析器。

Built-in Helpers

内置工具

oRPC provides built-in helpers for common server tasks:
  • Cookies:
    getCookie
    ,
    setCookie
    ,
    deleteCookie
    from
    @orpc/server/helpers
  • Cookie signing:
    sign
    ,
    unsign
    for tamper-proof cookies
  • Encryption:
    encrypt
    ,
    decrypt
    for sensitive data (AES-GCM with PBKDF2)
  • Rate limiting:
    @orpc/experimental-ratelimit
    with Memory, Redis, Upstash, and Cloudflare adapters
  • Event publishing:
    @orpc/experimental-publisher
    for distributed pub/sub with resume support
See
references/helpers.md
for full API and examples.
oRPC为常见的服务器任务提供了内置工具:
  • Cookies:从
    @orpc/server/helpers
    中获取
    getCookie
    setCookie
    deleteCookie
  • Cookie签名
    sign
    unsign
    用于防篡改Cookie
  • 加密
    encrypt
    decrypt
    用于敏感数据(采用PBKDF2的AES-GCM算法)
  • 速率限制
    @orpc/experimental-ratelimit
    ,支持Memory、Redis、Upstash和Cloudflare适配器
  • 事件发布
    @orpc/experimental-publisher
    ,支持分布式发布/订阅及恢复功能
有关完整API及示例,请参阅
references/helpers.md

Key Rules and Constraints

关键规则与约束

  1. Handler is required -
    .handler()
    is the only required method on a procedure
  2. Output schema recommended - Explicitly specify
    .output()
    for better TypeScript performance
  3. Middleware deduplication - oRPC auto-deduplicates leading middleware; use context guards for manual dedup
  4. Error data is public - Never put sensitive info in
    ORPCError.data
  5. Body parser conflicts - Register framework body parsers AFTER oRPC middleware (Express, Fastify, Elysia)
  6. RPCHandler vs OpenAPIHandler - RPCHandler uses proprietary protocol (for RPCLink only); OpenAPIHandler is REST/OpenAPI-compatible
  7. Lazy routers - Use
    os.lazy(() => import('./module'))
    for code splitting; use standalone
    lazy()
    for faster type inference
  8. SSE auto-reconnect - Standard SSE clients auto-reconnect; use
    lastEventId
    to resume streams
  9. File limitations - No chunked/resumable uploads; File/Blob unsupported in AsyncIteratorObject
  10. React Native - Fetch API has limitations (no File/Blob, no Event Iterator); use
    expo/fetch
    or RPC JSON Serializer workarounds
  1. 处理器为必填项 -
    .handler()
    是过程中唯一必填的方法
  2. 推荐指定输出Schema - 显式指定
    .output()
    可提升TypeScript性能
  3. 中间件自动去重 - oRPC会自动去重前置中间件;如需手动去重,请使用上下文守卫
  4. 错误数据为公开内容 - 切勿在
    ORPCError.data
    中放入敏感信息
  5. 体解析器冲突 - 在oRPC中间件之后注册框架体解析器(Express、Fastify、Elysia)
  6. RPCHandler与OpenAPIHandler的区别 - RPCHandler使用专有协议(仅适用于RPCLink);OpenAPIHandler兼容REST/OpenAPI
  7. 懒加载路由 - 使用
    os.lazy(() => import('./module'))
    实现代码分割;使用独立的
    lazy()
    可加快类型推断
  8. SSE自动重连 - 标准SSE客户端会自动重连;使用
    lastEventId
    恢复流
  9. 文件限制 - 不支持分块/可恢复上传;AsyncIteratorObject不支持File/Blob
  10. React Native - Fetch API存在限制(不支持File/Blob、不支持事件迭代器);使用
    expo/fetch
    或RPC JSON序列化器解决

Handler Setup Pattern

处理器搭建模式

All adapters follow this pattern:
ts
import { RPCHandler } from '@orpc/server/fetch' // or /node, /fastify, etc.

const handler = new RPCHandler(router, {
  plugins: [new CORSPlugin()],
  interceptors: [onError((error) => console.error(error))],
})

// Handle request with prefix and context
const { matched, response } = await handler.handle(request, {
  prefix: '/rpc',
  context: {},
})
所有适配器均遵循以下模式:
ts
import { RPCHandler } from '@orpc/server/fetch' // or /node, /fastify, etc.

const handler = new RPCHandler(router, {
  plugins: [new CORSPlugin()],
  interceptors: [onError((error) => console.error(error))],
})

// Handle request with prefix and context
const { matched, response } = await handler.handle(request, {
  prefix: '/rpc',
  context: {},
})

Client Setup Pattern

客户端搭建模式

ts
import { RPCLink } from '@orpc/client/fetch'        // HTTP
import { RPCLink } from '@orpc/client/websocket'     // WebSocket
import { RPCLink } from '@orpc/client/message-port'  // Message Port
ts
import { RPCLink } from '@orpc/client/fetch'        // HTTP
import { RPCLink } from '@orpc/client/websocket'     // WebSocket
import { RPCLink } from '@orpc/client/message-port'  // Message Port

Common Errors

常见错误

Error CodeHTTP StatusWhen
BAD_REQUEST
400Input validation failure
UNAUTHORIZED
401Missing/invalid auth
FORBIDDEN
403Insufficient permissions
NOT_FOUND
404Resource not found
TIMEOUT
408Request timeout
TOO_MANY_REQUESTS
429Rate limited
INTERNAL_SERVER_ERROR
500Unhandled errors
Non-ORPCError exceptions are automatically converted to
INTERNAL_SERVER_ERROR
.
错误代码HTTP状态码触发场景
BAD_REQUEST
400输入验证失败
UNAUTHORIZED
401缺少/无效的身份验证
FORBIDDEN
403权限不足
NOT_FOUND
*404资源未找到
TIMEOUT
408请求超时
TOO_MANY_REQUESTS
429触发速率限制
INTERNAL_SERVER_ERROR
500未处理的错误
非ORPCError异常会自动转换为
INTERNAL_SERVER_ERROR

Reference Files

参考文件

  • API Reference - Procedures, routers, middleware, context, errors, metadata, event iterators, server actions, file handling
  • Adapters - All 20+ framework adapters with setup code (Next.js, Express, Hono, Fastify, WebSocket, Electron, etc.)
  • Plugins - All built-in plugins (CORS, batch, retry, compression, CSRF, validation, etc.)
  • OpenAPI - OpenAPI spec generation, handler, routing, input/output structure, Scalar UI, OpenAPILink
  • Integrations - TanStack Query, React SWR, Pinia Colada, Better Auth, AI SDK, Sentry, Pino, OpenTelemetry
  • Advanced - Testing, serialization, TypeScript best practices, publishing clients, body parsing, playgrounds, ecosystem
  • Contract-First - Contract-first development, tRPC migration guide, comparison with alternatives
  • Helpers - Cookie management, signing, encryption, rate limiting, publisher with event resume
  • NestJS - NestJS integration with decorators, dependency injection, contract-first
  • API参考 - 过程、路由、中间件、上下文、错误、元数据、事件迭代器、服务器操作、文件处理
  • 适配器 - 所有20+种框架适配器的搭建代码(Next.js、Express、Hono、Fastify、WebSocket、Electron等)
  • 插件 - 所有内置插件(CORS、批量处理、重试、压缩、CSRF、验证等)
  • OpenAPI - OpenAPI规范生成、处理器、路由、输入/输出结构、Scalar UI、OpenAPILink
  • 集成 - TanStack Query、React SWR、Pinia Colada、Better Auth、AI SDK、Sentry、Pino、OpenTelemetry
  • 进阶内容 - 测试、序列化、TypeScript最佳实践、客户端发布、体解析、调试工具、生态系统
  • 契约优先 - 契约优先开发、tRPC迁移指南、与替代方案的对比
  • 工具 - Cookie管理、签名、加密、速率限制、带恢复功能的事件发布器
  • NestJS - NestJS集成,支持装饰器、依赖注入、契约优先