mcpField
S
scaffolding-nestjs-app

scaffolding-nestjs-app

github.com/satanlittlehelper开发061 次安装2 天前更新
代码仓库

关于 scaffolding-nestjs-app

用于在空目录或现有单体仓库中搭建 NestJS 服务的技能。它设定日志、API 行为、测试和部署脚手架基线,并指导按环境选择配置方式。

主要功能

  1. 01识别 Nest CLI 与 Nx 环境
  2. 02Pino 日志基线
  3. 03统一 API 响应与异常处理
  4. 04配置 Vitest 和覆盖率
  5. 05Docker、Compose 与 Makefile 脚手架

适用场景

  1. 01从零创建 NestJS 服务
  2. 02向 Nx 单体仓库添加 NestJS 应用
  3. 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.standalone
docker-compose-service-snippet.monorepo.md
docker-compose.standalone.yml
dockerfile-monorepo-reference.md
Dockerfile.standalone
env-example-snippet.monorepo.md
Makefile.monorepo-reference
Makefile.standalone
templates/docs/
claude-md-nestjs-snippet.md
claude-md-skills-workflow-snippet.md
gitignore-ai-artifacts-snippet.txt
templates/eslint/
.prettierignore
.prettierrc
eslint.app.config.mjs
eslint.base.config.mjs
templates/husky/
lintstagedrc.nx.js
lintstagedrc.standalone.js
pre-commit
pre-push.nx
pre-push.standalone
templates/pino/
app-logger.module.ts
pino-config.constants.ts
pino-config.helpers.ts
pino-config.loader.ts
pino-config.types.ts
templates/response/
api-exception-filter.ts
api-response.interceptor.ts
api.types.ts
templates/vitest/
vitest.config.ts
vitest.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:cov npm 脚本——这些将在第 7 步替换为 Vitest 对应配置。
  • 对于 Nx monorepo 应用:还要删除生成的 project.json 中引用 @nx/jest:jest 的 test/e2e 目标——第 8 步会用 nx:run-commands + vitest run 替换它们,使其与工作区其他应用保持一致。

配置 Vitest/tsc,避免 spec 文件破坏生产构建或 IDE:

  1. 在 tsconfig.app.json 中(与 "include" 同级)添加 "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]——否则生产构建会包含 spec 文件,并因 vi 等仅限 Vitest 使用的全局变量而失败。
  2. 创建 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'。
  3. 在每个 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.tsAppLoggerOptions、AppLoggerContextFilter、PinoConfig 及其 is* 类型守卫
pino-config.constants.tsREDACT_AUTH_HEADER_PATHS(默认对 auth/token 标头进行脱敏)、PINO_CONFIG_FILENAME
pino-config.helpers.tsbuildPinoParams——纯构建器:pretty-transport、脱敏路径、context-blacklist 钩子
pino-config.loader.tsloadPinoConfig——当 PINO_CONFIG_ENABLED=true 时,从 cwd 读取可选的 pino.config.json
app-logger.module.tsAppLoggerModule.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.tsnestjs-pino 封装模块(组合以下四个文件)无
templates/pino/pino-config.types.tsAppLoggerOptions/PinoConfig 类型 + 类型守卫无
templates/pino/pino-config.constants.ts默认脱敏路径、pino.config.json 文件名无
templates/pino/pino-config.helpers.tsbuildPinoParams 纯构建器无
templates/pino/pino-config.loader.ts可选的 pino.config.json 加载器无
templates/response/api.types.tsApiResponse<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 + .prettierignorePrettier 格式规则无
templates/vitest/vitest.config.ts + vitest.setup.ts测试运行器 + 覆盖率无
templates/husky/pre-commitlint-staged 钩子无
templates/husky/pre-push.nx / pre-push.standalone推送时运行测试的钩子选一个
templates/husky/lintstagedrc.nx.js / lintstagedrc.standalone.jslint-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-referencelocal-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)——仅供内部使用的应用也需要该端点进行编排存活检查

评论

支持 Markdown