tRPC全栈类型安全实战
tRPC全栈类型安全实战:告别API类型地狱,TypeScript前后端零成本类型共享摘要:在全栈TypeScript项目中,前后端类型不同步是最常见的Bug来源之一。tRPC通过编译时类型推导,实现了端到端的类型安全——前端调用后端API就像调用本地函数一样,类型自动推导、错误提前暴露。本文从原理到生产级实战,带你掌握tRPC的核心用法。痛点:传统API开发的类型地狱在传统全栈开发中,前后端的类型同步是一个巨大的痛点:// ❌ 传统方式:手动维护类型,容易不同步// 后端 APIapp.get('/api/users/:id',async(req,res)={constuser=awaitdb.user.findUnique({where:{id:req.params.id}});// 返回的数据结构可能随时变化res.json({user:{id:user.id,name:user.name,email:user.email}});});// 前端 —— 需要手动定义类型interfaceUser{id:string;name:string;email:string;}// 如果后端返回了新字段 emailVerified,前端不知道// 如果后端删除了 email 字段,前端编译不会报错,运行时才会崩溃const{user}=awaitfetch('/api/users/123').then(r=r.json());console.log(user.name);// 💥 后端改了字段名?运行时才知道这种开发模式的问题:类型不一致:后端改了接口,前端不知道重复定义:前后端各维护一套类型运行时错误:类型错误只有在调用时才能发现文档过时:Swagger/OpenAPI 文档经常与实际不一致tRPC 核心原理tRPC 的核心思想很简单:前后端共享同一个TypeScript项目,通过类型推导实现端到端类型安全。前端调用 tRPC 传输层 后端处理 ───────── ─────────── ───────── user.getById({id: "123"}) → HTTP/WS → procedure(input) → return data ↑ ↑ 自动序列化 自动类型检查 自动反序列化 自动输入验证关键特性:零代码生成:不需要 Swagger、GraphQL Codegen类型推导:前端自动获得后端返回值类型输入验证:使用 Zod 等库在运行时验证输入端到端类型安全:从前端到数据库,类型链路不断实战:构建全栈应用Step 1: 项目初始化# 使用 create-t3-app 脚手架(推荐)npx create-t3-app@latest my-trpc-appcdmy-trpc-app# 或者手动初始化mkdirtrpc-democdtrpc-demonpminit-ynpminstall@trpc/server @trpc/client @trpc/react-query @trpc/next zodnpminstall@tanstack/react-query react react-domnpminstall-Dtypescript @types/react @types/nodeStep 2: 定义 tRPC Router(后端)// server/trpc.tsimport{initTRPC,TRPCError}from'@trpc/server';import{typeCreateNextContextOptions}from'@trpc/server/adapters/next';importsuperjsonfrom'superjson';import{ZodError}from'zod';import{prisma}from'./db';// Context —— 每个请求的上下文exportconstcreateTRPCContext=(opts:CreateNextContextOptions)={const{req,res}=opts;return{prisma,req,res,user:getUserFromToken(req.headers.authorization),};};typeContext=AwaitedReturnTypetypeofcreateTRPCContext;// 初始化 tRPCconstt=initTRPC.contextContext().create({transformer:superjson,// 支持 Date、Map 等复杂类型errorFormatter({shape,error}){return{...shape,data:{...shape.data,zodError:error.causeinstanceofZodError?error.cause.flatten():null,},};},});// 导出基础组件exportconstcreateCallerFactory=t.createCallerFactory;exportconstrouter=t.router;exportconstpublicProcedure=t.procedure;// 鉴权中间件constenforceAuth=t.middleware(({ctx,next})={if(!ctx.user){thrownewTRPCError({code:'UNAUTHORIZED'});}returnnext({ctx:{user:ctx.user,// 类型收窄:后续 procedure 中 user 一定存在},});});exportconstprotectedProcedure=t.procedure.use(enforceAuth);Step 3: 定义业务 Router// server/routers/user.tsimport{z}from'zod';import{router,publicProcedure,protectedProcedure}from'../trpc';exportconstuserRouter=router({// 查询用户getById:publicProcedure.input(z.object({id:z.string().uuid()})).query(async({input,ctx})={constuser=awaitctx.prisma.user.findUnique({where:{id:input.id},select:{id:true,name:true,email:true,avatar:true,createdAt:true,},});if(!user){thrownewTRPCError({code:'NOT_FOUND',message:`User${input.id}not found`,});}returnuser;// 类型自动推导!}),// 列表查询(带分页和筛选)list:publicProcedure.input(z.object({page:z.number().min(1).default(1),pageSize:z.number().min(1).max(100).default(20),search:z.string().optional(),sortBy:z.enum(['name','createdAt']).default('createdAt'),sortOrder:z.enum([/