Raypx

输入校验

使用 Zod Schema 校验 Procedure 输入。

oRPC 原生支持 Zod Schema 作为输入校验器。通过 .input() 方法链式调用,你可以在 Procedure 执行前验证输入数据,校验失败时自动返回错误。

基本用法

使用 z.object() 定义输入 Schema,通过 .input() 链接到 Procedure:

import { z } from "zod"
import { protectedProcedure } from "../index"

export const profileRouter = {
  update: protectedProcedure
    .input(
      z.object({
        defaultModel: z.string().optional(),
        preferredLocale: z.string().optional(),
        outputTone: z.string().optional(),
        marketingOptIn: z.boolean().optional(),
      }),
    )
    .handler(async ({ context, input }) => {
      // input 的类型自动推断为:
      // { defaultModel?: string; preferredLocale?: string; outputTone?: string; marketingOptIn?: boolean }
      const db = createDb()
      const userId = context.session.user.id

      const [existing] = await db
        .select()
        .from(profiles)
        .where(eq(profiles.userId, userId))
        .limit(1)

      if (existing) {
        const [updated] = await db
          .update(profiles)
          .set({ ...input, updatedAt: new Date() })
          .where(eq(profiles.userId, userId))
          .returning()
        return updated
      }

      const [created] = await db
        .insert(profiles)
        .values({ userId, ...input })
        .returning()
      return created
    }),
}

实际示例

必填参数

// 获取单个文档
get: protectedProcedure
  .input(z.object({ id: z.string().min(1) }))
  .handler(async ({ context, input }) => {
    const [doc] = await db
      .select()
      .from(documents)
      .where(and(eq(documents.id, input.id), eq(documents.userId, context.session.user.id)))
      .limit(1)
    if (!doc) throw new Error("Document not found")
    return doc
  }),

多个必填参数

// 发送消息
sendMessage: protectedProcedure
  .input(z.object({
    conversationId: z.string().min(1),
    content: z.string().min(1),
  }))
  .handler(async ({ context, input }) => {
    // input.conversationId: string
    // input.content: string
    const [userMsg] = await db
      .insert(messages)
      .values({
        conversationId: input.conversationId,
        userId: context.session.user.id,
        role: "user",
        content: input.content,
      })
      .returning()
    return userMsg
  }),

可选参数与默认值

// 创建对话
create: protectedProcedure
  .input(z.object({
    title: z.string().min(1),
    task: z.string().optional(),
  }))
  .handler(async ({ context, input }) => {
    const [convo] = await db
      .insert(conversations)
      .values({
        userId: context.session.user.id,
        title: input.title,
        task: input.task ?? "summarize",
      })
      .returning()
    return convo
  }),

错误处理

当输入校验失败时,oRPC 会自动返回校验错误。你也可以在 handler 中手动抛出错误:

ORPCError

oRPC 提供了 ORPCError 类用于抛出标准化的错误:

import { ORPCError } from "@orpc/server"

export const documentRouter = {
  get: protectedProcedure
    .input(z.object({ id: z.string().min(1) }))
    .handler(async ({ context, input }) => {
      const [doc] = await db
        .select()
        .from(documents)
        .where(and(eq(documents.id, input.id), eq(documents.userId, context.session.user.id)))
        .limit(1)
      if (!doc) {
        throw new ORPCError("NOT_FOUND", { message: "Document not found" })
      }
      return doc
    }),
}

全局错误拦截器

在 RPC 处理器级别配置全局错误拦截器,所有未捕获的错误都会被记录:

// src/routes/api/rpc/$.ts
const rpcHandler = new RPCHandler(appRouter, {
  interceptors: [
    onError((error) => {
      logger.error("rpc.error", { feature: "rpc", error: serializeError(error) })
    }),
  ],
})

类型推断

oRPC 的核心优势之一是类型自动传递。服务端定义的 Zod Schema 会自动推断出 TypeScript 类型,并通过 @orpc/serverRouterClient 类型传递到客户端。

// 服务端
export type AppRouter = typeof appRouter
export type AppRouterClient = RouterClient<typeof appRouter>
// 客户端——orpc.todos.create 的 input 类型自动推断
const mutation = orpc.todos.create.useMutation({
  // TypeScript 知道 input 需要 { title: string; task?: string }
})

你不需要在客户端重复定义接口类型或 Zod Schema。类型从服务端到客户端完全自动推导,实现了真正的端到端类型安全。

On this page