
开发工具CLI代码生成【免费下载链接】create-t3-appThe best way to start a full-stack, typesafe Next.js app项目地址https://gitcode.com/gh_mirrors/cr/create-t3-app点击查看免费下载导读tRPC 允许开发者在不进行任何代码生成、不引入运行时负担的前提下编写端到端end-to-end类型安全的 API。它借助 TypeScript 强大的类型推断能力从后端 API 路由器的类型定义直接推导出前端调用时的类型使前端以完全类型安全 自动补全的方式调用后端过程procedure。本指南基于 create-t3-app 官方文档日语版原文并结合仓库内真实模板源码系统讲解 tRPC 在 T3 Stack 中的完整用法从过程/路由器的编写、前端类型安全的调用到 Zod 错误推断、生成文件逐一拆解、对外暴露 API 的三种方式、与 Next.js API 路由的对比以及 CORS、乐观更新、集成测试等实战片段。tRPC 的核心用法tRPC 的理念是在后端用 TypeScript 编写函数然后在前端直接调用这些函数。一个简单的 tRPC 过程procedure等价于传统后端中的路由处理器长这样const userRouter createTRPCRouter({ getById: publicProcedure.input(z.string()).query(({ ctx, input }) { return ctx.prisma.user.findFirst({ where: { id: input, }, }); }), });这个过程的执行链是输入校验先用 Zod 对输入做校验z.string()确保输入是字符串。Zod 与 环境变量校验 用的是同一个校验库。如果输入不是字符串tRPC 会返回信息丰富的错误响应而不是让后端代码崩溃。解析器resolver在输入之后链式连接一个解析器函数它可以是query、mutation或subscription三者之一。示例中的解析器使用 Prisma 客户端查询数据库参见 Prisma 用法返回id匹配的用户对象。将子路由器合并为 appRouter你可以在routers中定义一组共享命名空间的相关过程。例如可以有users路由器、posts路由器、messages路由器然后将它们合并进一个单一、集中的appRouterconst appRouter createTRPCRouter({ users: userRouter, posts: postRouter, messages: messageRouter, }); export type AppRouter typeof appRouter;注意只需要导出路由器的类型定义意味着客户端永远不需要导入任何服务端代码——这是 tRPC 保持零运行时负担的关键设计。在仓库的实际模板中cli/template/extras/src/server/api/root.ts 正是这样做的它把postRouter合并进appRouter导出AppRouter类型并通过createCallerFactory额外导出了一个服务端调用器createCaller供 RSC 与集成测试使用。前端调用类型安全的自动补全tRPC 为tanstack/react-query提供了一层包装让你在享受 TanStack Query 全部 Hooks 能力的同时获得 API 调用的类型推断。前端调用过程的方式如下import { useRouter } from next/router; import { api } from ../../utils/api; const UserPage () { const { query } useRouter(); const userQuery api.users.getById.useQuery(query.id); return ( div h1{userQuery.data?.name}/h1 /div ); };当你输入api.的那一刻自动补全会立刻列出所有路由器选中路由器后该路由器的全部过程也会随之出现。如果你的输入与后端定义的校验器不一致TypeScript 会直接报错——这就是端到端类型安全的直观体验。推断错误让 Zod 校验错误在前端可见默认情况下create-t3-app 会配置一个 错误格式化器使后端发生校验错误时前端能够推断出具体的 Zod 错误。这一机制的底层实现位于 cli/template/extras/src/server/api/trpc-app/base.ts初始化 tRPC 时传入errorFormatter当error.cause是ZodError实例时把error.cause.flatten()的结果挂到响应的data.zodError字段上const t initTRPC.contexttypeof createTRPCContext().create({ transformer: superjson, errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, }; }, });前端用法示例function MyComponent() { const { mutate, error } api.post.create.useMutation(); return ( form onSubmit{(e) { e.preventDefault(); const formData new FormData(e.currentTarget); mutate({ title: formData.get(title) }); }} input nametitle / {error?.data?.zodError?.fieldErrors.title ( {/** mutate returned with an error on the title */} span classNamemb-8 text-red-500 {error.data.zodError.fieldErrors.title} /span )} ... /form ); }注意error?.data?.zodError?.fieldErrors.title的完整类型推断链fieldErrors是 Zodflatten()的输出结构title对应表单输入字段名。这意味着你可以在不写任何字符串类型断言的情况下把后端校验错误精确渲染到对应字段上。生成文件逐一拆解tRPC 需要不少样板代码create-t3-app会为你全部生成好。以下是这些文件的职责说明结合仓库cli/template/extras/下的真实模板。pages/api/trpc/[trpc].tsPages Router与app/api/trpc/[trpc]/route.tsApp Router这是 API 的入口点负责对外暴露 tRPC 路由器。通常情况下你几乎不会改动这个文件但如果需要启用 CORS 中间件之类的能力知道下面的机制会很有用导出的createNextApiHandler本质上是一个 Next.js API 处理器接收request和response对象因此你可以在外面包任意中间件CORS 示例见下文。Pages Router 版本见 cli/template/extras/src/pages/api/trpc/[trpc].ts使用trpc/server/adapters/next的createNextApiHandler并在onError回调中于开发环境打印❌ tRPC failed on ...日志。App Router 版本见 cli/template/extras/src/app/api/trpc/[trpc]/route.ts使用trpc/server/adapters/fetch的fetchRequestHandler并分别导出GET与POSTendpoint固定为/api/trpccreateContext从req.headers构建上下文。server/api/trpc.ts这个文件分为两部分上下文context创建与tRPC 初始化。1. 上下文创建。上下文是所有 tRPC 过程都能访问的数据非常适合存放数据库连接、认证信息等。create-t3-app 使用两个函数以便在没有 request 对象时也能使用上下文的一部分createInnerTRPCContext定义不依赖请求的上下文例如数据库连接。它可被用于 集成测试 或 ssg-helpers服务端预取这类没有请求对象的场景。Pages Router 模板cli/template/extras/src/server/api/trpc-pages/base.ts保留了这种双层结构。createTRPCContext定义依赖请求的上下文例如用户会话。它通过opts.req或 App Router 下的headers获取会话再把它传入createInnerTRPCContext生成最终上下文。在启用认证时cli/template/extras/src/server/api/trpc-app/with-auth.tscreateTRPCContext会调用auth()获取session并放入上下文。2. tRPC 初始化。初始化 tRPC 并定义可复用的过程和中间件。按惯例不应导出整个t对象而是创建可复用的过程和中间件再导出。仓库模板导出了createTRPCRouter、createCallerFactory、publicProcedure以及启用认证时的protectedProcedure。此外还有两个值得注意的内建设施superjson数据转换器作为 数据转换器 使用保证数据类型到达客户端时被保留——例如发送Date对象客户端拿到的仍是Date而非字符串大多数 API 做不到这一点。timingMiddleware统计每个过程的执行耗时并打印[TRPC] ${path} took ...ms to execute在开发模式下还会人为加入 100–500ms 的随机延迟用于提前暴露生产环境中才会出现的水合瀑布请求问题源码位置。启用认证后还会多出protectedProcedure它在timingMiddleware之后追加一个校验中间件若ctx.session?.user不存在则抛出TRPCError({ code: UNAUTHORIZED })否则把session推断为非空并继续cli/template/extras/src/server/api/trpc-app/with-auth.ts。server/api/routers/*.ts在这里定义 API 的路由与过程。按惯例为相关的一组过程创建独立的路由器。仓库自带的示例 cli/template/extras/src/server/api/routers/post/with-auth.ts 展示了三种典型过程export const postRouter createTRPCRouter({ hello: publicProcedure .input(z.object({ text: z.string() })) .query(({ input }) ({ greeting: Hello ${input.text} })), create: protectedProcedure .input(z.object({ name: z.string().min(1) })) .mutation(async ({ input }) { /* ... */ }), getLatest: protectedProcedure.query(() post), });其中publicProcedure不做登录校验protectedProcedure则只允许已登录用户调用——选择何种过程本质上是选择是否需要认证这道中间件。server/api/root.ts在这里把routers/**中定义的所有子路由器合并为单个 app 路由器见前文appRouter示例并导出AppRouter类型与createCaller。utils/api.tsApp Router 下拆分为trpc/目录这是 tRPC 的前端入口点。在这里导入路由器的类型定义创建 tRPC 客户端与 react-query Hooks。由于后端启用了superjson数据转换器前端也必须启用——这样后端序列化的数据才能在前端被正确反序列化。同时在这里定义 tRPC 的链接links默认使用httpBatchStreamLink启用请求批处理把同一 tick 内的多个查询合并为一个请求并配合SuperJSON转换器URL 指向getBaseUrl() /api/trpc同时附带x-trpc-source: nextjs-react请求头loggerLink在开发环境输出有用的请求日志process.env.NODE_ENV development时启用。模板还导出了RouterInputs/RouterOutputs两个推断辅助类型export type RouterInputs inferRouterInputsAppRouter; export type RouterOutputs inferRouterOutputsAppRouter; // example type HelloInput RouterInputs[example][hello]配套的 cli/template/extras/src/trpc/query-client.ts 统一配置了 TanStack QueryClient默认staleTime: 30 * 1000SSR 下避免客户端立即重复请求、dehydrate.serializeData使用SuperJSON.serialize等。在 App Router 场景下cli/template/extras/src/trpc/server.ts 还通过createHydrationHelpers导出了供 React Server Component 使用的api与HydrateClient并用cache()复用上下文与 QueryClient。如何从外部调用 API普通 API 可以用curl、Postman、fetch等任意 HTTP 客户端甚至浏览器直接调用。tRPC 则略有不同如果不想用 tRPC 客户端调用过程官方推荐以下方式。对外暴露单个过程只暴露单个过程时使用服务端调用server side calls创建一个普通的 Next.js API 端点但复用 tRPC 过程的解析器部分。import { type NextApiRequest, type NextApiResponse } from next; import { appRouter, createCaller } from ../../../server/api/root; import { createTRPCContext } from ../../../server/api/trpc; const userByIdHandler async (req: NextApiRequest, res: NextApiResponse) { // Create context and caller const ctx await createTRPCContext({ req, res }); const caller createCaller(ctx); try { const { id } req.query; const user await caller.user.getById(id); res.status(200).json(user); } catch (cause) { if (cause instanceof TRPCError) { // An error from tRPC occurred const httpCode getHTTPStatusCodeFromError(cause); return res.status(httpCode).json(cause); } // Another error occurred console.error(cause); res.status(500).json({ message: Internal server error }); } }; export default userByIdHandler;这段代码之所以可行正是因为 root.ts 中createCaller createCallerFactory(appRouter)导出了createCaller。注意错误处理的分支tRPC 抛出的错误通过getHTTPStatusCodeFromError映射为 HTTP 状态码其他未知错误则返回 500。把每个过程都暴露为 REST 端点如果需要把所有过程对外暴露可以关注社区插件 trpc-openapi通过给过程添加一些特殊元数据就能从 tRPC 路由器生成 OpenAPI 兼容的 REST API。直接当作 HTTP 请求调用tRPC 本身通过 HTTP 通信因此理论上也可以用普通 HTTP 请求调用过程。不过由于 tRPC 使用的 RPC 协议语法会相当繁琐。如果好奇可以打开浏览器开发者工具的 Network 面板观察 tRPC 请求与响应的实际形态——但这只建议作为学习练习生产环境请优先采用上述两种方案。与 Next.js API 端点的对比假设要从数据库获取用户对象并返回给前端。用 Next.js API 端点是这样写的import { type NextApiRequest, type NextApiResponse } from next; import { prisma } from ../../../server/db; const userByIdHandler async (req: NextApiRequest, res: NextApiResponse) { if (req.method ! GET) { return res.status(405).end(); } const { id } req.query; if (!id || typeof id ! string) { return res.status(400).json({ error: Invalid id }); } const examples await prisma.example.findFirst({ where: { id, }, }); res.status(200).json(examples); }; export default userByIdHandler;import { useState, useEffect } from react; import { useRouter } from next/router; const UserPage () { const router useRouter(); const { id } router.query; const [user, setUser] useState(null); useEffect(() { fetch(/api/user/${id}) .then((res) res.json()) .then((data) setUser(data)); }, [id]); };把它与前文的 tRPC 例子对比tRPC 的优势显而易见不需要为每个路由手写 url 字符串移动代码时容易引发难以排查的失效整个路由器就是一个带自动补全的对象不需要手动校验请求使用了哪种 HTTP 方法不需要在过程里手动校验请求 query 或 body 的数据是否正确——Zod 已经替你完成不需要手工构造响应对象可以像写普通 TypeScript 函数一样抛错误、返回值或对象前端调用过程自带自动补全和类型安全。实用代码片段启用 CORS当需要从不同域名消费 API 时例如包含 React Native 应用的 monorepo可能需要启用 CORS。由于createNextApiHandler本质是一个接收 request/response 的 Next.js API 处理器可以直接在外面包一层import { type NextApiRequest, type NextApiResponse } from next; import { createNextApiHandler } from trpc/server/adapters/next; import { appRouter } from ~/server/api/root; import { createTRPCContext } from ~/server/api/trpc; import cors from nextjs-cors; const handler async (req: NextApiRequest, res: NextApiResponse) { // Enable cors await cors(req, res); // Create and call the tRPC handler return createNextApiHandler({ router: appRouter, createContext: createTRPCContext, })(req, res); }; export default handler;乐观更新Optimistic Updates乐观更新指在 API 调用完成之前就更新 UI用户不必等待请求结束即可看到操作结果体验更好。但对数据正确性要求很高的应用应谨慎使用因为此时 UI 并不真实反映后端状态。更多细节可参考 React Query 官方文档。const MyComponent () { const listPostQuery api.post.list.useQuery(); const utils api.useUtils(); const postCreate api.post.create.useMutation({ async onMutate(newPost) { // Cancel outgoing fetches (so they dont overwrite our optimistic update) await utils.post.list.cancel(); // Get the data from the queryCache const prevData utils.post.list.getData(); // Optimistically update the data with our new post utils.post.list.setData(undefined, (old) [...old, newPost]); // Return the previous data so we can revert if something goes wrong return { prevData }; }, onError(err, newPost, ctx) { // If the mutation fails, use the context-value from onMutate utils.post.list.setData(undefined, ctx.prevData); }, onSettled() { // Sync with server once mutation has settled utils.post.list.invalidate(); }, }); };其流程是onMutate中取消进行中的请求并缓存旧数据 → 用新数据直接更新查询缓存 → 若onError触发则回滚到ctx.prevData→onSettled中调用invalidate()与服务器最终同步。Vitest 集成测试示例下面是用 Vitest 编写的集成测试用来验证 tRPC 路由器按预期工作、输入解析器推断出正确的类型、返回数据与期望输出一致。其可行性正源于createInnerTRPCContext与createCaller都可以在没有请求对象的情况下工作import { type inferProcedureInput } from trpc/server; import { expect, test } from vitest; import { appRouter, type AppRouter } from ~/server/api/root; import { createInnerTRPCContext } from ~/server/api/trpc; test(example router, async () { const ctx await createInnerTRPCContext({ session: null }); const caller appRouter.createCaller(ctx); type Input inferProcedureInputAppRouter[example][hello]; const input: Input { text: test, }; const example await caller.example.hello(input); expect(example).toMatchObject({ greeting: Hello test }); });如果过程是受保护的protectedProcedure创建上下文时传入一个 mock 的session对象即可test(protected example router, async () { const ctx await createInnerTRPCContext({ session: { user: { id: 123, name: John Doe }, expires: 1, }, }); const caller appRouter.createCaller(ctx); // ... });扩展阅读资源资源说明tRPC 官方文档https://www.trpc.iotRPC 示例合集https://github.com/trpc/trpc/tree/next/examplesReact Query 官方文档https://tanstack.com/query/v4/docs/adapters/react-query在仓库内还可以继续研读官方文档英文版 trpc.md、TypeScript 用法、环境变量校验 与 Prisma 集成以及所有 tRPC 模板源码所在的 cli/template/extras/src/server/api 与 cli/template/extras/src/trpc 目录。掌握了本文的写法与文件职责后你就能在 T3 Stack 项目中熟练地扩展类型安全的后端 API并在 RSC、集成测试与外部调用等多种场景中自由复用。赞分享开发工具CLI代码生成【免费下载链接】create-t3-appThe best way to start a full-stack, typesafe Next.js app项目地址https://gitcode.com/gh_mirrors/cr/create-t3-app点击查看免费下载相关推荐create-t3-app 中的 tRPC 实战指南零代码生成的端到端类型安全 APIcreate t3 app 中的 tRPC 实战指南零代码生成的端到端类型安全 API tRPC 是 create t3 app 默认栈T3 Stack中开发工具CLI代码生成create-t3-app 全栈类型安全指南深入理解 tRPC 端到端类型安全 APIcreate t3 app 全栈类型安全指南深入理解 tRPC 端到端类型安全 API tRPC 让开发者无需代码生成、无需运行时开销仅凭 TypeScri开发工具CLI代码生成mlx-community/gemma-4-e2b-it-mxfp8Apple Silicon专属的多模态AI模型来了完整解析与初体验mlx community/gemma 4 e2b it mxfp8Apple Silicon专属的多模态AI模型来了完整解析与初体验 mlx commun开发工具CLI代码生成创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考