nestjs-trpc 模块配置完全指南:TRPCModule.forRoot 全参数解析与 Comp AI CRM 实践
后端前端CRM人工智能AI Agent【免费下载链接】crmComp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.项目地址https://gitcode.com/gh_mirrors/crm48/crm点击查看免费下载TRPCModule.forRoot()是 nestjs-trpc 在 NestJS 中接入 tRPC 的唯一入口其选项决定了路由挂载点、请求上下文、全局中间件、错误处理、数据序列化与流式传输等全部行为。本文基于 nestjs-trpc v2.13.0 发布的dist/interfaces/module-options.interface.d.ts逐项拆解TRPCModuleOptions的每个字段并结合 Comp AI CRM 仓库 apps/api/src/trpc/trpc.module.ts 中的真实生产配置给出可直接复制的配置写法与避坑要点。读完本文你将掌握如何正确装配 forRoot、如何用全局中间件与 onError 做可观测性、如何用 errorFormatter 塑造客户端看到的错误载荷以及 SSE / JSONL 流式场景下的超时与保活调优。TRPCModuleOptions 全景一份类型定义看懂全部选项TRPCModuleOptions的类型定义被完整转写自 v2.13.0 发布的dist/interfaces/module-options.interface.d.tsexport interface TRPCModuleOptions { basePath?: string; context?: ClassTRPCContext; errorFormatter?: TRPCErrorFormatterany, TRPCDefaultErrorShape; transformer?: DataTransformer | CombinedDataTransformer; logger?: LoggerService; onError?: ClassTRPCErrorHandler; globalMiddlewares?: ArrayClassTRPCMiddleware | ConstructorTRPCMiddleware; sse?: TRPCSSEOptions; jsonl?: TRPCJSONLOptions; }各选项的类型与默认值汇总如下OptionTypeDefaultbasePathstring/trpccontextClassTRPCContext—errorFormatterTRPCErrorFormatter—transformerDataTransformer \| CombinedDataTransformer—loggerLoggerServiceConsoleLoggeronErrorClassTRPCErrorHandler—globalMiddlewaresArrayClassTRPCMiddleware[]sseTRPCSSEOptions见下文说明jsonlTRPCJSONLOptions—两条贯穿全文的硬性规则请先记住TRPCModule.forRoot(options?)返回的是DynamicModule且没有forRootAsync。如果配置依赖运行时值如数据库连接、环境变量正确做法是把ConfigService注入到 context 类或中间件里而不是试图在模块定义阶段await任何东西。所有以类形式传入的选项context、onError、globalMiddlewares中的类必须同时出现在模块的providers数组里。forRoot只是记录“用哪个类”真正让 Nest 完成实例化与依赖注入的是providers。漏掉任何一个运行时就会得到未定义注入或“procedure 不存在”的 404。这一点在 Comp AI CRM 中体现得十分标准apps/api/src/trpc/trpc.module.ts 里forRoot传入的TrpcContext、TrpcErrorHandler、LoggingMiddleware、DomainErrorMiddleware四个类全部重复列入了providers。basePath路由挂载点与 404 的头号来源basePath决定 tRPC handler 挂载在哪个路径下。默认值是/trpc此时每个 procedure 的完整地址为/trpc/alias.procedureTRPCModule.forRoot({ basePath: /api/trpc })两个最容易踩的坑必须与客户端httpLink配置的url完全一致包括你用app.setGlobalPrefix()设置的全局前缀。两者是拼接关系basePath与全局前缀会组合成最终路径忘记这一点是“看起来像 procedure 不存在”的 404 最常见成因。如果客户端指向了错误的路径你会在网络面板看到一个 404而 tRPC 侧查不到任何 procedure 调用记录——先核对两端 URL 再排查逻辑。Comp AI CRM 的实践是basePath: /api/trpc见 apps/api/src/trpc/trpc.module.ts并在错误处理中保留了/trpc/${path}的路径还原逻辑见 apps/api/src/trpc/trpc-error.handler.ts方便遥测系统还原被全局前缀修饰前的原始 procedure 路径。context每个请求一次的自定义上下文context接收一个实现TRPCContext接口的类该类的实例每个请求创建一次是挂载请求对象、会话、按请求粒度的数据加载器的正确位置。接口约定与实现细节可进一步阅读 middlewares-and-context.md。Comp AI CRM 的实现展示了标准写法apps/api/src/trpc/trpc.context.tsimport { auth } from crm/auth; import { Injectable } from nestjs/common; import { fromNodeHeaders } from better-auth/node; import type { Request } from express; import type { ContextOptions, TRPCContext } from nestjs-trpc; import type { BaseTrpcContext } from ./context.types; export async function createBaseTrpcContext( req: Request | undefined, ): PromiseBaseTrpcContext { const session req ? await auth.api .getSession({ headers: fromNodeHeaders(req.headers) }) .catch(() null) : null; return { req, session }; } Injectable() export class TrpcContext implements TRPCContext { async create(opts: ContextOptions): PromiseBaseTrpcContext { const req req in opts ? opts.req : undefined; return createBaseTrpcContext(req); } }从源码结构可以看到几个值得借鉴的要点上下文类型在 context.types.ts 中单独定义BaseTrpcContext包含req与session并派生出带user的AuthedTrpcContext供认证后的 procedure 使用——类型在中间件链中逐层收窄而不是在上下文里塞一个大而全的对象。session 解析失败时用.catch(() null)兜底保证未登录请求也能正常进入后续流程由认证中间件决定是否拒绝。req in opts的存在性判断是为了兼容 Express 与 Fastify 两套ContextOptions结构保证上下文类在两个平台下都能工作。globalMiddlewares全局中间件与“不要放认证”的告诫globalMiddlewares中的中间件会应用到应用内每一个procedure且执行顺序在 router 级与 procedure 级中间件之前Module({ imports: [ TRPCModule.forRoot({ globalMiddlewares: [LoggedMiddleware, ErrorReportingMiddleware], }), ], providers: [LoggedMiddleware, ErrorReportingMiddleware], }) export class AppModule {}适合放进全局中间件的场景计时、日志、错误上报、限流。不适合的场景是认证——因为全局认证中间件必须为每一个公开 procedure 特判放行而这些例外很容易写错、漏掉导致公开接口意外被锁死或受保护接口意外暴露。更好的做法是在需要保护的 router 上显式标注UseMiddlewares(AuthMiddleware)让受保护面在代码里一目了然。Comp AI CRM 的全局中间件组合apps/api/src/trpc/trpc.module.ts恰好体现了“全局做可观测、局部做认证”的分工LoggingMiddlewareapps/api/src/trpc/middlewares/logging.middleware.ts在next()前后记录耗时输出type path ok/err durationMs的结构化日志。注意它用Date.now()而不是process.hrtime毫秒精度对日志足够关键点是始终returnopts.next()的结果否则中间件链会在这一层被吞掉。DomainErrorMiddlewareapps/api/src/trpc/middlewares/domain-error.middleware.ts把业务代码抛出的 NestJSHttpException翻译成 tRPC 的TRPCError通过 HTTP 状态码400/401/403/404/409/429映射到BAD_REQUEST/UNAUTHORIZED/FORBIDDEN/NOT_FOUND/CONFLICT/TOO_MANY_REQUESTS其余一律归为INTERNAL_SERVER_ERROR。这解决了一个关键问题默认情况下HttpException不会被翻译成 tRPC 错误形状会以不透明的 500 浮出表面。AuthMiddleware与SessionOnlyMiddleware并没有放进globalMiddlewares而是作为 providers 被模块exports由各个 router 按需引用——这正是原文档建议的“显式认证”模式。onError只观察、不改写的错误上报钩子onError是每次 procedure 抛错时被调用的可注入 handler典型用途是上报 Sentry 等监控系统。关键约束它只能观察不能改写响应。// app.error-handler.ts import { Inject, Injectable } from nestjs/common; import { OnErrorOptions, TRPCErrorHandler } from nestjs-trpc; Injectable() export class AppErrorHandler implements TRPCErrorHandler { constructor(Inject(LogService) private readonly logService: LogService) {} onError(opts: OnErrorOptions): void { this.logService.error([${opts.type}] ${opts.path}: ${opts.error.message}); } }OnErrorOptions与TRPCErrorHandler的完整接口如下export interface OnErrorOptions { error: TRPCError; type: TRPCProcedureType | unknown; path: string | undefined; input: unknown; ctx: Recordstring, unknown | undefined; req: unknown; } export interface TRPCErrorHandler { onError(opts: OnErrorOptions): void; }装配方式同样是“forRoot 注册 providers 提供实例”Module({ imports: [TRPCModule.forRoot({ onError: AppErrorHandler })], providers: [AppErrorHandler], }) export class AppModule {}三条使用纪律onError返回void且不会被 await——不要把慢操作如同步发送邮件、磁盘写入放在请求路径上。opts.input是用户的原始载荷opts.ctx可能携带会话数据两者都会随你的上报到达目标系统受数据合规约束时务必先脱敏再落日志。要区分真实 bug 与预期拒绝按错误码过滤——一个打错 id 的NOT_FOUND不是事故onError(opts: OnErrorOptions): void { if (opts.error.code INTERNAL_SERVER_ERROR) { this.sentry.captureException(opts.error.cause ?? opts.error, { extra: { path: opts.path } }); } }Comp AI CRM 的 trpc-error.handler.ts 完整实现了这套过滤INTERNAL_SERVER_ERROR时用logger.error记录堆栈error.stack ?? String(error.cause ?? error)并调用crm/telemetry的apiError上报携带还原后的/trpc/${path}与status: 500其他错误码则降级为logger.warn只记message/type/path/code避免噪音淹没真实故障。errorFormatter唯一能改写客户端载荷的选项与onError不同errorFormatter是挂在 options 对象上的普通函数不是可注入类它负责塑形客户端收到的错误载荷——既能附加结构化的校验错误又能保证内部消息不会在生产环境泄漏出去TRPCModule.forRoot({ errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, }; }, })tRPC 官方的错误格式化规范可参考 tRPC 文档的 error formatting 章节核心心智模型是onError看内部、errorFormatter看外部前者做可观测性后者做客户端契约。Comp AI CRM 的 error-formatter.ts 是一个值得精读的生产级范例它把 tRPC 字符串化后的整个ZodError从“满是code、minimum、inclusive、path的 JSON 数组”提炼成人类可读的完整句子如email must be a valid email。两个实现细节值得注意用duck-typing鸭子类型解析 cause 而非instanceof ZodError——错误跨包边界传递时workspace 里如果存在两份 zod 副本会导致instanceof静默失效把原始 JSON 又打回屏幕上。对以标点结尾的 message 直接原样返回项目自定义的校验文案本身是整句否则才拼接字段名前缀空句子与重复句子会被过滤去重。transformerJSON 表达不了的类型的救星数据 transformer 用于传输 JSON 无法直接表达的类型——Date、Map、Set、BigInt、undefinedimport superjson from superjson; TRPCModule.forRoot({ transformer: superjson })三条硬性约束客户端必须配置同一个 transformer否则每个请求都会反序列化失败。自 v2.5.0 起 CLI 在生成 schema 时会自动探测 transformer生成的类型会把 transformer 的影响考虑进去。新增或移除 transformer 是破坏性的线格式变更——必须两端服务端与所有客户端一起部署不能只改一端灰度。logger唯一不经过 DI 的选项logger接受任意实现了LoggerService的实现替换默认的ConsoleLoggerimport { Module } from nestjs/common; import { TRPCModule } from nestjs-trpc; import { MyLogger } from ./my-logger.service; Module({ imports: [TRPCModule.forRoot({ logger: new MyLogger() })], }) export class AppModule {}特别注意这里传的是实例而不是类——它是全部选项中唯一不参与 DI 解析的因此一个自身需要注入依赖的 logger 只能手动构造或从 app context 中获取。Pino 与 Winston 的适配器如nestjs-pino都能满足该接口。Comp AI CRM 传入了new ContextLogger()apps/api/src/trpc/trpc.module.ts这个自定义 logger 定义在 context-logger.ts与仓库自建的日志中间件、异常过滤器共用同一套上下文日志体系保证 tRPC 请求日志与 Nest 其他日志格式统一。sse订阅流的保活与超时调优TRPCSSEOptions完整类型v2.13.0 新增export interface TRPCSSEOptions { /** Enable SSE subscriptions. default true */ enabled?: boolean; ping?: { /** default false */ enabled: boolean; /** Interval in milliseconds. default 1000 */ intervalMs?: number; }; /** Max duration in ms before ending the stream. default undefined */ maxDurationMs?: number; /** End the request immediately after data is sent. default false */ emitAndEndImmediately?: boolean; client?: { /** Client reconnects after this much inactivity, in ms. default undefined */ reconnectAfterInactivityMs?: number; }; }一个生产环境的安全基线TRPCModule.forRoot({ sse: { ping: { enabled: true, intervalMs: 30000 }, client: { reconnectAfterInactivityMs: 60000 }, }, })四个关键认知Ping 默认关闭而intervalMs默认1000。如果只开 ping 不设间隔每个打开的流每秒会收到一次 ping——务必显式设置间隔。间隔要低于代理的空闲超时。30s 对 nginx、ALB、Cloudflare 常见的 60s 默认值是安全的。client.reconnectAfterInactivityMs应明显大于ping.intervalMs否则客户端会在健康的 ping 间隙里反复重连。emitAndEndImmediately是为无法保持流式响应的 serverless 运行时准备的在常规长驻 Node 服务器上请保持关闭——它会让订阅失去意义。maxDurationMs用来封顶流生命周期当上游负载均衡器反正会切断连接时主动收尾比被动掐断能让客户端重连得更干净。jsonl批量流式响应的保活心跳TRPCJSONLOptions只有一个字段export interface TRPCJSONLOptions { /** Interval in ms between keep-alive pings on streamed batch responses. default undefined */ pingMs?: number; }它作用于httpBatchStreamLink的批量流式响应不是订阅。适用场景你批量请求了多个 procedure其中某个慢 procedure 让整个响应挂着不返回超过了代理的空闲超时。此时给批量流加心跳TRPCModule.forRoot({ jsonl: { pingMs: 30000 } })已经消失的选项autoSchemaFile 与 schemaFileImportsautoSchemaFile和schemaFileImports是v1 时代的选项在 v2 的TRPCModuleOptions类型中不存在——传给forRoot会直接报 TypeScript 错误。但官方多篇文档context、client、integrations 页面至今仍展示autoSchemaFile: ./src/generated这是 v1/v2 混排造成的坑。v2 中输出位置是 CLI 的职责nestjs-trpc generate --output ./src/generated相关细节见 codegen-and-client.md。兼容性备注v2 CLI 在扫描你的forRoot调用时仍会识别字面量autoSchemaFile以帮助从 v1 迁移的项目——但不要依赖它它不属于类型化 API。Express 与 Fastify零配置双平台适配nestjs-trpc原生同时支持 Express 与 Fastify无需任何配置适配器会检查 Nest 的HttpAdapterHost并自动选择正确的 tRPC adapter。唯一的可见影响在ContextOptions的类型上——它是CreateExpressContextOptions | CreateFastifyContextOptions的联合类型。在伸手去取某个驱动特有的 request 属性之前先做类型收窄你的 context 类就能保持跨平台可移植。这正是 Comp AI CRM 的TrpcContext.create中req in opts ? opts.req : undefined判断的用意兼容两个平台的同时保持类型安全。实战对照一份完整的生产级 forRoot 配置把本文所有要点汇聚成 Comp AI CRM 的实际装配apps/api/src/trpc/trpc.module.tsModule({ imports: [ TRPCModule.forRoot({ basePath: /api/trpc, context: TrpcContext, logger: new ContextLogger(), errorFormatter: formatTrpcError, onError: TrpcErrorHandler, globalMiddlewares: [LoggingMiddleware, DomainErrorMiddleware], }), ], providers: [ TrpcContext, TrpcErrorHandler, LoggingMiddleware, DomainErrorMiddleware, AuthMiddleware, SessionOnlyMiddleware, ], exports: [AuthMiddleware, SessionOnlyMiddleware], }) export class TrpcModule {}这份配置体现了本文的全部原则basePath 显式声明避免默认/trpc与全局前缀组合产生歧义context 类实现了完整的会话解析并独立于模块定义时间全局中间件只做日志与异常翻译认证AuthMiddleware与会话限制SessionOnlyMiddleware通过exports交给各 router 显式UseMiddlewaresonError 按错误码分级500 级才上报遥测其余仅 warnerrorFormatter 把 Zod 校验错误翻译成可读句子不泄漏内部堆栈logger 传入项目自定义实例与全局日志体系打通。对照 v2.13.0 的类型定义逐项核验后可以发现这份配置恰好覆盖了TRPCModuleOptions中除transformer、sse、jsonl之外的全部字段——项目暂未引入自定义 transformer数据均为 JSON 可表达类型也暂未启用订阅与批量流式传输一旦启用可分别按本文的 SSE 保活基线与jsonl: { pingMs: 30000 }方案直接扩展。参考链接apps/api/src/trpc/trpc.module.ts — forRoot 生产装配示例apps/api/src/trpc/trpc.context.ts — context 类实现apps/api/src/trpc/error-formatter.ts — errorFormatter 生产实现apps/api/src/trpc/trpc-error.handler.ts — onError 分级上报实现apps/api/src/trpc/middlewares — 全局/认证中间件目录middlewares-and-context.md — context 类与中间件链的完整讨论codegen-and-client.md — CLI 生成与客户端消费api-reference.md — 从发布的 .d.ts 转写的完整导出符号与类型签名赞分享后端前端CRM人工智能AI Agent【免费下载链接】crmComp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.项目地址https://gitcode.com/gh_mirrors/crm48/crm点击查看免费下载相关推荐NestJS tRPC 端到端类型安全实战nestjs-trpc 适配器完全指南基于 Comp AI CRM 源码验证NestJS tRPC 端到端类型安全实战nestjs trpc 适配器完全指南基于 Comp AI CRM 源码验证 本文是一份以 nestjs t后端前端CRM人工智能AI AgentComp AI CRM 实战nestjs-trpc 中间件与上下文的完整指南Comp AI CRM 实战nestjs trpc 中间件与上下文的完整指南 导读 本指南以 nestjs trpc 官方技能文档为骨架结合 Comp AI后端前端CRM人工智能AI Agentnestjs-trpc 代码生成与客户端使用Comp AI CRM 的端到端类型安全实践nestjs trpc 代码生成与客户端使用Comp AI CRM 的端到端类型安全实践 本篇技术指南聚焦 nestjs trpc 的代码生成Codegen后端前端CRM人工智能AI Agent上一篇STM32 PID温控实战指南从0到1实现±0.5℃高精度控制下一篇终极指南如何使用RPG Maker Decrypter快速解密游戏资源创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考