莴莴莴笋

NestJS 配置维护

NestJS 提供了开箱即用的模块 @nestjs/config 用来管理 NestJS 应用的配置,官方文档 已经提供了足够详细的案例,很容易就能上手使用,不过在实际开发过程中还是会遇到一些没有被很好解决的痛点,于是写一篇文章总结一下。

main.ts 中使用

const configService = app.get<ConfigService>(ConfigService)   
await app.listen(configService.get('SERVER_PORT'))

NestFactory.create() 中使用

由于 ConfigService是由 Nest 管理的,在调用 NestFactory.create 之后 ConfigServic 才会被实例化,此时想要获取配置就得另外想办法。我的解决办法是通过 NestFactory.createApplicationContext()创建一个没有实际意义的 configApp,通过 configApp 获取 ConfigService,然后再创建真正的 app。

第一步先把 ConfigModule 抽离出来,configApp 只需要引入这一个模块。

// custom-config.module.ts
@Module({
    imports: [
        ConfigModule.forRoot({
            isGlobal: true,
            envFilePath: ['.env.development'],
            ignoreEnvFile: process.env.NODE_ENV === 'production',
            load: [redisConfig],
            validationOptions: {
                abortEarly: true,
            },
        }),
    ],
})
export class CustomConfigModule {}
// app.module.ts
@Module({
    imports: [CustomConfigModule, TaskModule, QueueModule],
})
export class AppModule {}

单独为 CustomConfigModule 创建 configApp

// main.ts
const configApp = await NestFactory.createApplicationContext(
    CustomConfigModule
)
const config = configApp.get<ConfigService>(ConfigService)
const options = {
    host: config.get('redis.host'),
    port: config.get('redis.port'),
}
await configApp.close()
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
        transport: Transport.REDIS,
        options,
    }
)

在无法注入依赖地方使用

这也是一个常见的需求,ConfigService 只能在能注入依赖的地方使用,不过 ConfigService 实例化完成后,在配置文件中定义的配置会被加载到process.env中,此时可以通过process.env获取配置。

// redis.config.ts
export default registerAs('redis', () => {
    const schema = Joi.object<{
        host: string
        port: number
    }>({
        port: Joi.number().required(),
        host: Joi.string().required(),
    })

    const { error, value } = schema.validate({
        port: process.env.REDIS_PORT,
        host: process.env.REDIS_HOST,
    })

    if (error) {
        throw error
    }
    return value
})
// app.module.ts
@Module({
    imports: [
        ConfigModule.forRoot({
            envFilePath: ['.env.development'],
            load: [redisConfig],
        }),
    ],
})
export class AppModule {}

然后在需要使用配置的地方直接加载就行了,需要注意的是必须在 app 启动后才能获取到正确的值。

// util.ts
import redisConfig from './redis.config.ts'

const options = redisConfig() // 此时app尚未启动完成,获取到的值不正确

export function getConfig() {
    const options = redisConfig() // 正确的获取方式
}

配合 Prisma

Prisma 有自己的一套配置,默认使用 .env 作为配置文件,但是实际使用中通常是需要在不同的环境加载不同的配置文件,要想为 Prisma 加载不同的环境变量,需要通过 dotenv-cli 来实现。

安装 dotenv-cli

pnpm add -D dotenv-cli

package.json中配置一下 scripts

{
	"prisma:dev": "dotenv -e .env.development prisma",
    "prisma:prod": "dotenv -e .env.production prisma"
}

执行 pnpm prisma:dev会加载 .env.development,执行 pnpm prisma:prod则会加载.env.production