NestJS 如何配置环境变量

112 阅读3分钟

所需依赖

pnpm i @nestjs/config js-yaml lodash
pnpm i @types/js-yaml @types/lodash cross-env -D

目录结构

|- config
| |- config.yaml # 通用配置文件
| |- config.local.yaml # 通用配置文件本地版(不会被 Git 记录)
| |- config.development.yaml # 开发环境配置文件版
| |- config.development.local.yaml # 开发环境配置文件版(不会被 Git 记录)
| |- config.production.yaml # 生产配置文件本地版
| |- config.production.local.yaml # 生产配置文件本地版(不会被 Git 记录)
|- src
| |- common
|   |- config.module.ts # 自定义的环境配置模块
| |- app.module.ts
|- .gitignore
|- nest-cli.json

涉及文件

1、package.json

{
  "scripts": {
    "build": "cross-env NODE_ENV=production nest build",
    "start": "cross-env NODE_ENV=development nest start",
    "start:dev": "cross-env NODE_ENV=development nest start --watch",
    "start:debug": "cross-env NODE_ENV=development nest start --debug --watch",
    "start:prod": "node dist/main",
    "start:deploy": "node main.js",
  },
  "dependencies": {
    "@nestjs/config": "^4.0.2",
    "js-yaml": "^4.1.1",
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "@types/js-yaml": "^4.0.9",
    "@types/lodash": "^4.17.21",
    "cross-env": "^10.1.0"
  }
}

2、config.module.ts

import { join } from 'path'
import jsYaml from 'js-yaml'
import { merge } from 'lodash'
import { Module } from '@nestjs/common'
import { readFileSync, existsSync } from 'fs'
import { ConfigModule as NestConfigModule } from '@nestjs/config'

@Module({
  imports: [NestConfigModule.forRoot({ load: [configuration], isGlobal: true, cache: true })],
})
export class ConfigModule {}

/**
 * 读取指定路径的 YAML 配置文件并转为对象类型
 * @param filePath YAML 配置文件的路径
 * @returns 解析后的配置对象,如果文件不存在则返回空对象
 */
function loadConfig(filePath: string): Record<string, any> {
  return existsSync(filePath) ? (jsYaml.load(readFileSync(filePath, 'utf-8')) as Record<string, any>) : {}
}

/**
 * 配置加载核心函数:读取并合并多环境 YAML 配置文件
 *
 * 功能说明:
 *  - 根据 NODE_ENV 区分开发/生产环境,确定配置文件存放目录
 *  - 按优先级读取4类 YAML 配置文件(通用→通用本地→环境→环境本地)
 *  - 深度合并配置对象,后续配置会覆盖前面的同名嵌套属性
 *  - 最终返回合并后的完整配置,供 NestJS ConfigModule 使用
 *
 * 配置优先级(从低到高,后面覆盖前面):
 *  - config.yaml < config.local.yaml < config.{NODE_ENV}.yaml < config.{NODE_ENV}.local.yaml
 */
function configuration() {
  // 从环境变量中获取当前运行环境
  const { NODE_ENV } = process.env

  // 确定配置文件的存放目录
  //  - 开发环境: 读取项目根目录下的 config 文件夹
  //  - 生产环境: 直接读取当前文件所在的目录
  const CONFIG_DIR_PATH = NODE_ENV === 'development' ? join(process.cwd(), 'config') : __dirname

  // 定义各类配置文件的路径
  // 通用配置文件(所有环境共享的基础配置)
  const YAML_COMMON_CONFIG_PATH = join(CONFIG_DIR_PATH, 'config.yaml')
  // 通用本地配置文件(本地开发覆盖通用配置的个性化设置,不应提交到代码仓库)
  const YAML_COMMON_CONFIG_LOCAL_PATH = join(CONFIG_DIR_PATH, 'config.local.yaml')
  // 特定环境的配置文件(针对当前环境的专用配置,如开发/生产环境的差异配置)
  const YAML_ENV_CONFIG_PATH = join(CONFIG_DIR_PATH, `config.${NODE_ENV || 'development'}.yaml`)
  // 特定环境的本地配置文件(本地覆盖当前环境配置的个性化设置,不应提交到代码仓库)
  const YAML_ENV_CONFIG_LOCAL_PATH = join(CONFIG_DIR_PATH, `config.${NODE_ENV || 'development'}.local.yaml`)

  // 读取各类配置文件的内容
  const COMMON_CONFIG = loadConfig(YAML_COMMON_CONFIG_PATH)
  const COMMON_CONFIG_LOCAL = loadConfig(YAML_COMMON_CONFIG_LOCAL_PATH)
  const ENV_CONFIG = loadConfig(YAML_ENV_CONFIG_PATH)
  const ENV_CONFIG_LOCAL = loadConfig(YAML_ENV_CONFIG_LOCAL_PATH)

  // 深度合并配置,后面的配置会覆盖前面的同名配置
  // 这样既保证了基础配置的通用性,又允许环境和本地配置进行个性化定制
  const finalConfig = merge(COMMON_CONFIG, COMMON_CONFIG_LOCAL, ENV_CONFIG, ENV_CONFIG_LOCAL)

  return finalConfig
}

3、app.module.ts

import { Module } from '@nestjs/common'
import { ConfigModule } from './common/config.module.ts'

@Module({
  imports: [ConfigModule]
})
export class AppModule {}

4、.gitignore

# dotenv environment variable files
.env.local
*.local.yaml

5、nest-cli.json

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "assets": [
      { "include": "../package.json", "outDir": "./dist/package.json" },
      { "include": "../pnpm-lock.yaml", "outDir": "./dist/pnpm-lock.yaml" },
      { "include": "../config/config.yaml", "outDir": "./dist" }
    ],
    "deleteOutDir": true,
    "builder": "webpack"
  },
  "generateOptions": {
    "flat": false,
    "spec": false
  }
}

6、配置参考

# config/config.yaml

# 服务器相关配置
server:
  port: 3000 # 服务器监听端口(客户端请求需访问此端口)
  globalPrefix: 'api' # 全局路由前缀(所有接口路径会自动添加此前缀,如访问接口实际路径为 /api/xxx)
  defaultPassword: '123456' # 默认的系统登录密码(新用户注册或重置密码时的初始密码)
  isDemo: false # 是否演示环境
  
# 数据库相关配置
database:
  host: '127.0.0.1' # 数据库服务器地址(本地数据库填写 127.0.0.1,远程数据库填写对应 IP 或域名)
  port: 3306 # 数据库服务器端口(MySQL 默认端口为 3306)
  username: 'root' # 连接数据库的用户名(需拥有对应数据库的操作权限)
  password: 'root' # 连接数据库的密码(与用户名对应)
  database: 'nestjs' # 要连接的具体数据库名称
  synchronize: false # 数据库结构自动同步开关(true 时启动时自动创建表结构,生产环境必须设为 false,避免数据丢失)
  
# Redis 缓存相关配置
redis:
  host: '127.0.0.1'
  port: 6379
  password: 'redispassword'
  
# JWT 身份认证相关配置
jwt:
  secret: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALrVoHV5PAxYfowa # Token 加密密钥(需保密,建议生产环境使用复杂随机字符串)
  expiresIn: 1800 # Token 有效期(单位:秒,1800 秒 = 30 分钟,过期后需重新获取)
  
openai:
  baseURL: https://api.deepseek.com
  apiKey: sk-zq7G7UOQXlKMWcxYRBXzJc6dV1RulLzmBMJYCBrhxRHIgjE7r
  
crypto:
  RsaPublicKey: |
    -----BEGIN PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC61aB1eTwMWH6MGhQYu4PUFV22
    zq7G7UOQXlKMWcxYRBXzJc6dV1RulLzmBMJYCBrhxRHIgjE7r/FauWDgmKkIPZBw
    JOFUNZ+GhLpZFWqTRlvq00NqOvM7zxkpNqxyxnOGi94xJStk8L27I9hc8emie+Fe
    XEJmVm387NdqQa9DuQIDAQAB
    -----END PUBLIC KEY-----
  RsaPrivateKey: |
    -----BEGIN PRIVATE KEY-----
    MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALrVoHV5PAxYfowa
    FBi7g9QVXbbOrsbtQ5BeUoxZzFhEFfMlzp1XVG6UvOYEwlgIGuHFEciCMTuv8Vq5
    YOCYqQg9kHAk4VQ1n4aEulkVapNGW+rTQ2o68zvPGSk2rHLGc4aL3jElK2Twvbsj
    2Fzx6aJ74V5cQmZWbfzs12pBr0O5AgMBAAECgYAZjga4irCP77q/ZwHAsEVDyPhc
    fMCjLOjE4mHgGJg+qnFmJJGyK7O8vFNqEtSSPemPX+kix8v8IgCdLCX4EmlsH0AO
    mFo5QwX9Nay2/jw2fwxsO87D7yKD0z/laZw5bRtaKYog6imqtLLPJXI4TkPJOJha
    i0Ps+fxr6qJ+98ff0QJBAONrVJ9adhQmw6Hucn2zk4oFIWArjHISREmPVH33vZgQ
    YyLK7RtEq3t3kp3na3UH3z5K+iFEF5oNjhnHO6WhxG8CQQDSUJQHC60z4ok0/ZLj
    eaJQE0txLgkGft50LsRXycEjSMVeq+1wwMYSEzbLldkkWOyVKRfuunH60ro/D3Nt
    l55XAkEA3dUZM2OzIE77r13/chwTw6LjfoKcOb0VvUdBXKqm1sjNWufkxx+BwirN
    6pcNW5f1LDdIq+BWwV+NiBmuVg9oowJAZaXyVZFsLgaQYB8qrmzsaR1aOiKQ1CXf
    aHHuDawCgAY89mvMP9G8KaJFupH7OBkOh20sFwLf8eWFhNL37AD60wJBAJUIjI/V
    JDUrA0ONcI0xFkWU+TuwYj4CY8s4eo7oSXrOjhy/4Ee57RfiBEpNF18AA9BCTvSS
    GUd7GrqUUME8EP8=
    -----END PRIVATE KEY-----

使用介绍

1、service 服务使用

import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'

@Injectable()
export class LoginService {
  constructor(private readonly configService: ConfigService) {}
  
  public testEnvConfig() {
    const dbHost = this.configService.get<string>('database.host','localhost')
  }
}

2、main.ts 中使用

import { AppModule } from './app.module'
import { ConfigService } from '@nestjs/config'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  
  // 从应用容器中获取配置服务实例,由于 ConfigModule 被设置为全局模块,这里可以直接获取
  const configService = app.get(ConfigService)
  const serverPort = configService.get<number>('server.port', 3000)
  
  // 启动应用并监听配置中指定的端口
  // 这一步会启动 HTTP 服务器,使应用开始接收外部请求
  await app.listen(serverPort)
  
  // 打印服务启动信息,方便开发者在终端查看访问地址
  console.log('\n--------------------------------------------------')
  console.log(`➜  Local:    http://localhost:${serverPort}`)
  console.log('--------------------------------------------------')
}

// 调用启动函数,启动应用
bootstrap()