何を作成する?
Nest.jsで環境ごとにデータベースの接続先を分けるために,接続情報を実行環境の環境変数から非同期で取得し作成する.
環境
- Node.js v12.14.1
- Nest.js v6.7.2
- TypeORM v0.2.22
- Postgresql v11.6
実装ログ
必要最小限の実装
参考)Nest.js Document > TECHNIQUES >Database
ライブラリインストール
TypeORM, Database Driver (Postgresql)をインストールする.
$ npm install @nestjs/typeorm typeorm pg
DB接続情報を定義
app.module.tsにデータベースの接続情報を定義する.
import{Module}from'@nestjs/common';import{TypeOrmModule}from'@nestjs/typeorm';import{ItemModule}from'./item/item.module';import{Connection}from'typeorm';import{join}from'path';@Module({imports:[ItemModule,// DBの接続情報を定義TypeOrmModule.forRoot({type:'postgres',host:'localhost',port:5432,username:'postgres',password:'postgres',database:'postgres',entities:[join(__dirname+'/**/*.entity{.ts,.js}')],synchronize:false,}),],})exportclassAppModule{}
一番シンプルな書き方です.自分一人しか触らず,環境もこれだけ!ということであればこの書き方で良いでしょう.
しかし,実際の開発では個人の開発環境,テスト環境,ステージング環境,本番環境と複数の環境が存在し,上記のような実装では環境ごとに接続情報をハードコードし直し → ビルド → デプロイという手順を踏む必要がありナンセンスです.
そのため,通常は環境変数に定義し,接続情報はその環境変数を参照し作成します.
と,いうことで環境変数を参照するようにapp.module.tsを修正します.
環境変数を参照するように実装を修正
※この方法では実行時に依存関係が解決できずエラーとなります.
参考)Nest.js Document > TECHNIQUES > Configuration
ライブラリインストール
環境変数を参照するために必要なライブラリをインストールします.
$npminstall@nestjs/config
ダミーの環境変数を用意
本来は,環境変数に定義するのですがサンプル実装なので環境変数ファイル(.env)をプロジェクトルートに作成する.
DATABASE_HOST=localhostDATABASE_PORT=5432DATABASE_USERNAME=postgresDATABASE_PASSWORD=postgresDATABASE_NAME=postgres
環境変数を参照するように接続定義を修正
import{Module}from'@nestjs/common';import{TypeOrmModule}from'@nestjs/typeorm';import{ConfigModule,ConfigService}from'@nestjs/config';import{ItemModule}from'./item/item.module';import{Connection}from'typeorm';import{join}from'path';@Module({imports:[ItemModule,ConfigModule.forRoot({envFilePath:'.env',isGlobal:true,// ignoreEnvFile: true, <- 環境変数から取得する場合はコメントアウトを外す.}),// 非同期で環境変数から値を取得し,接続情報を作成する.TypeOrmModule.forRootAsync({imports:[ConfigModule],useFactory:async(configServide:ConfigService)=>({type:'postgres'as'postgres',host:configServide.get('DATABASE_HOST'),port:Number(configServide.get('DATABASE_HOST')),username:configServide.get('DATABASE_USERNAME'),password:configServide.get('DATABASE_PASSWORD'),entities:[join(__dirname+'/**/*.entity{.ts,.js}')],synchronize:false,}),inject:[ConfigService],}),],})exportclassAppModule{constructor(privatereadonlyconnection:Connection){}}
起動後,以下のエラーが発生.
2:25:34 PM - Found 0 errors. Watching for file changes.
[Nest] 19111 - 01/13/2020, 2:25:35 PM [NestFactory] Starting Nest application...
[Nest] 19111 - 01/13/2020, 2:25:35 PM [InstanceLoader] TypeOrmModule dependencies initialized +24ms
[Nest] 19111 - 01/13/2020, 2:25:35 PM [InstanceLoader] ConfigModule dependencies initialized +1ms
[Nest] 19111 - 01/13/2020, 2:25:35 PM [ExceptionHandler] Nest can't resolve dependencies of the TypeOrmModuleOptions (?). Please make sure that the argument ConfigService at index [0] is available in the TypeOrmCoreModule context.
Potential solutions:
- If ConfigService is a provider, is it part of the current TypeOrmCoreModule?
- If ConfigService is exported from a separate @Module, is that module imported within TypeOrmCoreModule?
@Module({
imports: [ /* the Module containing ConfigService */ ]
})
+1ms
Error: Nest can't resolve dependencies of the TypeOrmModuleOptions (?). Please make sure that the argument ConfigService at index [0] is available in the TypeOrmCoreModule context.
Potential solutions:
- If ConfigService is a provider, is it part of the current TypeOrmCoreModule?
- If ConfigService is exported from a separate @Module, is that module imported within TypeOrmCoreModule?
@Module({
imports: [ /* the Module containing ConfigService */ ]
})
at Injector.lookupComponentInExports (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:185:19)
at processTicksAndRejections (internal/process/task_queues.js:94:5)
at async Injector.resolveComponentInstance (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:142:33)
at async resolveParam (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:96:38)
at async Promise.all (index 0)
at async Injector.resolveConstructorParams (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:111:27)
at async Injector.loadInstance (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:78:9)
at async Injector.loadProvider (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:35:9)
at async Promise.all (index 3)
at async InstanceLoader.createInstancesOfProviders (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/instance-loader.js:41:9)
環境変数を参照するためのConfigService
がTypeOrmModuleOptions
内で依存関係が解決できないことが原因らしい.
同じような事象がGithubのIssueにあがっていたので参考に載せておきます.
Can't init TypeOrmModule using factory and forRootAsync
環境変数を参照するように実装を修正
app.module.tsを以下のように修正します.
import{Module}from'@nestjs/common';import{TypeOrmModule}from'@nestjs/typeorm';import{ConfigModule}from'@nestjs/config';import{ItemModule}from'./item/item.module';import{TypeOrmConfigService}from'./common/database/type-orm-config.service';@Module({imports:[ConfigModule.forRoot({envFilePath:'.env',isGlobal:true,// ignoreEnvFile: true, <- 環境変数から取得する場合はコメントアウトを外す.}),TypeOrmModule.forRootAsync({imports:[ConfigModule],// 接続情報を作成するServiceクラスを定義useClass:TypeOrmConfigService,}),ItemModule,],})exportclassAppModule{}
type-orm-config.service.ts
import{TypeOrmOptionsFactory,TypeOrmModuleOptions}from'@nestjs/typeorm';import{Injectable}from'@nestjs/common';import{ConfigService}from'@nestjs/config';import{join}from'path';/**
* DBの接続設定.
*/@Injectable()exportclassTypeOrmConfigServiceimplementsTypeOrmOptionsFactory{/**
* DBの接続設定を環境変数をもとに作成します.</br>
* 環境変数に設定されていない場合は,デフォルトの設定値を返却します.
* @returns 接続情報
*/createTypeOrmOptions():TypeOrmModuleOptions{constconfigService=newConfigService();return{type:'postgres'as'postgres',host:configService.get('DATABASE_HOST','localhost'),port:Number(configService.get('DATABASE_PORT',5432)),username:configService.get('DATABASE_USERNAME','postgres'),password:configService.get('DATABASE_PASSWORD','postgres'),database:configService.get('DATABASE_NAME','postgres'),entities:[join(__dirname+'../**/*.entity{.ts,.js}')],synchronize:false,};}}
ConfigService
をDIするのではなく,自分でnewするのがポイントです.
最後に
最近使い始めたのですが,素晴らしいフレームワークだとひしひしと感じております.
フレームワーク自体の良さはこちらの記事で紹介されています.