iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 27
0
Modern Web

讀官網文件邊走邊學nest.js系列 第 27

Day27 存取env變數使用dotenv-儲存敏感資訊

大部分不會把資料庫連線帳號、密碼等相關訊息寫在程式碼裡面,通常寫在一個檔案裡面,程式裡面用key來讀取value,ASP.NET Core裡面是寫在appsettings.json,透過Configuration存取appsetting.json

nestjs是採用dotenv套件,使用Config Service存取dotenv下的變數。

dotenv是透過讀取.env檔,把key-value pair存到node.js下process.env

假設把資料庫連線的相關訊息存到.dev,檔名可以自行決定,比較多是跟環境有關,比如開發環境就development.env,正式環境就prod.env,未來採用docker或kubernetes可以傳入NODE_ENV來切換,取得該環境下的資料庫連線資訊

把資料庫相關連線資訊放到development.env,這個檔案應該加入gitignore

APP_NAME=IronMan2019_Nestjs

DB_NAME=users
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PW=root
DB_TYPEORM_SYNC=true
DB_TYPEORM_LOG=true

使用Config Service可以更方便在各個nestjs module存取process.env,只要注入Config Service。

新增config.service

export class ConfigService {

    private readonly envConfig: {[key:string]:string}

    constructor(filePath: string) {
        // 讀取.env檔,透過dotenv.parse方法形成key-value pairs
        // 存在envConfig變數裡
        this.envConfig= dotenv.parse(fs.readFileSync(filePath))
    }

    // 傳進來key,回傳value
    get(key:string){
        return this.envConfig[key];
    }

    // 可以寫方法處理env變數,這樣也比較好除錯
    getDbPassword(){
        return this.envConfig['DB_PW'];
    }
    
}

為了在其他的module可以重複利用Config Service(import後注入(inject)),建立config.module.ts

@Module({
    providers: [
        // 這是nestjs另外一種Dependency Injection的方式
        {
            // 如果nestjs IoC Container要ConfigService的時候
            provide: ConfigService,
            // 回傳"這個"值
            // 剛剛的ConfigService要傳入.env路徑及檔名
            useValue: new ConfigService(`${process.env.NODE_ENV || 'development'}.env`)
        }
    ],
    // export表示這個Module被import後,ConfigService可以被其他Module Inject
    exports: [ConfigService]
})
export class ConfigModule {}

先import config module到app module並修改app controller測試

app.module.ts

@Module({
  imports: [
    ConfigModule,
    SharedModule,
 ...
})
export class AppModule implements NestModule{

app.controller.ts

@Controller()
export class AppController {
  // 注入Config Service
  constructor(private configService: ConfigService){
  }
  @Get()
  getAppIndex(){
    // 用get並傳入.env底下APP_NAME這個key
    return this.configService.get('APP_NAME');
  }
}

使用postman測試

再來要refactor TypeORM載入的資料庫連線資訊,主要參考官網Database async部分

有兩種做法,都是在typeorm初始畫資料庫連線之前,要import Config Service取的process.env變數

  • useFactory
    • 指定指定TypeOrmOptions
  • useExisting
    • 另外建立TypeOrmConfigService實作TypeOrmOptionsFactory

TypeOrmOptions就是資料庫連線資訊及其他設定如retry、alive time等

照官網作useFactory不成功,在指定type變數部分,typescript complier會出現型別不符合expoConnectionOption,這個問題要另外找答案

錯誤訊息如下

而另外建立TypeOrmConfigService來提供forRootAsync所需要的TypeOrmOptions可以測試OK

建立TypeOrmConfigService

@Injectable()
export class TypeOrmConfigService 
    // 需要實作TypeOrmOptionsFactory
        implements TypeOrmOptionsFactory {
    // 注入config service取得env變數
    constructor(private readonly configService: ConfigService) {}
    // 就是回傳TypeOrmOptions物件
    createTypeOrmOptions(): TypeOrmModuleOptions{
        return {
                type: 'postgres',//configService.get('DB_TYPE') as DatabaseType,
                host: this.configService.get('DB_HOST'),
                port: Number(this.configService.get('DB_PORT')),
                username: this.configService.get('DB_USERNAME'),
                password: this.configService.getDbPassword(),
                database: this.configService.get('DB_NAME'),
                synchronize: this.configService.get('DB_TYPEORM_SYNC') === 'true',
                logging: this.configService.get('DB_TYPEORM_LOG') === 'true',
                ...
        };
    }
}

修改在shared module typeorm設定

@Module({
    imports: [
            // 涉及非同步載入Connection Option的時候,改用forRootAsync
            TypeOrmModule.forRootAsync({
                // 會利用ConfigService,所以要import
                imports: [ConfigModule],
                inject: [
                    // 宣告哪個provider或是service需要被注入
                    ConfigService,
                ],
                // 指定用TypeOrmConfigService,作為載入TypeOrmOptions
                // Options就是資料庫連線資訊等
                useClass: TypeOrmConfigService,
            }),
    ],
    ...
}
export class SharedModule {}

使用postman測試

Github Source Code


上一篇
Day26 用Nestjs Decorator建立Swagger API Doc
下一篇
Day28 用nestjs建立API Microservice(上)
系列文
讀官網文件邊走邊學nest.js31

尚未有邦友留言

立即登入留言