
tRPC Standalone Adapter 完全指南基于 Node.js HTTP 服务搭建零依赖的端到端类型安全 API【免费下载链接】trpc♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc本篇技术指南以 tRPC 官方文档www/versioned_docs/version-10.x/server/adapters/standalone.md为核心骨架结合当前仓库中的源码与真实示例系统讲解 Standalone Adapter 的适用场景、完整搭建步骤、CORS/OPTIONS 处理方式以及createHTTPHandler、basePath、HTTP/2 等进阶能力。读完本文你将能在本地开发或自建服务器上独立启动一个可运行的 tRPC 服务并掌握将 tRPC 接入任意 Node.js HTTP 服务器的底层原理。1. Standalone Adapter 是什么一个极简的 Node.js HTTP 服务包装层tRPC 官方文档对其定位非常明确Standalone Adapter 是让一个新项目最快跑通 tRPC 的方式它既适合本地开发也适合自建服务器server-based的生产环境。从实现本质上看它只是对 Node.js 标准 HTTP Server 的薄封装并在其上补充了与 tRPC 相关的常规选项。这一点在仓库源码中可以得到直接印证。核心实现位于 standalone.tscreateHTTPServer的全部代码就是一行export function createHTTPServerTRouter extends AnyRouter( opts: CreateHTTPHandlerOptionsTRouter, ) { return http.createServer(createHTTPHandler(opts)); }也就是说tRPC 的“独立服务器”本质上是http.createServer(...)配合一个把请求路由到 tRPC 内部处理器的RequestListener。因此它返回的对象就是 Node.js 原生的http.Server实例天然拥有.listen()、.close()等全部 API 与事件。1.1 与其他 Adapter 的分工什么时候不必用它官方文档同时给出了选型边界如果已有 Express、Fastify、Next.js 等存量 API 服务应优先使用各自的专用适配器把 tRPC 集成进去如果倾向 serverless 或边缘计算部署则 AWS Lambda 与 Fetch 适配器更合适。当前仓库中这些适配器均有对应文档可供查阅Express 适配器Fastify 适配器Next.js 适配器AWS Lambda 适配器Fetch 适配器1.2 双入口dual entry-point的经典用法文档特别指出一个常见实践当线上部署所用的适配器难以在本地机器上运行时应用会保留两个入口——本地开发用 Standalone Adapter部署到云平台时换用另一套适配器。例如在 Vercel Edge 上无法直接运行常驻的 Node HTTP 服务而本地跑createHTTPServer却非常轻量二者可以共存于同一份代码库中。这一模式在当前仓库有完整落地示范examples/minimal/src/server/index.ts最精简的 Standalone 服务仅 50 余行即定义了路由并server.listen(3000)examples/standalone-server/src/server.ts同一份代码同时启动createHTTPServer与基于ws的 WebSocket 服务通过applyWSSHandler演示了 HTTP 订阅subscription并存的能力。2. 十分钟搭建一个 Standalone tRPC Server2.1 第一步实现 App Router无论使用哪种适配器tRPC 服务端的第一步都是定义路由router。下方代码来自官方文档定义了一个查询过程query与一个变更过程mutationimport { initTRPC } from trpc/server; import { z } from zod; export const t initTRPC.create(); export const appRouter t.router({ getUser: t.procedure.input(z.string()).query((opts) { return { id: opts.input, name: Bilbo }; }), createUser: t.procedure .input(z.object({ name: z.string().min(5) })) .mutation(async (opts) { // use your ORM of choice return await UserModel.create({ data: opts.input, }); }), }); // export type definition of API export type AppRouter typeof appRouter;需要特别强调最后一行export type AppRouter typeof appRouter;它只导出类型而不导出实现这是 tRPC 实现端到端类型安全的基石——客户端仅依赖这一类型签名即可获得与后端完全一致的推断却不暴露任何服务端逻辑。官方文档建议配合 quickstart 指南 进一步了解 router 与 procedure 的定义规范。注意createUser使用z.string().min(5)作为输入校验如果请求体不满足约束tRPC 会在进入业务函数之前直接返回 ZodError 对应的错误无需手写防御性校验代码。2.2 第二步用 Standalone Adapter 启动服务定义好 router 后只需调用createHTTPServer并传入 router 与可选的createContext即可import { initTRPC } from trpc/server; import { createHTTPServer } from trpc/server/adapters/standalone; import { appRouter } from ./appRouter.ts; createHTTPServer({ router: appRouter, createContext() { console.log(context 3); return {}; }, }).listen(2022);关于这段代码值得展开三点均有源码依据createContext的签名类型CreateHTTPContextOptions在 standalone.ts 中定义为NodeHTTPCreateContextFnOptionshttp.IncomingMessage, http.ServerResponse即上下文工厂会收到原生请求/响应对象。若你需要在 context 中暴露当前用户、请求头或数据库连接池就在此函数的opts.req/opts.res上读取返回的对象会注入到每个 procedure 的opts.ctx中。文档中的console.log(context 3)只是演示该函数会在每次请求到达时被调用。.listen(2022)由于createHTTPServer返回原生http.Server端口监听、listening/error事件等全部沿用 Node.js 语义无需学习任何自定义 API。已省略createContext也合法参考 examples/minimal/src/server/index.ts仓库中最小的例子甚至不传createContext直接createHTTPServer({ router: appRouter })即可运行。2.3 客户端如何连接虽然 Standalone 文档没有展开客户端部分但从同目录的示例可以窥见调用方式仓库中 examples/standalone-server/src/client.ts 使用createTRPCClientvanilla 客户端以httpBatchLink指向http://localhost:2022。这意味着 Server 层唯一要保证的就是客户端所访问的 HTTP 路径与 Standalone 服务的监听地址一致类型则完全由共享的AppRouter类型保障这就是“Move Fast and Break Nothing”的由来。3. 处理 CORS 与 OPTIONS 预检请求3.1 默认行为不处理、不响应文档明确指出默认情况下 Standalone 服务不会响应 HTTP OPTIONS 请求也不会设置任何 CORS 响应头。这符合 tRPC 对 Node 底层 HTTP 服务保持“零假设”的设计。源码中的类型注释同样说明了这一事实By default, httpOPTIONSrequests are not handled, and CORS headers are not returned. —— node-http/types.ts因此如果你的前端运行在另一个域名/端口比如 Vite 开发服务器代理前的直连场景或者部署环境本身不做 CORS 兜底就需要自行处理。3.2 第一步安装 cors 包官方推荐使用广受欢迎的corsnpm 包来补充支持yarn add cors yarn add -D types/cors若使用 npm等价命令为npm install cors与npm install -D types/cors。3.3 第二步通过middleware选项挂载createHTTPServer接受一个middleware选项把cors()传入即可import { initTRPC } from trpc/server; import { createHTTPServer } from trpc/server/adapters/standalone; import cors from cors; createHTTPServer({ middleware: cors(), router: appRouter, createContext() { console.log(context 3); return {}; }, }).listen(3333);注意上方示例用cors()放开了所有跨域请求只适合开发阶段生产环境应当通过cors({ origin: [...] })等方式做白名单限制。仓库中的真实示范位于 examples/minimal-react/server/index.ts其正是使用createHTTPServer({ middleware: cors(), router: appRouter })的方式支撑浏览器端 React 应用——该示例在当前仓库文档中被描述为 “Standalone tRPC Server with CORS handling”。3.4middleware的本质与局限从 node-http/types.ts 的源码可以看到middleware的类型是一个 connect/Node.js 兼容中间件type ConnectMiddlewareTRequest, TResponse ( req: TRequest, res: TResponse, next: (err?: any) any, ) void;因此它理论上可用于任何 connect 风格的需求而不仅是 CORS。但文档特别提醒它被定位为“简单逃生舱”simple escape hatch本身不支持多个中间件组合。若需要组合多个中间件官方给出三种可选方案改用中间件体系更完整的适配器如 Express 适配器借助connect这类中间件组合库将多个中间件合并成一个后再传入结合下文的自定义 HTTP Server 方案在createHTTPHandler外层自行编排中间件逻辑。4. 进阶用 createHTTPHandler 接管自有 HTTP 服务器当createHTTPServer无法满足需求例如需要自定义请求路由、鉴权、日志或静态资源处理时Standalone Adapter 还导出了底层的createHTTPHandler。它返回一个标准的 Node.jsRequestListener可以任意时机嵌入你自己的http.createServer回调import { createServer } from http; import { initTRPC } from trpc/server; import { createHTTPHandler } from trpc/server/adapters/standalone; const handler createHTTPHandler({ router: appRouter, createContext() { return {}; }, }); createServer((req, res) { /** * Handle the request however you like, * just call the tRPC handler when youre ready */ handler(req, res); }).listen(3333);从源码看这正是createHTTPServer内部使用的同一实现createHTTPServer等价于http.createServer(createHTTPHandler(opts))。所以二者共享完全一致的 handler 语义——tRPC 请求的解析、批量请求batching、错误格式化等逻辑都在createHTTPHandler返回的处理器中完成区别只在于由谁调用它。在回调中你可以在handler(req, res)之前自由地插入鉴权判断、请求改写、自定义中间件等真正实现“请求怎么处理由你决定需要 tRPC 时再交给它”。5. 进阶basePath 指定服务前缀如果你希望把 tRPC 端点挂载在某个 URL 前缀之下例如反向代理按路径分流将/trpc/下的流量转发给本服务Standalone Adapter 提供了basePath选项。官方类型注释给出了精确约定见 standalone.tsbasePath会从请求路径request path的开头切片去掉默认值为/示例值/trpc/、/trpc/api/。注意不要遗漏末尾的斜杠。配置方式import { createServer } from http; import { createHTTPHandler } from trpc/server/adapters/standalone; const handler createHTTPHandler({ router: appRouter, basePath: /trpc/, }); createServer((req, res) { if (req.url?.startsWith(/trpc/)) { return handler(req, res); } // [... insert your custom logic here ...] res.statusCode 404; res.end(Not Found); }).listen(3001);结合源码standalone.ts可以看清其内部实现handler 根据opts.basePath ?? /计算出sliceLength随后用url.pathname.slice(sliceLength)去掉前缀得到 procedure 路径再交给共享的nodeHTTPRequestHandler执行实际调用。也就是说basePath是纯路径层面的“去前缀”逻辑其余一切行为与不带前缀时完全一致。6. 进阶HTTP/2 支持当前仓库的 Standalone Adapter 还导出了面向 HTTP/2 的createHTTP2Handler其使用方式与 HTTP/1 版本对称。以文档中的 secure server 为例import http2 from http2; import { createHTTP2Handler } from trpc/server/adapters/standalone; import { appRouter } from ./_app; import { createContext } from ./context; const handler createHTTP2Handler({ router: appRouter, createContext, // basePath: /trpc/, // optional, defaults to / }); const server http2.createSecureServer( { key: ..., cert: ..., }, (req, res) { /** * Handle the request however you like, * just call the tRPC handler when youre ready */ handler(req, res); }, ); server.listen(3001);与 HTTP/2 配套的 context 工厂签名需要使用独立的类型CreateHTTP2ContextOptionsimport type { CreateHTTP2ContextOptions } from trpc/server/adapters/standalone; export async function createContext(opts: CreateHTTP2ContextOptions) { opts.req; // Http2ServerRequest opts.res; // Http2ServerResponse opts.info; // tRPC 请求相关信息TRPCRequestInfo return {}; } export type Context AwaitedReturnTypetypeof createContext;从 standalone.ts 的源码可以确认createHTTP2Handler与 HTTP/1 版共用同一个内部createHandler区别仅在于泛型参数换成了http2.Http2ServerRequest/http2.Http2ServerResponse因此前文所述的basePath、context、错误处理等行为在 HTTP/2 下同样生效。需要注意的是HTTP/2 的明文模式http2.createServer在多数现代浏览器中不支持实际部署通常需要搭配 TLS即上方http2.createSecureServer传入key/cert或交由支持 h2 的反向代理如 Nginx、Caddy处理。7. 源码原理纵深一次 tRPC 请求在 Standalone 内部如何流转综合 standalone.ts 与 node-http 目录下的实现可以完整还原一条请求的调用链监听与分发createHTTPServer通过http.createServer(createHTTPHandler(opts))创建服务器路径规整每个请求到达后内部createHandler读取请求 URL依据basePath用url.pathname.slice(sliceLength)得到 tRPC 调用路径如post.all见 standalone.ts上下文构建调用你提供的createContext({ req, res, info })结果作为本次调用的ctx核心处理调用共享的nodeHTTPRequestHandler见 node-http它负责 JSON 请求体解析、输入校验、执行 procedure、序列化响应以及返回码与错误格式的统一异常兜底若步骤 4 抛出未捕获异常由internal_exceptionHandler统一转为标准错误响应避免进程因单次请求崩溃run(...).catch(...)可见于 standalone.ts。此外NodeHTTPHandlerOptions中还包含maxBodySize?: number见 node-http/types.ts用于限制请求体体积上限请求对象上还可能存在req.body部分环境已预解析 body以及可选的socket/flush字段这些细节共同构成了适配器在“最简封装”与“生产健壮性”之间的平衡。在测试层面仓库内部的packages/server/src/__tests__/trpcServerResource.ts会直接调用createHTTPServer/createHTTPHandler来启动被测服务这进一步证明该 API 是服务端集成测试中的标准入口订阅与流式场景的端到端验证则由packages/tests/server/下的streaming.test.ts、websockets.test.ts等测试覆盖。8. 场景总结与推荐阅读使用场景推荐做法全新项目快速跑通 / 本地开发createHTTPServer({ router })一行启动生产环境自建服务器部署同样适用可叠加middleware、TLS、反向代理已有 Express/Fastify/Next.js 应用使用对应专用适配器见第 1.1 节链接Serverless / Edge 平台AWS Lambda、Fetch 适配器需要自定义路由/鉴权/组合中间件createHTTPHandler 自定义http.createServer服务挂载于特定前缀basePath: /trpc/HTTP/2 传输createHTTP2Handlerhttp2.createSecureServer本地 HTTP 与生产云适配并存双入口模式参考 examples/standalone-server若希望亲手运行这些能力仓库中的两个最小示例可以直接作为起点examples/minimal纯 Standalone 服务无 CORSexamples/minimal-react带cors()处理的 Standalone 服务 React 前端。更完整的方案对比与配置说明可继续阅读当前仓库的 Standalone Adapter 现行文档 以及 服务端适配器总览。【免费下载链接】trpc♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考