2. 开发环境与项目脚手架
企业级 NestJS 项目的质量差异,往往在写下第一行业务代码之前就已经决定。脚手架阶段确定的 TypeScript 严格程度、代码检查规则、环境变量加载方式,会直接影响后续几十个模块的协作效率与线上稳定性。本节围绕 Nest CLI 创建工程、TypeScript 严格模式、ESLint 与 Prettier、环境变量与多环境配置加载五个环节,搭建一条可复制的标准化开发基线。
用 Nest CLI 生成工程骨架
Nest CLI 是官方提供的工程生成与开发工具,它内置了模块、控制器、服务、守卫等代码模板,能保证团队成员的目录结构一致。推荐使用包管理器直接调用,避免全局安装带来的版本漂移:
npx @nestjs/cli@latest new order-service --package-manager pnpm --strict
cd order-service
pnpm run start:dev
其中 --strict 会在生成时直接开启 TypeScript 严格模式,省去后续手工调整。生成后的核心目录结构如下:src/main.ts 是应用入口,负责创建 Nest 应用实例;src/app.module.ts 是根模块,聚合所有子模块;test/ 存放端到端测试;nest-cli.json 描述构建与生成行为。执行 pnpm run start:dev 后,CLI 会以 watch 模式启动,控制台输出类似 Nest application successfully started,访问 http://localhost:3000 返回 Hello World! 即表示骨架可用。
TypeScript 严格模式:把错误拦在编译期
NestJS 默认的 tsconfig.json 较为宽松,企业项目应显式收紧。关键选项及作用如下:
| 选项 | 取值 | 解决的问题 |
|---|---|---|
| strict | true | 开启全部严格检查的总开关 |
| strictNullChecks | true | 禁止把 null/undefined 赋给非空类型 |
| noImplicitAny | true | 禁止隐式 any,强制标注类型 |
| noUnusedLocals | true | 未使用的局部变量直接报错 |
| noFallthroughCasesInSwitch | true | 防止 switch 分支意外穿透 |
| forceConsistentCasingInFileNames | true | 避免大小写不一致导致的跨平台问题 |
开启 strictNullChecks 后,形如 findOne(id): Promise<Order | null> 的返回值必须显式处理 null 分支,否则编译失败。这正是企业代码最需要的约束——把空值判断从运行时提前到编译期。若历史项目迁移困难,可先用 strict: true 配合局部 // @ts-expect-error 注释逐步收敛,而不是整体关闭。
ESLint 与 Prettier:统一风格与质量门禁
ESLint 负责代码质量与潜在缺陷,Prettier 负责格式统一,两者职责不同但需协同。Nest CLI 生成的工程已内置 @nestjs/eslint-config 与 Prettier,建议在 .eslintrc.js 中补充团队规则,例如禁止 any、要求显式返回类型、限制 console.log:
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'warn',
'no-console': ['warn', { allow: ['warn', 'error'] }],
}
配合 pnpm run lint 与 pnpm run format,再通过 husky + lint-staged 在提交前自动执行,就能形成“本地检查—提交拦截—CI 复核”的三层门禁。Prettier 的 .prettierrc 建议固定 singleQuote: true、trailingComma: 'all'、printWidth: 100,避免不同编辑器产生无意义的 diff。
环境变量与多环境配置加载
企业服务通常需要区分本地、测试、预发、生产四套环境。NestJS 官方推荐 @nestjs/config,它基于 dotenv 并支持配置校验与类型化读取。安装后按环境准备文件:.env.development、.env.test、.env.production,公共变量放 .env。加载顺序为“先公共、后环境覆盖”,因此环境文件中的同名变量优先级更高。
// app.module.ts
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [`.env.${process.env.NODE_ENV || 'development'}`, '.env'],
validationSchema: Joi.object({
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().uri().required(),
JWT_SECRET: Joi.string().min(32).required(),
}),
})
通过 validationSchema 做启动期校验,缺少 DATABASE_URL 或 JWT_SECRET 时应用会直接启动失败并打印具体字段,而不是在运行到某个接口时才抛出难以定位的错误。业务代码中统一用 ConfigService.get<string>('DATABASE_URL') 读取,禁止直接访问 process.env,这样配置来源可追踪、可测试、可替换。
.env.* 文件加入 .gitignore,仓库内仅保留 .env.example 作为字段说明。标准化基线的整体关系
下图把本节五个环节串成一条从工程生成到运行期配置的依赖链,帮助理解各部分的输入输出与先后关系。
这条链路的价值在于:CLI 保证结构一致,严格模式保证类型安全,ESLint/Prettier 保证风格一致,多环境配置保证部署可复现。四者叠加后,新成员克隆仓库、执行 pnpm install 与 pnpm run start:dev 即可获得与团队完全一致的开发环境,无需口头约定“记得开严格模式”或“别提交 .env”。
可直接落地的基线检查清单
完成上述配置后,可用以下清单快速自检,每项都对应一个可观察的验证动作:
- 执行
npx tsc --noEmit,无类型错误输出,说明严格模式已生效。 - 故意写一个未使用的变量,
pnpm run lint应报错而非静默通过。 - 删除
.env.development中的JWT_SECRET后启动,应用应失败并提示该字段缺失。 - 执行
git commit时,lint-staged 自动运行且格式化被修改的文件。 - 仓库中不存在
.env.production,仅保留.env.example作为字段模板。
把这五项纳入 CI 的初始检查,脚手架阶段的工作就真正转化为了团队可持续依赖的工程约束,而不是一次性的初始化动作。