资讯动态

NestJS 入门(8):环境变量与配置——密钥不要写死在代码里

发布时间:2026/8/15 9:50:56 来源:尧图企业网站定制
上一篇NestJS 入门7生命周期钩子 讲了启动时连库。那些动作依赖的「连哪台库、用哪把密钥」如果写死在源码里换环境就要改代码、提交还可能把 secret 带进 Git。第三篇 JWT 示例里常见这种写法JwtModule.register({secret:process.env.JWT_SECRET||your-secret-key,signOptions:{expiresIn:30m},});process.env已经比硬编码好一截|| your-secret-key则是入门阶段最容易留下的坑本地能跑生产忘配变量时会静默用同一把弱密钥。这篇文章讲环境变量怎么进 Nest以及怎样让启动期就发现「没配齐」。1. 配置要解决的三件事问题错误做法更好的做法不同环境不同值源码里写死secret: abc从环境变量读密钥进 Git把真 key 写进仓库.env只留在本地仓库里放.env.example漏配了还在跑用弱密钥字符串兜底启动时校验缺了直接退出常见变量JWT_SECRET签发 / 验签必须一致不要默认值DATABASE_URL本地可以不配走文件存储生产必须配漏了就别起WORKER_URL这类下游地址本地可以用http://localhost:3002密钥类不要默认值地址类可以有本地默认值。2. 最简单直接读process.env不引入nestjs/config也能工作exportfunctionusePostgresPersistence():boolean{returnBoolean(process.env.DATABASE_URL?.trim());}JwtModule.register({secret:process.env.JWT_SECRET,// 没有就留给启动校验去炸不要写弱兜底});constworkerUrlprocess.env.WORKER_URL||http://localhost:3002;密钥和地址的差别就在这两段JWT_SECRET缺了不该还能起WORKER_URL本地缺了指向本机即可。够用的前提进程启动前已经把环境变量注入shell、本地.env、Docker-e你接受「模块文件被 import 时就读一次process.env」这正是很多小型 Nest 项目的现状。入门可以先这样跑通再升级到下面的ConfigModule。3. 直接读process.env的三个坑坑 1弱密钥兜底secret:process.env.JWT_SECRET||your-secret-key;生产漏配JWT_SECRET时服务照样起任何人都能用公开兜底串伪造 token。坑 2同一配置写两处容易不一致签发在 ModuleJwtModule.register({secret:process.env.JWT_SECRET,});验签在 Strategysuper({secretOrKey:process.env.JWT_SECRET,});两处必须读同一个变量。改了一边忘了另一边就是「能登录、受保护接口全 401」。后面用ConfigService读一次就是为了少一处手抄。坑 3register({ secret: process.env.X })发生得太早JwtModule.register({...})在模块类被求值时就读环境变量。若你后来才config()加载.env这里可能已经是undefined。更稳的是启动时加载配置 → 再创建 App或改用下面的registerAsync。4. 仓库里放.env.example不放真.env约定.env ← 真实密钥加入 .gitignore .env.example ← 变量名 假值 / 注释可以提交示例# 复制为 .env 后填真实值JWT_SECRET请换成足够长的随机串# 本地可不设走文件存储生产必须设# DATABASE_URLpostgresql://user:pass127.0.0.1:5432/app# WORKER_URLhttp://localhost:3002新人 clone 仓库先看 example知道要配什么而不会拿到你的生产 key。本地用.env文件即可。生产把变量注入进进程Docker-e、编排 secrets不要把.env打进镜像。5. 升级ConfigModuleConfigServicenpmi nestjs/config根模块加载一次import{ConfigModule}fromnestjs/config;Module({imports:[ConfigModule.forRoot({isGlobal:true,// 其它模块不必再 importsenvFilePath:[.env.local,.env],}),AuthModule,// ...],})exportclassAppModule{}isGlobal: true和第六篇的Global()同类配置属于基础设施适合全局。进程里已经有的环境变量例如编排注入的dotenv 默认不会被.env文件盖掉。所以生产注入的JWT_SECRET不会被镜像里误带的文件改掉。业务里注入import{ConfigService}fromnestjs/config;Injectable()exportclassJwtStrategyextendsPassportStrategy(Strategy){constructor(config:ConfigService){super({jwtFromRequest:ExtractJwt.fromAuthHeaderAsBearerToken(),ignoreExpiration:false,secretOrKey:config.getOrThrowstring(JWT_SECRET),});}}getOrThrow没有这个键就抛错避免静默兜底。开关类配置仍可很薄constructor(privatereadonlyconfig:ConfigService){}usePostgres(){returnBoolean(this.config.getstring(DATABASE_URL)?.trim());}6. JWT 要用registerAsync才能注入 ConfigService静态register({ secret: process.env... })拿不到ConfigService。改成异步注册import{ConfigModule,ConfigService}fromnestjs/config;import{JwtModule}fromnestjs/jwt;Module({imports:[JwtModule.registerAsync({imports:[ConfigModule],inject:[ConfigService],useFactory:(config:ConfigService)({secret:config.getOrThrowstring(JWT_SECRET),signOptions:{expiresIn:30m},}),}),],providers:[AuthService,JwtStrategy,JwtAuthGuard],exports:[AuthService,JwtAuthGuard],})exportclassAuthModule{}好处签发和验签都从同一ConfigService读JWT_SECRET缺变量时启动失败而不是带着弱密钥上线读配置发生在 Nest 容器就绪之后不再和「何时 loadDotenv」抢跑7. 启动时校验缺关键变量就别 listen比「用到了才 500」更好的是启动直接挂。校验写一处就够。还在直接读process.env、没用 ConfigModule 时放在bootstrapfunctionrequireEnv(name:string):string{constvalueprocess.env[name]?.trim();if(!value){thrownewError(缺少环境变量:${name});}returnvalue;}asyncfunctionbootstrap(){requireEnv(JWT_SECRET);constappawaitNestFactory.create(AppModule);app.enableShutdownHooks();awaitapp.listen(3000);}已经用了ConfigModule第五节就写在validate里不必再手写一遍requireEnvConfigModule.forRoot({isGlobal:true,envFilePath:[.env.local,.env],validate:(env){if(!env.JWT_SECRET?.trim()){thrownewError(JWT_SECRET 未配置);}returnenv;},});规则可以简单记变量漏了怎么办JWT_SECRET启动失败DATABASE_URL本地可不配生产必须配漏了启动失败WORKER_URL可用 localhost 默认功能开关LOG_*true缺省当关闭8. 和本系列其它篇怎么接bootstrap → 读环境 / ConfigModule.forRoot → new Provider构造函数第二篇 → onModuleInit 连库第七篇有 DATABASE_URL 才连 Postgres → Guard 用 JWT_SECRET 验签第三篇 → listen配置不是业务模块但几乎每个模块都会读它。所以它适合全局模块第六篇的Global()/isGlobal: true启动期校验和第七篇钩子互补钩子做 I/O配置做「值对不对」9. 小结密钥、库地址、下游 URL 用环境变量不要写死在仓库.env不提交.env.example提交当说明书生产用编排注入不要把.env打进镜像密钥不要|| 弱兜底地址类才可以有本地默认值进阶用ConfigModulegetOrThrowJWT 用JwtModule.registerAsync关键变量缺了应启动失败校验只写一处requireEnv或validate不要两套一起上对照本系列分层、注入、模块边界清楚了吗Guard / Pipe / 信封 / 启动钩子各管一段密钥和 URL 从哪来漏配时是启动就炸还是默默用错值系列导航上一篇NestJS 入门7生命周期钩子第六篇NestJS 入门6Module 边界与导出第五篇NestJS 入门5Pipe 与 DTO 校验第四篇NestJS 入门4统一响应与异常处理第三篇NestJS 入门3Guard 如何挡住未登录请求第二篇NestJS 入门2依赖注入到底解决了什么问题第一篇NestJS 入门1先搞懂 Module、Controller、Service

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价