orpc
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseoRPC
oRPC
oRPC is a typesafe API framework: write plain TypeScript functions on the server, call them from clients like local functions. Input is validated at runtime, types flow end to end, and there is no code generation step. The same router can also be served as a REST API with an OpenAPI spec.
This skill targets oRPC v2. Check what is installed before writing code: (or any package). A 1.x version means v1, where this skill's guidance does not apply; use the skill to upgrade. v2 currently ships under the dist-tag (; a plain install silently gets v1). If shows at 2.x, the beta has ended: install normally.
npm ls @orpc/server@orpc/*orpc-migratebetanpm install @orpc/server@beta @orpc/client@betanpm view @orpc/server dist-tagslatestPretrained oRPC knowledge describes v1 and is often wrong for v2 (routing moved to , split into plus a path, automatic middleware dedupe was removed). Prefer retrieval: the index of every docs page is at https://orpc.dev/llms.txt; see Full documentation for the mechanics.
.meta(openapi(...))RPCLinkurloriginPackage map:
- : the
@orpc/serverbuilder, routers, middleware,os, server-side clients (RPCHandler,call),createRouterClientfor mocksimplement - :
@orpc/client,createORPCClient,RPCLink,safe,createSafeClientisInferableError - : contract-first API definitions implemented separately from their logic (see the
@orpc/contractskill)orpc-contract - :
@orpc/openapi,OpenAPIHandler, OpenAPI 3.1 spec generationOpenAPILink - Integration packages such as and
@orpc/tanstack-query@orpc/nest
oRPC是一个类型安全的API框架:在服务端编写普通的TypeScript函数,即可像调用本地函数一样从客户端调用它们。输入会在运行时验证,类型信息贯穿整个流程,且无需代码生成步骤。同一个路由还可以作为带有OpenAPI规范的REST API部署。
本技能针对oRPC v2版本。编写代码前请检查已安装的版本:(或任意包)。若为1.x版本则是v1,本技能的指导内容不适用;请使用技能进行升级。目前v2版本在分发标签下发布(执行安装;直接执行安装命令会默认获取v1版本)。若显示标签对应2.x版本,则测试版已结束,可正常安装。
npm ls @orpc/server@orpc/*orpc-migratebetanpm install @orpc/server@beta @orpc/client@betanpm view @orpc/server dist-tagslatest预训练的oRPC知识针对v1版本,通常不适用于v2(路由功能移至,将拆分为加路径,自动中间件去重功能已移除)。优先从文档获取信息:所有文档页面的索引位于https://orpc.dev/llms.txt;具体机制请查看[完整文档](#完整文档)。
.meta(openapi(...))RPCLinkurlorigin包映射:
- :包含
@orpc/server构建器、路由、中间件、os、服务端客户端(RPCHandler、call)、用于模拟的createRouterClientimplement - :包含
@orpc/client、createORPCClient、RPCLink、safe、createSafeClientisInferableError - :契约优先的API定义,与业务逻辑分离实现(详情请查看
@orpc/contract技能)orpc-contract - :包含
@orpc/openapi、OpenAPIHandler、OpenAPI 3.1规范生成功能OpenAPILink - 集成包如和
@orpc/tanstack-query@orpc/nest
Define procedures
定义过程
Build a procedure with the builder: describe input with a schema, implement with . Zod, Valibot, ArkType, and any other Standard Schema library work for , , and error .
os.handler.input.outputdatats
import { os } from '@orpc/server'
import * as z from 'zod'
export const listPlanets = os
.handler(async () => [{ id: 1, name: 'Earth' }]) // no .input: takes no arguments
export const findPlanet = os
.input(z.object({ id: z.number() }))
.handler(async ({ input }) => ({ id: input.id, name: 'Earth' })).handlerts
const example = os
.$context<{ headers: Headers }>() // initial context this procedure requires
.errors({ NOT_FOUND: {} }) // typed errors
.use(requireAuth) // middleware
.input(z.object({ id: z.number() }))
.output(z.object({ id: z.number(), name: z.string() })) // optional; also speeds up type checking
.handler(async ({ input, context, errors }) => ({ id: input.id, name: 'Earth' }))Every builder step returns a new instance, so share base builders freely: then build many procedures from .
const authed = os.use(requireAuth)authed使用构建器创建过程:通过Schema描述输入,通过实现逻辑。Zod、Valibot、ArkType以及其他任意Standard Schema库均可用于、和错误的验证。
os.handler.input.outputdatats
import { os } from '@orpc/server'
import * as z from 'zod'
export const listPlanets = os
.handler(async () => [{ id: 1, name: 'Earth' }]) // 无.input:不接收参数
export const findPlanet = os
.input(z.object({ id: z.number() }))
.handler(async ({ input }) => ({ id: input.id, name: 'Earth' })).handlerts
const example = os
.$context<{ headers: Headers }>() // 声明该过程所需的初始上下文
.errors({ NOT_FOUND: {} }) // 声明类型化错误
.use(requireAuth) // 使用中间件
.input(z.object({ id: z.number() }))
.output(z.object({ id: z.number(), name: z.string() })) // 可选;同时可加快类型检查速度
.handler(async ({ input, context, errors }) => ({ id: input.id, name: 'Earth' }))每个构建步骤都会返回新的实例,因此可以自由共享基础构建器:,然后基于创建多个过程。
const authed = os.use(requireAuth)authedAssemble a router
组装路由
A router is a plain object mapping keys to procedures (or nested routers). Do not use the keys , , , , .
thenbindvalueOftoStringtoJSONts
export const router = {
planet: { list: listPlanets, find: findPlanet },
admin: os.use(requireAuth).router({ deletePlanet }), // apply shared middleware to a subtree
planetLazy: os.lazy(() => import('./planet')), // code-split; module's default export is a router
}Infer types with / from . Applying at both router and procedure level can run the same middleware twice; see the dedupe pattern below.
InferRouterInputsInferRouterOutputs@orpc/server.use路由是一个普通对象,将键映射到过程(或嵌套路由)。请勿使用、、、、作为键名。
thenbindvalueOftoStringtoJSONts
export const router = {
planet: { list: listPlanets, find: findPlanet },
admin: os.use(requireAuth).router({ deletePlanet }), // 为子路由应用共享中间件
planetLazy: os.lazy(() => import('./planet')), // 代码拆分;模块的默认导出为路由
}使用中的/推断类型。同时在路由和过程级别调用会导致同一中间件执行两次;请查看下方的去重模式。
@orpc/serverInferRouterInputsInferRouterOutputs.useMiddleware and context
中间件与上下文
Context comes from two places. Initial context is declared with and passed explicitly when serving or calling (environment values: headers, env, db). Injected context is added at runtime by middleware via (runtime values: the authenticated user).
.$contextnext({ context })ts
import { ORPCError, os } from '@orpc/server'
const base = os.$context<{ headers: Headers }>()
const requireAuth = base.middleware(async ({ context, next }) => {
const user = await parseUser(context.headers)
if (!user) {
throw new ORPCError('UNAUTHORIZED')
}
return next({ context: { user } }) // handler now sees context.user, typed non-null
}).use.inputos.middleware(async ({ next }, id: number) => ...).use(mw.adaptInput(input => input.id))Best practice, dedupe expensive middleware: the same middleware can run twice in one call (router-level plus procedure-level , or a procedure ing another). Cache the result in context:
.usecallts
const authProvider = os
.$context<{ headers: Headers, auth?: { id: string }, authLoaded?: boolean }>()
.middleware(async ({ context, next }) => {
const auth = context.authLoaded ? context.auth : await loadAuth(context.headers)
return next({ context: { auth, authLoaded: true } })
})上下文来自两个来源。初始上下文通过声明,并在部署或调用时显式传入(环境值:请求头、环境变量、数据库)。注入上下文由中间件通过在运行时添加(运行时值:已认证用户)。
.$contextnext({ context })ts
import { ORPCError, os } from '@orpc/server'
const base = os.$context<{ headers: Headers }>()
const requireAuth = base.middleware(async ({ context, next }) => {
const user = await parseUser(context.headers)
if (!user) {
throw new ORPCError('UNAUTHORIZED')
}
return next({ context: { user } }) // 处理器现在可以看到类型非空的context.user
}).use.inputos.middleware(async ({ next }, id: number) => ...).use(mw.adaptInput(input => input.id))最佳实践:对开销较大的中间件进行去重:同一中间件可能在一次调用中执行两次(路由级别加过程级别,或一个过程调用另一个过程)。将结果缓存在上下文中:
.usets
const authProvider = os
.$context<{ headers: Headers, auth?: { id: string }, authLoaded?: boolean }>()
.middleware(async ({ context, next }) => {
const auth = context.authLoaded ? context.auth : await loadAuth(context.headers)
return next({ context: { auth, authLoaded: true } })
})Typesafe errors
类型安全错误
Throw (a plus optional and ). Both and are sent to the client, so never put secrets in them. Throw only instances, never literals.
ORPCErrorcodemessagedatamessagedataErrorDefine errors with so clients can infer each error's shape:
.errorsts
const find = os
.errors({
NOT_FOUND: { message: 'Planet not found' }, // default message
RATE_LIMITED: { data: z.object({ retryAfter: z.number() }) },
})
.handler(async ({ input, errors }) => {
throw errors.NOT_FOUND()
})throw new ORPCError('NOT_FOUND')ORPCErrortry/catch抛出(包含以及可选的和)。和都会发送给客户端,因此切勿在其中包含敏感信息。仅抛出实例,切勿抛出字面量。
ORPCErrorcodemessagedatamessagedataError通过定义错误,以便客户端推断每个错误的形状:
.errorsts
const find = os
.errors({
NOT_FOUND: { message: 'Planet not found' }, // 默认消息
RATE_LIMITED: { data: z.object({ retryAfter: z.number() }) },
})
.handler(async ({ input, errors }) => {
throw errors.NOT_FOUND()
})在该处理器中抛出会在代码和数据匹配时转换为对应的类型化错误。在中间件的中将自定义错误类转换为。
throw new ORPCError('NOT_FOUND')try/catchORPCErrorServe with RPCHandler
使用RPCHandler部署服务
RPCHandlerts
import { onError } from '@orpc/server'
import { RPCHandler } from '@orpc/server/fetch'
import { CORSHandlerPlugin } from '@orpc/server/plugins'
const handler = new RPCHandler(router, {
plugins: [new CORSHandlerPlugin()],
interceptors: [onError(error => console.error(error))],
})
export async function fetch(request: Request): Promise<Response> {
const { matched, response } = await handler.handle(request, {
prefix: '/rpc',
context: { headers: request.headers }, // provide the router's initial context here
})
return matched ? response : new Response('Not found', { status: 404 })
}
// Bun.serve({ fetch }) / Deno.serve(fetch) / export default { fetch } on WorkersNode HTTP:
ts
import { createServer } from 'node:http'
import { RPCHandler } from '@orpc/server/node'
const handler = new RPCHandler(router)
const server = createServer(async (req, res) => {
const { matched } = await handler.handle(req, res, { prefix: '/rpc', context: {} })
if (matched)
return
res.statusCode = 404
res.end('Not found')
})
server.listen(3000)Unmatched requests fall through to your own handling. By default accepts only , , , and ; enabling via is a CSRF risk with cookie auth, see RPC Handler. Handler options also include , , , , , and . Adapters also exist for AWS Lambda, Fastify, WebSocket, Message Port, and Expo.
RPCHandlerPOSTPUTPATCHDELETEGETallowMethodsinterceptorsroutingInterceptorsclientInterceptorspluginsfiltererrorStatusMapRPCHandlerts
import { onError } from '@orpc/server'
import { RPCHandler } from '@orpc/server/fetch'
import { CORSHandlerPlugin } from '@orpc/server/plugins'
const handler = new RPCHandler(router, {
plugins: [new CORSHandlerPlugin()],
interceptors: [onError(error => console.error(error))],
})
export async function fetch(request: Request): Promise<Response> {
const { matched, response } = await handler.handle(request, {
prefix: '/rpc',
context: { headers: request.headers }, // 在此提供路由的初始上下文
})
return matched ? response : new Response('Not found', { status: 404 })
}
// 在Bun中执行Bun.serve({ fetch }) / Deno中执行Deno.serve(fetch) / 在Workers中导出default { fetch }Node HTTP:
ts
import { createServer } from 'node:http'
import { RPCHandler } from '@orpc/server/node'
const handler = new RPCHandler(router)
const server = createServer(async (req, res) => {
const { matched } = await handler.handle(req, res, { prefix: '/rpc', context: {} })
if (matched)
return
res.statusCode = 404
res.end('Not found')
})
server.listen(3000)未匹配的请求将交由你自行处理。默认情况下,仅接受、、和请求;通过启用请求在使用Cookie认证时存在CSRF风险,详情请查看RPC Handler。处理器选项还包括、、、、和。还提供了适配AWS Lambda、Fastify、WebSocket、Message Port和Expo的适配器。
RPCHandlerPOSTPUTPATCHDELETEallowMethodsGETinterceptorsroutingInterceptorsclientInterceptorspluginsfiltererrorStatusMapCall procedures
调用过程
Server side (same process, no HTTP; also the fastest way to test procedures):
ts
import { call, createRouterClient } from '@orpc/server'
const planet = await call(findPlanet, { id: 1 }, { context: { headers } })
const client = createRouterClient(router, { context: { headers } }) // context can be a function
const planets = await client.planet.list()Client side, turns calls into HTTP requests. Import the router as a type only so no server code reaches the client bundle:
RPCLinkts
import type { RouterClient } from '@orpc/server'
import type { router } from '../server/router'
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
const link = new RPCLink({
origin: 'http://127.0.0.1:3000',
url: '/rpc', // must match the server's prefix
headers: () => ({ authorization: `Bearer ${token}` }), // options accept functions
})
export const orpc: RouterClient<typeof router> = createORPCClient(link)
const planet = await orpc.planet.find({ id: 1 })Client error handling: plain works, but preserves typed error inference:
try/catchsafets
import { createSafeClient, isInferableError, safe } from '@orpc/client'
const [error, data] = await safe(orpc.planet.find({ id: 1 }))
if (isInferableError(error)) {
console.log(error.code, error.data) // typed from the procedure's .errors
}
else if (error) {
// unknown error
}
const safeClient = createSafeClient(orpc) // every call returns [error, data]服务端(同一进程,无需HTTP;也是测试过程的最快方式):
ts
import { call, createRouterClient } from '@orpc/server'
const planet = await call(findPlanet, { id: 1 }, { context: { headers } })
const client = createRouterClient(router, { context: { headers } }) // 上下文可以是函数
const planets = await client.planet.list()客户端:将调用转换为HTTP请求。仅将路由作为类型导入,避免服务端代码进入客户端包:
RPCLinkts
import type { RouterClient } from '@orpc/server'
import type { router } from '../server/router'
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
const link = new RPCLink({
origin: 'http://127.0.0.1:3000',
url: '/rpc', // 必须与服务端的prefix匹配
headers: () => ({ authorization: `Bearer ${token}` }), // 选项接受函数
})
export const orpc: RouterClient<typeof router> = createORPCClient(link)
const planet = await orpc.planet.find({ id: 1 })客户端错误处理:普通的即可生效,但方法可保留类型化错误推断:
try/catchsafets
import { createSafeClient, isInferableError, safe } from '@orpc/client'
const [error, data] = await safe(orpc.planet.find({ id: 1 }))
if (isInferableError(error)) {
console.log(error.code, error.data) // 类型来自过程的.errors定义
}
else if (error) {
// 未知错误
}
const safeClient = createSafeClient(orpc) // 每次调用都会返回[error, data]Beyond the basics
进阶内容
- Streaming / SSE: return an async generator from , validate events with
.handler, resume viaasyncIteratorObject: AsyncIteratorObjectlastEventId - OpenAPI: serve the same router as REST with routing metadata plus , and generate a spec (covered in depth by the
OpenAPIHandlerskill): OpenAPI Handlerorpc-openapi - Contract-first: define contracts with , implement with
@orpc/contract(covered in depth by theimplementskill): Contractsorpc-contract - Plugins for handler and link: batch, CORS, dedupe, retry, compression, request limits, smart coercion, static files, timeout, tmp file upload, and more. Fetch a plugin's docs page before configuring it; option names are not guessable: Plugins
- Integrations: TanStack Query (), SWR, Pinia Colada, Next.js, NestJS, AI SDK, OpenTelemetry
createTanstackQueryUtils - Testing: procedures directly; mock with
call, and run the project's typecheck before declaring success, since end-to-end types are oRPC's first correctness signal: Testing and Mockingimplement(router.planet.list).handler(() => []) - Monorepos: TypeScript project references keep client types resolvable: Monorepo Setup
- Migrating from tRPC or oRPC v1: use the skill
orpc-migrate
- 流式传输/SSE:从返回异步生成器,使用
.handler验证事件,通过asyncIteratorObject恢复:AsyncIteratorObjectlastEventId - OpenAPI:结合路由元数据与,将同一路由作为REST API部署并生成规范(详情请查看
OpenAPIHandler技能):OpenAPI Handlerorpc-openapi - 契约优先:使用定义契约,通过
@orpc/contract实现(详情请查看implement技能):Contractsorpc-contract - 处理器与链接插件:批量处理、CORS、去重、重试、压缩、请求限制、智能类型转换、静态文件、超时、临时文件上传等。配置插件前请查看其文档页面;选项名称无法通过猜测获得:Plugins
- 集成:TanStack Query()、SWR、Pinia Colada、Next.js、NestJS、AI SDK、OpenTelemetry
createTanstackQueryUtils - 测试:直接使用调用过程;使用
call进行模拟,并在完成前运行项目的类型检查,因为端到端类型是oRPC的首要正确性信号:Testing and Mockingimplement(router.planet.list).handler(() => []) - 单仓库:TypeScript项目引用可确保客户端类型可解析:Monorepo Setup
- 从tRPC或oRPC v1迁移:使用技能
orpc-migrate
Full documentation
完整文档
This skill is an overview; fetch exact docs instead of guessing APIs, and if this skill and a fetched page disagree, trust the page. The docs are served at https://orpc.dev (the v1 docs stay at https://v1.orpc.dev):
- https://orpc.dev/llms.txt : index of every page with descriptions
- https://orpc.dev/llms-full.txt : the entire docs in one file (large; prefer single pages)
- Append to any docs URL for that page's exact source markdown (for example https://orpc.dev/docs/procedure.md)
.md
Doc map, all under :
https://orpc.dev/docs/- Top level: ,
procedure,router,middleware,context,error-handling, plusmetadata(file uploads) andbinary-data(streaming/SSE)async-iterator-object - ,
rpc/*: protocol details, handlers, links;openapi/*: contract-first (thecontract/*skill)orpc-contract - : server- and client-side clients, error handling,
client/*DynamicLink - : per-runtime serving quirks (fetch-api, node-http, aws-lambda, fastify, websocket, message-port, expo)
adapters/* - : twenty handler/link plugins;
plugins/*: cookie, encryption, form-data, publisher, ratelimit, signing, base64urlhelpers/* - : framework glue;
integrations/*: guidance (testing, SSR, monorepos, validation)recipes/* - ,
migrations/from-v1: upgrades (use themigrations/from-trpcskill)orpc-migrate
本技能仅为概述;请从文档获取准确的API信息,若本技能与文档内容不一致,请以文档为准。文档托管于https://orpc.dev(v1文档保留在https://v1.orpc.dev):
- https://orpc.dev/llms.txt :所有页面的索引及描述
- https://orpc.dev/llms-full.txt :完整文档汇总为单个文件(文件较大;优先查看单个页面)
- 在任意文档URL后添加即可获取该页面的原始Markdown源码(例如https://orpc.dev/docs/procedure.md)
.md
文档映射,均位于路径下:
https://orpc.dev/docs/- 顶层:、
procedure、router、middleware、context、error-handling,以及metadata(文件上传)和binary-data(流式传输/SSE)async-iterator-object - 、
rpc/*:协议细节、处理器、链接;openapi/*:契约优先(contract/*技能)orpc-contract - :服务端与客户端、错误处理、
client/*DynamicLink - :各运行时的部署细节(fetch-api、node-http、aws-lambda、fastify、websocket、message-port、expo)
adapters/* - :20个处理器/链接插件;
plugins/*:Cookie、加密、表单数据、发布器、限流、签名、base64urlhelpers/* - :框架集成;
integrations/*:实践指南(测试、SSR、单仓库、验证)recipes/* - 、
migrations/from-v1:升级指南(使用migrations/from-trpc技能)orpc-migrate