Nest.Js透過env檔載入TypeOrmModule參數
前言
在剛開始學習時,很自然地都照的官方文件就寫在forRoot
裡,在學習過程還不會想太多。
但真的要嘗試在自己專案使用時,就會發現所有參數都寫死在程式裡,絕對不是一種好的作法
尤其又像資料庫ip、帳號、密碼,這種有可能有其他原因而有變動的情況
如果環境比較新穎,使用docker的話,這問題還算好處理。寫在process.ENV
裡面,基本上還可以做到動態載入,那就不必特別修改了。
但若還沒打算用到微服務的架構,則將這些參數拉出來放在env檔裡,再透過configService統一載入,會是較好的作法!
TypeOrmModule透過ENV載入參數
所幸已有人提問,且原作者也做了相應更新
在TypeOrmModule
裡,已提供forRootAsync
可動態載入參數。
ConfigService
寫法,與官方文件一樣。就是直接載入dev檔
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
// name: 'MY_DB_NAME',
useFactory: async (configService: ConfigService) => ({
retryAttempts: 10,
retryDelay: 30 * 1000,
type: 'postgres',
host: configService.get('DB_HOST') || 'localhost',
port: Number(configService.get('DB_PORT')) || 5432,
username: configService.get('DB_USERNAME') || 'dev',
password: configService.get('DB_PASSWORD') || 'dev',
database: process.env.POSTGRES_DB || 'MY_DEMO_DB',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: configService.get('DB_SYNCHRONIZE') === 'true' || configService.get('DB_SYNCHRONIZE') === 'TRUE',
logging: true,
logger: 'file',
}) as TypeOrmModuleOptions,
inject: [ConfigService],
}),
除了useFactory
的寫法之外,也可使用useClass
。可參閱此篇Nest.Js原作者提供的示例
使用forRootAsync
幾個要注意的地方
如果事情有這麼簡單直接設定就好了
尤其專案已經寫的差不多,才想到要把這塊抽出來。一改完絕對是滿滿的ERROR噴發
官方文件沒特別寫到,勢必裡面就有不少坑…
分享一下剛從坑裡爬出來的經驗Q_Q
傳入的參數要做型別轉換
因由env載入的資料,都是string
像port、boolean,就需要再做型別轉換
轉數字:Number(configService.get('DB_PORT'))
轉boolean:configService.get('DB_SYNCHRONIZE') === 'true'
因JavaScript本身有許多奇妙動態轉換,直接使用Boolean(configService.get('DB_SYNCHRONIZE'))
,會因字串有值而永遠回傳true
所以需要透過===
做精確判斷,若要顧慮大小寫問題,則如上方範例這樣書寫即可。
明明參數是從forRoot
複製貼上,TypeScript卻報錯?
請留意程式碼最後方,多了強制轉型as TypeOrmModuleOptions
不知什麼原因,TypeScript居然無法正確解析出他的參數!
而Nest.Js作者也沒特別去改善,也是透過強制轉型將報錯的部份轉型過去(見此處留言)
測試發現,type
、synchronize
、logger
,都需要特別轉型才不會報錯。
後來該留言下方有人提出直接將整包參數轉型TypeOrmModuleOptions
,算是一個比較好的解法。也好讀許多!
name的參數,無法透過env動態載入
若專案需要透過typeOrm連接多個資料庫的需求,隨便Google一下也會發現可設定name
來做到
但用forRootAsync
,就沒辦法將此參數由外部傳入了
因為是設定在useFactory
之外
另外,若沒有連接多個資料庫的需求,就不必特別設定name
,由Nest.Js自動處理就好。
因name
的目的是當有多資料庫時,做為區分資料庫使用
會在@InjectRepository(Photo, 'ANOTHER_DB')
中,傳入不同資料庫的名字
有設定的話,就必須在每個@InjectRepository
中傳入相應名字,才會正確解析資料表
後記
我自己有些transaction寫法是透過typeOrm注入connection來做的
即connection.transaction(async (entityManager) => { ... })
發現改成forRootAsync
時,會出現一直找不到connection,弄了好久還是搞不定
最後直接統一改成注入@InjectEntityManager()
,使用entityManager.transaction({...})
來處理這塊,就不在報錯了
具體這兩個有什麼差別也不太清楚,但總算順利透過env檔傳遞typeOrm的參數了!
參考資料
github / Can’t init TypeOrmModule using factory and forRootAsync
github / Proper way to initialize config
TypeOrmModule.forRootAsync can’t read name property correctly