Skip to content

NestJS 环境变量配置读取技巧

在 NestJS 项目中,环境配置管理是一个重要话题。本文介绍如何优雅地管理环境变量、配置文件,以及如何进行验证。

一、环境变量基础

1.1 使用 @nestjs/config

NestJS 提供了 @nestjs/config 包来管理配置:

bash
pnpm install @nestjs/config

1.2 基础配置

typescript
// app.module.ts
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,  // 全局可用,无需重复导入
    }),
  ],
})
export class AppModule {}

1.3 在代码中读取环境变量

typescript
import { ConfigService } from '@nestjs/config';

@Injectable()
export class UserService {
  constructor(private configService: ConfigService) {}

  getDbUrl() {
    return this.configService.get<string>('DATABASE_URL');
  }
}

二、使用 .env 文件

2.1 安装依赖

bash
pnpm install dotenv

2.2 环境文件示例

bash
# .env
DB_HOST=127.0.0.1
DB_PORT=3306
DB_URL=www.example.com

# .env.development
DB=mysql-dev
DB_HOST=127.0.0.1
DB_URL=www.imooc.com

# .env.production
DB=mysql-prod
DB_HOST=192.168.1.1
DB_URL=www.imooc-prod.com

2.3 配置加载多个 .env 文件

typescript
// app.module.ts
const envFilePath = `.env.${process.env.NODE_ENV || 'development'}`;

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath,  // 指定环境文件
    }),
  ],
})

2.4 多文件加载与优先级

优先级:靠前的文件优先级更高

typescript
// 数组形式,前面的优先级高
const envFilePath = [
  `.env.${process.env.NODE_ENV || 'development'}`,
  '.env'
];

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath,
    }),
  ],
})

加载顺序示例(development 环境):

  1. .env.development - 最高优先级
  2. .env - 作为默认值补充

三、使用 YAML 配置文件

3.1 安装依赖

bash
pnpm install js-yaml
pnpm install -D @types/js-yaml

3.2 YAML 配置示例

yaml
# config/default.yaml
token_secret: long-secret
db:
  host: "localhost"
  port: 5432

# config/development.yaml
db:
  host: "dev-local"
  port: 3306

# config/production.yaml
db:
  host: "prod-server"
  port: 5432

3.3 加载 YAML 配置

typescript
import { ConfigModule } from '@nestjs/config';
import * as yaml from 'js-yaml';
import * as fs from 'fs';

const getConfig = () => {
  const env = process.env.NODE_ENV || 'development';
  const defaultConfig = yaml.load(
    fs.readFileSync('config/default.yaml', 'utf8')
  );
  const envConfig = yaml.load(
    fs.readFileSync(`config/${env}.yaml`, 'utf8')
  );
  return { ...defaultConfig, ...envConfig };
};

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [getConfig],
    }),
  ],
})

四、使用 Enum 定义变量名

使用枚举可以避免硬编码字符串,提供类型安全:

4.1 定义枚举

typescript
// enum/config.enum.ts
export enum ConfigEnum {
  DB = 'DB',
  DB_HOST = 'DB_HOST',
  DB_PORT = 'DB_PORT',
  DB_URL = 'DB_URL',
}

4.2 使用枚举读取

typescript
import { ConfigEnum } from './enum/config.enum';

@Injectable()
export class UserService {
  constructor(private configService: ConfigService) {}

  getDbHost() {
    return this.configService.get<string>(ConfigEnum.DB_HOST);
  }

  getDbUrl() {
    return this.configService.get<string>(ConfigEnum.DB_URL);
  }
}

五、使用 cross-env 设置环境

5.1 安装依赖

bash
pnpm install -D cross-env

5.2 配置启动脚本

json
{
  "scripts": {
    "start:dev": "cross-env NODE_ENV=development nest start --watch",
    "start:prod": "cross-env NODE_ENV=production node dist/main"
  }
}

为什么需要 cross-env?

跨平台设置环境变量:

  • Windows:set NODE_ENV=production
  • Unix/Mac:NODE_ENV=production
  • cross-env 统一了两种平台的差异

六、使用 Joi 验证环境变量

6.1 安装依赖

bash
pnpm install joi

6.2 配置验证规则

typescript
import * as Joi from 'joi';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
      validationSchema: Joi.object({
        // 环境必须是 development 或 production
        NODE_ENV: Joi.string()
          .valid('development', 'production')
          .default('development'),

        // 端口必须是数字,默认 3306
        DB_PORT: Joi.number().default(3306),

        // URL 必须是合法域名
        DB_URL: Joi.string().domain(),

        // HOST 必须是合法 IP
        DB_HOST: Joi.string().ip(),

        // 必填项
        API_KEY: Joi.string().required(),
      }),
    }),
  ],
})

6.3 验证时机

验证在应用启动时进行,验证失败会导致应用启动失败,提前发现配置问题。

七、完整配置示例

typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';

// 靠前的元素优先级高
const envFilePath = [
  `.env.${process.env.NODE_ENV || 'development'}`,
  '.env'
];

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath,
      validationSchema: Joi.object({
        NODE_ENV: Joi.string()
          .valid('development', 'production')
          .default('development'),
        DB_PORT: Joi.number().default(3306),
        DB_URL: Joi.string().domain(),
        DB_HOST: Joi.string().ip(),
      }),
    }),
    UserModule,
  ],
})
export class AppModule {}

八、配置优先级总结

从高到低的优先级顺序:

1. 环境变量 (process.env)
2. .env.${NODE_ENV} 文件
3. .env 文件
4. load() 函数加载的配置
5. 默认值

九、最佳实践

实践说明
.env 加入 .gitignore避免敏感信息泄露
.env.example 提供模板让其他人知道需要哪些环境变量
使用 Joi 验证启动时发现配置问题
使用 Enum避免硬编码,提供类型安全
使用 cross-env跨平台兼容
isGlobal: true避免每个模块都导入 ConfigModule

十、常见问题

Q: 为什么我的环境变量读取不到?

A: 检查以下几点:

  1. .env 文件是否在项目根目录
  2. envFilePath 路径是否正确
  3. 环境变量名是否拼写一致

Q: 如何在 main.ts 中使用配置?

A: 可以使用 app.get(ConfigService) 或直接使用 process.env

typescript
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const port = app.get(ConfigService).get('PORT', 3000);
  await app.listen(port);
}

基于 VitePress 构建