关于 scaffolding-nestjs-app
用于在空目录或现有单体仓库中搭建 NestJS 服务的技能。它设定日志、API 行为、测试和部署脚手架基线,并指导按环境选择配置方式。
主要功能
- 01识别 Nest CLI 与 Nx 环境
- 02Pino 日志基线
- 03统一 API 响应与异常处理
- 04配置 Vitest 和覆盖率
- 05Docker、Compose 与 Makefile 脚手架
适用场景
- 01从零创建 NestJS 服务
- 02向 Nx 单体仓库添加 NestJS 应用
- 03建立测试与部署基础
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ satanlittlehelper/ ai-dev-kit --skill scaffolding-nestjs-app安装后在 Claude Code 中输入/scaffolding-nestjs-app确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
templates/docker/.env.example.standalonedocker-compose-service-snippet.monorepo.mddocker-compose.standalone.ymldockerfile-monorepo-reference.mdDockerfile.standaloneenv-example-snippet.monorepo.mdMakefile.monorepo-referenceMakefile.standalonetemplates/docs/claude-md-nestjs-snippet.mdclaude-md-skills-workflow-snippet.mdgitignore-ai-artifacts-snippet.txttemplates/eslint/.prettierignore.prettierrceslint.app.config.mjseslint.base.config.mjstemplates/husky/lintstagedrc.nx.jslintstagedrc.standalone.jspre-commitpre-push.nxpre-push.standalonetemplates/pino/app-logger.module.tspino-config.constants.tspino-config.helpers.tspino-config.loader.tspino-config.types.tstemplates/response/api-exception-filter.tsapi-response.interceptor.tsapi.types.tstemplates/vitest/vitest.config.tsvitest.setup.ts详细介绍
搭建新的 NestJS 应用
概述
将空目录(或现有 Nx monorepo 中的新应用)转变为 NestJS 服务,并配置作者的标准基线:pino 日志记录、统一的 ApiResponse 契约和全局异常筛选器、ESLint/Prettier、支持覆盖率的 Vitest、husky 提交前/推送前钩子,以及 Docker/Compose/Makefile 部署脚手架。模板位于此文件旁的 templates/ 目录中——请复制并根据需要调整,不要凭记忆重新编写。
适用场景
- 用户要求从头创建新的 NestJS 应用/服务/微服务。
- 用户要求在现有 Nx(或其他)monorepo 中添加新的后端应用。
- 不适用于向已搭建脚手架的应用添加单个功能/模块——此技能仅用于初始引导。
步骤 0 — 检测环境
digraph detect_env {
"nx.json exists in target repo root?" [shape=diamond];
"Any existing app/project in the repo?" [shape=diamond];
"Use Nx generator" [shape=box];
"Ask user: fresh Nx workspace or plain Nest CLI app?" [shape=diamond];
"Use Nest CLI (nest new / nest generate)" [shape=box];
"npx create-nx-workspace, then Nx generator" [shape=box];
"nx.json exists in target repo root?" -> "Use Nx generator" [label="yes"];
"nx.json exists in target repo root?" -> "Any existing app/project in the repo?" [label="no"];
"Any existing app/project in the repo?" -> "Use Nest CLI (nest new / nest generate)" [label="yes"];
"Any existing app/project in the repo?" -> "Ask user: fresh Nx workspace or plain Nest CLI app?" [label="no"];
"Ask user: fresh Nx workspace or plain Nest CLI app?" -> "npx create-nx-workspace, then Nx generator" [label="Nx"];
"Ask user: fresh Nx workspace or plain Nest CLI app?" -> "Use Nest CLI (nest new / nest generate)" [label="plain Nest"];
}
- 存在
nx.json→ 执行npx nx g @nx/nest:application apps/<app-name>(如果未提供应用名称/路径,请询问;如果尚未安装该插件,请先将@nx/nest添加到 devDependencies)。 - 没有
nx.json,但仓库中已有至少一个应用/包 → 这是一个非 Nx 多应用仓库;使用nest new <app-name>或nest generate,并遵循现有应用的结构。不要在未使用 Nx 的仓库中擅自引入 Nx。 - 没有
nx.json,也没有任何现有应用(空仓库/全新仓库) → 这是“项目中的第一个应用”场景。务必询问用户是要设置 Nx 工作区(执行npx create-nx-workspace@latest,然后使用上面的 Nx 生成器),还是要创建普通的独立 Nest CLI 应用(执行npx @nestjs/cli new .)。不要擅自做决定——此选择会影响后续每个步骤(要使用哪种模板变体:*.standalone.*还是 Nx/monorepo 版本)。
步骤 1 — 安装依赖
此技能所需的完整依赖列表——安装最新版本(@latest),不要手动固定版本。在 monorepo 中,先检查工作区根目录的 package.json,仅添加实际缺少的依赖;eslint/vitest 工具通常已经存在。
依赖:
npm install nestjs-pino pino pino-http class-validator class-transformer
如果会使用异常筛选器中的 axios 错误分支(见步骤 5),也请添加 axios;否则跳过。
开发依赖:
npm install -D pino-pretty eslint prettier eslint-config-prettier eslint-plugin-import eslint-plugin-prettier typescript-eslint vitest @vitest/coverage-v8 unplugin-swc vite-tsconfig-paths husky lint-staged
仅限 monorepo——如果 @nx/eslint-plugin 尚未作为工作区依赖,请一并添加。
第 2 步 — 生成后的清理:启用严格 TypeScript,移除 Jest 和 e2e 脚手架
严格模式是此技术栈中所有应用的默认设置——请检查确认,不要想当然地认为生成器已设置好。 打开应用的 tsconfig.json(独立项目)或工作区的 tsconfig.base.json(monorepo),确认已设置 "compilerOptions"."strict": true。nest new 的默认模板已设置此项,但 @nx/nest:application 会继承工作区的 tsconfig.base.json,而该文件可能早于此约定——如果缺少该设置,请显式添加。
不要为了在脚手架搭建期间绕过类型错误而关闭单独的严格子选项(strictNullChecks、noImplicitAny 等)——应修复根本的类型问题。
nest new 和 @nx/nest:application 默认都会搭建 Jest——作者的技术栈只使用 Vitest,且不设单独的 e2e 测试目标。生成应用后,应立即执行以下删除操作,再处理任何其他事项:
jest.config.ts/jest.config.js(应用级配置,以及全新独立仓库的根目录配置)test/(生成的 e2e 目录,通常包含app.e2e-spec.ts和jest-e2e.json)- 与源文件一同由 Jest 生成的任何
*.spec.ts文件(例如src/app.controller.spec.ts)——直接删除,不要移植到 Vitest;等实际有逻辑需要覆盖时,再按照项目自身的测试约定编写真正的 spec 文件。 - 与
jest相关的 devDependencies:jest、@nestjs/testing(仅在其他地方不需要时删除——本项目不使用它)、ts-jest、@types/jest、jest-environment-node(检查package.json,确认生成器具体添加了哪些依赖;Nx/Nest 生成器版本决定了准确列表——删除其插入的所有相关依赖)。 - 嵌入在
package.json中的jest配置块(Nx 的@nx/nest:application和普通的nest new都会添加顶层"jest"键),以及指向 Jest 的任何test/test:e2e/test:watch/test:covnpm 脚本——这些将在第 7 步替换为 Vitest 对应配置。 - 对于 Nx monorepo 应用:还要删除生成的
project.json中引用@nx/jest:jest的test/e2e目标——第 8 步会用nx:run-commands+vitest run替换它们,使其与工作区其他应用保持一致。
配置 Vitest/tsc,避免 spec 文件破坏生产构建或 IDE:
- 在
tsconfig.app.json中(与"include"同级)添加"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]——否则生产构建会包含 spec 文件,并因vi等仅限 Vitest 使用的全局变量而失败。 - 创建
tsconfig.spec.json:{ "extends": "./tsconfig.json", "compilerOptions": { "types": ["vitest/globals"] }, "include": ["src/**/*.spec.ts", "src/**/*.test.ts", "vitest.config.ts"] }——如果没有此配置,即使 Vitest 本身能正常运行测试,IDE 和tsc仍会在 spec 文件中报告Cannot find name 'vi'。 - 在每个 spec 文件中,都从
'vitest'显式导入vi,不要只依赖全局变量——IDE 只有在实际使用tsconfig.spec.json时才会从中解析类型,而显式导入则不受此影响。
应用基线:配置、全局前缀、验证
开始第 3 步之前,请确保 main.ts/app.module.ts 具备以下基线配置,无论生成器生成了什么内容:全局 ConfigModule,用于验证 PORT(使用 Joi 或 class-validator,并在使用处调用 getOrThrow——绝不直接使用裸的 process.env.PORT);app.setGlobalPrefix('api');启用 transform: true + whitelist: true + 隐式转换的全局 ValidationPipe;以及 useContainer(app, { fallbackOnErrors: true }),以便 class-validator 的自定义装饰器可以使用 Nest DI。如果应用位于 Traefik(或等效代理)反向代理之后,且代理会在转发前剥离服务名称前缀,则应用自身的路由仍应保持普通的 /api/...——代理的路由模式(例如,带有 strip-prefix 中间件的 /{service_name}/api)由代理负责处理,应用内部无需知晓。
应用基线:进程守护
每个应用的 main.ts 都必须能够应对请求链之外的故障。否则,未处理的拒绝或异常会导致进程退出,并在 stderr 上输出原始堆栈(绕过 pino);SIGTERM 也会跳过 Nest 关闭钩子,导致 Redis/BullMQ/DB 连接无法关闭。需要调用两个函数,二者都来自同一个共享助手——在 monorepo 中,从 @chatbot-platform/helpers/nest 导入 registerProcessGuards/runBootstrap(不要重复实现);在独立应用中,在 src/common/process-guards/ 下创建这两个相同的函数——绝不要在 main.ts 中直接内联 process.on(...):
runBootstrap(bootstrap)替代main.ts底部单独的bootstrap()/void bootstrap()调用:如果 bootstrap 被拒绝,会通过console.error写入错误(此时 pino 可能尚未启动),并以代码 1 退出进程。registerProcessGuards({ app, logger: app.get(Logger) })紧接在app.useLogger(app.get(Logger))之后调用:它会调用app.enableShutdownHooks();遇到unhandledRejection/uncaughtException时,会通过 pino 记录日志,执行app.close(),并与一个命名常量超时竞速,随后调用process.exit(1);关闭期间重复发生的事件会被忽略。这两类事件的处理方式相同——容器的重启策略会让服务恢复,而“存活但已损坏”的进程比重启更糟。
如果应用注册了 @nestjs-modules/ioredis 或 BullMQ @Processor,还要附加通过 pino 记录日志的 error 监听器(在 processor 上使用 @OnWorkerEvent('error');创建一个小型模块,使用 @InjectRedis() 调用 redis.on('error', ...))——否则 ioredis 和 BullMQ 会退回到在 pino 之外使用 console.error。
步骤 3 — 询问 API 版本控制
在编写 main.ts 之前,务必询问用户新应用是否需要 URI API 版本控制——不要默认选择任何一种。有些应用在此技术栈中启用了版本控制(ext-gate),有些则没有(bot-gateway、miniapp-gate、bot-publisher);这是每个应用单独决定的事项,不是统一约定。
如果需要,在 main.ts 中添加(位于 setGlobalPrefix 之后、useContainer 之前):
import { VersioningType } from '@nestjs/common';
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1',
});
这样路由会解析为 /api/v1/...。如果不需要,则跳过此项——路由仍为 /api/...。
步骤 4 — 接入 pino 日志记录器
将整个 templates/pino/ 目录复制到新应用中——其中五个文件相互配合,而不是一个单体模块:
| 模板文件 | 作用 |
|---|---|
pino-config.types.ts | AppLoggerOptions、AppLoggerContextFilter、PinoConfig 及其 is* 类型守卫 |
pino-config.constants.ts | REDACT_AUTH_HEADER_PATHS(默认对 auth/token 标头进行脱敏)、PINO_CONFIG_FILENAME |
pino-config.helpers.ts | buildPinoParams——纯构建器:pretty-transport、脱敏路径、context-blacklist 钩子 |
pino-config.loader.ts | loadPinoConfig——当 PINO_CONFIG_ENABLED=true 时,从 cwd 读取可选的 pino.config.json |
app-logger.module.ts | AppLoggerModule.forRoot/forRootAsync——组合上述四个文件的轻量 nestjs-pino 封装 |
放置位置:
- 已有多个 Nest 应用共享代码的 monorepo → 如果尚无共享库,则将整个目录放入共享库(例如
libs/nest-logger);如果已有,则直接依赖它——不要重复实现。 - 独立应用或 monorepo 中的首个应用 → 放在应用自身的
src/common/logger/中,包含同样的五个文件。 各选项的控制内容(由buildPinoParams使用的AppLoggerOptions): level— pino 日志级别('info'、'debug',等等)。pretty— 开发模式下的pino-pretty传输配置(colorize: true、translateTime: 'SYS:standard')。在生产环境中关闭 — NDJSON 会交给下游的日志采集器;彩色或格式化输出只会造成干扰。singleLine— 直接传递给pino-pretty自身的singleLine选项;true会让每个请求只占一行日志,即使req/res负载很大也一样;false会进行多行美化输出,方便在本地阅读深层嵌套对象。pinoConfigEnabled— 为true时,loadPinoConfig会要求process.cwd()中存在格式为{ contextFilter?: { blacklist: string[] }, redactPaths?: string[] }的pino.config.json,若文件缺失或格式错误则抛出明确错误;为false(默认值)时,不会读取任何文件,只应用内置的脱敏路径。遇到项目特有的噪声情况(例如健康检查模块自身的context淹没了开发日志,或内置脱敏列表未覆盖某个字段)时,可使用此开关,而不是针对每个项目手动修改共享库。
LOG_LEVEL/LOG_PRETTY/LOG_PRETTY_SINGLE_LINE/PINO_CONFIG_ENABLED — 为这四项选择一种 Joi 结构,并保持一致。 两种结构都可用;混用才会导致问题:
- 字符串结构(
Joi.string().empty('').valid('true', 'false').default('true'),通过config.get<string>('LOG_PRETTY') === 'true'进行比较) — 本技能默认采用的结构;如果同一个环境变量值需要在ConfigService之外读取(例如写入 shell 脚本),就必须使用此结构,因为原生布尔值无法经受序列化。 - 布尔结构(
Joi.boolean().default(false),通过config.getOrThrow<boolean>('LOG_PRETTY')读取)— 如果 Nest 自身的ConfigService之外没有任何代码需要原始值,这种结构更简单。
无论选择哪种结构,都要以相同方式应用于全部四个键。 混用正是此警告要避免的问题:声明 LOG_PRETTY: Joi.boolean(),却用 config.get<string>('LOG_PRETTY') === 'true' 进行比较,会静默地得到 false — 不会出现验证错误,只会产生错误的日志,因为真正的 boolean 永远不会与字符串 'true' 严格相等。逐个键确保 schema 声明的类型与工厂读取的类型相匹配。
字符串结构示例:
LOG_LEVEL: Joi.string().default('info'),
LOG_PRETTY: Joi.string().empty('').valid('true', 'false').default('true'),
LOG_PRETTY_SINGLE_LINE: Joi.string().empty('').valid('true', 'false').default('true'),
PINO_CONFIG_ENABLED: Joi.string().empty('').valid('true', 'false').default('false'),
在 app.module.ts 中接入(字符串结构工厂 — 如果改用布尔结构,则将所有 === 'true'/config.get<string> 替换为 config.getOrThrow<boolean>):
AppLoggerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService): AppLoggerOptions => ({
level: config.getOrThrow<string>('LOG_LEVEL'),
pretty: config.get<string>('LOG_PRETTY') === 'true',
singleLine: config.get<string>('LOG_PRETTY_SINGLE_LINE') === 'true',
pinoConfigEnabled: config.get<string>('PINO_CONFIG_ENABLED') === 'true',
}),
}),
并在 main.ts 中:先调用 NestFactory.create(AppModule, { bufferLogs: true }),再调用 app.useLogger(app.get(Logger))
(Logger 来自 nestjs-pino),然后调用 registerProcessGuards({ app, logger: app.get(Logger) })(参见“应用基线:进程防护”)。
环境变量 — 按照此仓库自身的环境变量约定,在以下三个位置全部更新(上面的 Joi schema、根目录的 .env.example,以及 — 对于包含 devops/ 部署层的单体仓库 — devops/.env.example 和对应的 docker-compose.prod.yml 服务环境变量块):LOG_LEVEL、LOG_PRETTY、LOG_PRETTY_SINGLE_LINE、
PINO_CONFIG_ENABLED。除非项目有特定理由需要更改,否则所有位置的 LOG_PRETTY_SINGLE_LINE 默认设为 true,PINO_CONFIG_ENABLED 默认设为 false。
接入后,实际启动应用(在 .env 中设置 LOG_PRETTY=true),并确认出现彩色单行输出 — 而不是原始 NDJSON — 然后再继续。否则很容易漏掉意外为 false 的 pretty 标记。
步骤 5 — 接入异常过滤器 + API 响应拦截器
首先,检查是否重复(这是最容易被跳过的步骤):在目标仓库中搜索是否已有
ExceptionFilter/响应拦截器实现,然后再创建新的实现。
- 找到一个,且仓库是单体仓库 → 将其提取到共享库中(参照此技能自身的演进历史:查看
@chatbot-platform/helpers/nest,其中有一个关于如何进行此类提取的完整示例),并让已有应用和 新应用都依赖共享版本。不要保留两份各自演进的副本。 - 找到一个,且仓库是独立仓库 → 按原样复用,不要再创建第二个。
- 未找到 → 复制
templates/response/api.types.ts、api-exception-filter.ts、api-response.interceptor.ts。 修正../types/api.types导入路径,使其与文件的存放位置相符。如果应用没有通过 axios 调用其他 HTTP API 的需求, 请从过滤器中移除isAxiosError分支和axios导入。
在 app.module.ts 的 providers 中注册——不要在 main.ts 中使用 useGlobalFilters/useGlobalInterceptors——并将
ApiResponseInterceptor 放在其他任何 APP_INTERCEPTOR 条目之后,确保它包装这些条目的输出:
providers: [
{ provide: APP_INTERCEPTOR, useClass: ApiResponseInterceptor },
{ provide: APP_FILTER, useClass: ApiExceptionFilter },
],
步骤 6 — 接入健康端点
此技术栈中的每个新应用都要有一个简单、无依赖的存活检测端点——不要使用 @nestjs/terminus,除非用户明确要求,否则不要进行 DB/Redis
就绪检查。模式如下(直接内联,无需复制模板文件——内容足够简单,可直接编写):
src/app/app.controller.ts:
import { Controller, Get } from '@nestjs/common';
import { Public } from '<path-to-shared-public-decorator-or-local-equivalent>';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Public()
@Get('health')
getHealth(): { service: string; status: string } {
return this.appService.getHealth();
}
}
src/app/app.service.ts(如果应用已采用点号命名的子文件夹约定来存放可测试服务,则使用 src/app/app.service/app.service.ts):
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHealth(): { service: string; status: string } {
return {
service: '<app-name>',
status: 'ok',
};
}
}
在 app.module.ts 中注册两者:controllers: [AppController],并将 AppService 添加到 providers。Public
装饰器会标记此路由,使任何全局认证守卫(APP_GUARD)跳过它——如果应用的守卫尚不支持通过 Reflector/元数据绕过公开路由,
请先为守卫添加此功能(参阅守卫自身的 canActivate,在认证逻辑之前添加 Reflector.getAllAndOverride 检查)。最终路径为:/api/health,如果此应用启用了步骤 3(版本控制),则为
/api/v1/health。
步骤 7 — ESLint + Prettier
将 templates/eslint/eslint.base.config.mjs 复制到仓库根目录(如果已有配置,则与其合并——不要
覆盖自定义的基础配置)。对于独立(非 Nx)应用,移除 @nx/enforce-module-boundaries 区块和 apps/*/libs/* tsconfig glob
路径——具体需要移除哪些内容,请参阅模板中的内联注释。
对于单体仓库中的应用,还要复制 templates/eslint/eslint.app.config.mjs,将 __APP_NAME__ 替换为实际的应用名称,并将文件保存为
apps/<app-name>/eslint.config.mjs。
将 templates/eslint/.prettierrc 和 templates/eslint/.prettierignore 复制到仓库根目录(如果两者都已存在,则跳过——不要覆盖自定义配置)。eslint.base.config.mjs 会强制执行 prettier/prettier: 'error',
因此 .prettierrc 与 ESLint 规则之间的格式差异会导致 lint 失败。
步骤 8 — Vitest
将 templates/vitest/vitest.config.ts 和 templates/vitest/vitest.setup.ts 复制到新应用中(vitest.config.ts
放在应用根目录,src/test/vitest.setup.ts)。保留 coverage.exclude 列表——它体现了作者的约定:*.api.service.ts、*.repository.ts、DTO、模块、控制器和异常过滤器不进行单元测试
(这些是无逻辑的胶水代码,或已通过间接方式覆盖)。对于尚无测试的新应用,保留注释掉的 thresholds——等项目有了有意义的覆盖率后再取消注释,不要让 80% 的阈值阻碍首次提交。
步骤 9 — Pre-commit / pre-push 钩子
如果 .husky/ 尚不存在,请运行 npx husky init。复制:
templates/husky/pre-commit→.husky/pre-commit(两个路径中的文件内容相同)。- Monorepo:
templates/husky/pre-push.nx→.husky/pre-push,templates/husky/lintstagedrc.nx.js→.lintstagedrc.js(位于根目录,由各应用共享 — 不要按应用重复创建)。 - 独立应用:
templates/husky/pre-push.standalone→.husky/pre-push,templates/husky/lintstagedrc.standalone.js→.lintstagedrc.js。
将两个钩子文件设为可执行(chmod +x .husky/pre-commit .husky/pre-push)。
步骤 10 — Docker / docker-compose / Makefile / .env.example
- 独立应用:复制
templates/docker/Dockerfile.standalone→devops/Dockerfile、templates/docker/docker-compose.standalone.yml→devops/docker-compose.yml、templates/docker/Makefile.standalone→Makefile、templates/docker/.env.example.standalone→.env.example。 - Monorepo,已存在共享的参数化 Dockerfile(
ARG APP_NAME)→ 阅读templates/docker/dockerfile-monorepo-reference.md,确认现有的devops/Dockerfile符合该结构, 不要新建 Dockerfile — 只需参照templates/docker/docker-compose-service-snippet.monorepo.md添加服务块(替换__APP_NAME__/__APP_NAME_UPPER__占位符),并根据templates/docker/env-example-snippet.monorepo.md添加新的环境变量。原样复用现有的根目录Makefile。 - Monorepo,首个应用,尚无 devops/ 配置 → 根据
templates/docker/dockerfile-monorepo-reference.md中的参考代码块创建devops/Dockerfile,并根据templates/docker/Makefile.monorepo-reference创建根目录的Makefile(将__PROJECT_NAME__替换为仓库名称)。该 Makefile 的prod-*/dev0-*目标引用docker-compose.networks.brandnew.yml/.existing.yml和docker-compose.base.yml— 这些文件定义了源项目中特定的全新 Docker 网络与现有 Docker 网络部署拓扑;只有在新仓库需要相同的区分时才添加。否则,移除_BNN/_EXN变量,并让COMPOSE_PROD目标直接指向docker-compose.prod.yml。
步骤 11 — Monorepo:在根目录 package.json 中添加 start 脚本
仅适用于 Monorepo(独立应用请跳过 — npm run start 已是应用自身的入口点)。在工作区根目录 package.json 中添加
start:<app-name> 脚本,遵循现有约定(例如
"start:ext-gate": "nx run ext-gate:serve")。否则,新应用就没有与其他应用的 start:* 脚本并列的、本地运行的文档化入口点。
步骤 12 — 在目标仓库中记录约定
将 templates/docs/claude-md-nestjs-snippet.md(替换 __APP_NAME__ 并调整路径)追加到
目标仓库的 CLAUDE.md 和/或 AGENTS.md 中,与其中已有的其他约定放在一起。如果仓库尚无这两个文件,则在仓库根目录创建 CLAUDE.md,并将此内容作为起始内容。
同时追加 templates/docs/claude-md-skills-workflow-snippet.md — 此内容与技术栈无关(介绍如何使用
skills/subagents、计划模式、提交规范和工作流产物位置),属于每个项目的 CLAUDE.md,而不仅限于 NestJS 项目。只有当目标仓库的 CLAUDE.md 已记录同等内容的
skills 工作流约定时才跳过 — 不要重复添加。
步骤 13 — 在 .gitignore 中排除 AI 产物
将 templates/docs/gitignore-ai-artifacts-snippet.txt 追加到目标仓库的 .gitignore(如果文件不存在则创建)。此代码片段仅排除个人 Claude Code 文件(CLAUDE.local.md、settings.local.json、worktrees、.doc-review/);上一步写入的 CLAUDE.md/AGENTS.md 是共享的项目文件,应继续纳入版本控制。先检查是否已有相关条目;不要重复添加已有行。
快速参考 — 模板文件
| 文件 | 用途 | 仅独立项目需修改的内容 |
|---|---|---|
templates/pino/app-logger.module.ts | nestjs-pino 封装模块(组合以下四个文件) | 无 |
templates/pino/pino-config.types.ts | AppLoggerOptions/PinoConfig 类型 + 类型守卫 | 无 |
templates/pino/pino-config.constants.ts | 默认脱敏路径、pino.config.json 文件名 | 无 |
templates/pino/pino-config.helpers.ts | buildPinoParams 纯构建器 | 无 |
templates/pino/pino-config.loader.ts | 可选的 pino.config.json 加载器 | 无 |
templates/response/api.types.ts | ApiResponse<T> 契约 | 无 |
templates/response/api-exception-filter.ts | 全局 @Catch() 过滤器 | 若未使用 axios 分支,则删除 |
templates/response/api-response.interceptor.ts | 封装控制器返回值 | 无 |
templates/eslint/eslint.base.config.mjs | 根级扁平 ESLint 配置 | 移除 Nx 边界规则 + glob tsconfig 路径 |
templates/eslint/eslint.app.config.mjs | 单个应用的 ESLint 覆盖配置 | 仅限单体仓库 |
templates/eslint/.prettierrc + .prettierignore | Prettier 格式规则 | 无 |
templates/vitest/vitest.config.ts + vitest.setup.ts | 测试运行器 + 覆盖率 | 无 |
templates/husky/pre-commit | lint-staged 钩子 | 无 |
templates/husky/pre-push.nx / pre-push.standalone | 推送时运行测试的钩子 | 选一个 |
templates/husky/lintstagedrc.nx.js / lintstagedrc.standalone.js | lint-staged 配置 | 选一个 |
templates/docker/Dockerfile.standalone / dockerfile-monorepo-reference.md | 多阶段构建 | 选一个 |
templates/docker/docker-compose.standalone.yml / docker-compose-service-snippet.monorepo.md | 部署配置 | 选一个 |
templates/docker/Makefile.standalone / Makefile.monorepo-reference | local-up/down/clean 目标 | 选一个 |
templates/docker/.env.example.standalone / env-example-snippet.monorepo.md | 环境变量基线 | 选一个 |
templates/docs/claude-md-nestjs-snippet.md | 面向目标仓库的 NestJS 专属约定说明 | 调整路径 |
templates/docs/claude-md-skills-workflow-snippet.md | 与技术栈无关的技能/子 Agent 工作流规则 | 无 |
templates/docs/gitignore-ai-artifacts-snippet.txt | 忽略个人 Claude Code 文件(CLAUDE.local.md、settings.local.json、worktrees) | 无 |
常见错误
| 错误 | 修正方法 |
|---|---|
在 main.ts 中通过 app.useGlobalFilters() 注册过滤器/拦截器 | 改为在 app.module.ts 中通过 APP_FILTER/APP_INTERCEPTOR providers 注册 |
将 ApiResponseInterceptor 放在其他 APP_INTERCEPTOR 之前 | 它必须放在最后,以便封装它们的输出 |
| 在已有异常过滤器/拦截器的单体仓库中再创建一个 | 将现有实现提取到共享库中并复用 |
将 Nx 的 @nx/enforce-module-boundaries ESLint 规则复制到独立应用中 | 删除该规则——没有需要强制执行边界的 libs/apps |
| 在第一天就设置严格的覆盖率阈值 | 暂时将 thresholds 注释掉,直到应用有了实际测试 |
| 在已有共享参数化 Dockerfile 的单体仓库中创建全新的单应用 Dockerfile | 复用带有 ARG APP_NAME 的共享 devops/Dockerfile,只需添加 compose 服务块 |
| 只在三个必需位置之一添加新的环境变量 | Joi schema + 根目录 .env.example + devops/.env.example(+ compose environment: 块)——每次都要全部添加 |
以裸 bootstrap() / void bootstrap() 结束 main.ts,且未调用 registerProcessGuards | 使用 runBootstrap(bootstrap) 包装,并在 useLogger 后立即调用 registerProcessGuards——参见“应用基线:进程保护” |
| 因为应用“仅供内部使用”而跳过健康检查端点 | 每个 HTTP 应用都要提供 GET /health(或在使用全局前缀时提供 /api/health)——仅供内部使用的应用也需要该端点进行编排存活检查 |