← 中文资源

简体中文版本

TRPC 过程输入校验

用 tRPC 加 Zod 定义类型安全的远程过程:输入 schema 即校验器,导出的路由类型让客户端全程推断输入输出,零代码生成。

阅读英文原文

示例说明

传统 RPC 的类型契约靠 proto 文件或 OpenAPI 中转,前后端各自维护易漂移。tRPC 直接把服务端路由的 TypeScript 类型导出给客户端:input 的 Zod schema 既是运行时校验器也是类型来源,调用参数不符在客户端编译期就报错,服务端校验兜底防伪造请求,一层定义两层防护。

技术要点

  • Zod schema 同时承担校验与类型推断
  • router 类型导出驱动客户端智能提示
  • query 与 mutation 语义分流

代码解读

三个过程覆盖读查、分页列表与创建:byId 要求 UUID;list 的分页参数带默认值与上下限约束;create 校验标题与正文的长度域。context 泛型让 ctx.db 在过程内获得类型,AppRouter 类型是客户端 createTRPCReact 的输入。

使用建议

公开过程仍要按用户身份过滤数据,校验不等于授权。错误信息默认会跨端传递,注意脱敏。拆分子路由组合成根路由,保持文件可维护。

代码示例

import { initTRPC } from '@trpc/server';
import { z } from 'zod';
import { createHTTPContext } from './context';

const t = initTRPC.context<typeof createHTTPContext>().create();

const publicProcedure = t.procedure;
const router = t.router;

const postRouter = router({
  byId: publicProcedure
    .input(z.object({ id: z.string().uuid() }))
    .query(async ({ input, ctx }) => {
      return ctx.db.posts.find((p) => p.id === input.id) ?? null;
    }),

  list: publicProcedure
    .input(z.object({
      page: z.number().int().min(1).default(1),
      limit: z.number().int().min(1).max(50).default(20),
    }))
    .query(async ({ input, ctx }) => {
      const start = (input.page - 1) * input.limit;
      return ctx.db.posts.slice(start, start + input.limit);
    }),

  create: publicProcedure
    .input(z.object({
      title: z.string().min(3).max(120),
      body: z.string().min(1),
    }))
    .mutation(async ({ input, ctx }) => {
      const post = { id: crypto.randomUUID(), ...input };
      ctx.db.posts.push(post);
      return post;
    }),
});

export const appRouter = router({ posts: postRouter });
export type AppRouter = typeof appRouter;