最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

手把手教你如何優(yōu)雅的書寫NestJS服務配置

 更新時間:2026年04月03日 08:54:48   作者:Mr_li  
Nest.js是一個漸進的Node.js框架,可以在TypeScript和JavaScript (ES6、ES7、ES8)之上構建高效、可伸縮的企業(yè)級服務器端應用程序,這篇文章主要介紹了如何優(yōu)雅的書寫NestJS服務配置的相關資料,需要的朋友可以參考下

前言

開發(fā)服務端應用,配置肯定是少不了的。端口號、數(shù)據(jù)庫賬號、Redis 密碼、各種業(yè)務白名單……這些東西如果硬編碼在代碼里,不僅不安全,每次切環(huán)境還得改代碼,簡直是災難。

配置的來源五花八門:簡單的可以直接讀環(huán)境變量(env),稍微復雜點的用 JSON、YAML 配置文件,遇到敏感信息或者微服務架構,可能還得去 Nacos、Apollo 這樣的遠端配置中心拉取。

在 NestJS 里,寫配置的方式有很多。如果團隊沒個統(tǒng)一的規(guī)范,大家各寫各的,雖然代碼也能跑,但后期維護起來絕對會讓人抓狂。這幾年再使用 NestJS 過程中踩了不少坑,摸索出了一套比較舒服的配置管理姿勢,今天就來系統(tǒng)地梳理一遍。

1. 刀耕火種:直接硬剛 process.env

最原始、最暴力的寫法,就是直接在代碼里讀 process.env

// 這種代碼散落在項目的各個角落,后期維護極其痛苦
const port = process.env.PORT || 3000;
const dbHost = process.env.DATABASE_HOST;

這種寫法最大的問題是毫無約束。字段名字全靠腦子記,類型全都是 string | undefined,還得自己手動轉數(shù)字、轉布爾值。哪天要是改個環(huán)境變量的名字,你得全局搜索一遍才能確認是不是都改全了。所以,這只能算是個起點,正經(jīng)項目千萬別這么搞。

2. 官方標配:@nestjs/config 模塊

為了解決配置問題,NestJS 官方提供了一個 @nestjs/config 包。它的底層其實就是我們熟悉的 dotenv,這也是目前處理配置的標準入口。

先裝個包(注意:它要求 TypeScript 4.1 及以上版本):

npm i --save @nestjs/config

2.1 怎么用起來?

最基礎的用法,就是在根模塊 AppModule 里引入 ConfigModule,調一下 forRoot()。它會自動去項目根目錄找 .env 文件并解析:

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true, // 強烈建議設為全局,這樣其他模塊就不用反復 import 了
    }),
  ],
})
export class AppModule {}

?? 踩坑預警:當系統(tǒng)的環(huán)境變量和 .env 文件里出現(xiàn)了同名的 key 時,系統(tǒng)環(huán)境變量的優(yōu)先級更高.env 里的值會被無情覆蓋。這是 dotenv 的默認規(guī)則,在線上部署排查問題時一定要記住這一點。

設置了 isGlobal: true 后,你在任何 Service 里都可以直接注入 ConfigService 來拿配置了。

2.2 應對多環(huán)境

真實項目肯定不止一個環(huán)境,起碼有開發(fā)(dev)、測試(staging)、生產(chǎn)(prod)。我們可以傳個數(shù)組給 envFilePath,讓它按需加載。通常配合 NODE_ENV 動態(tài)決定加載哪個文件:

ConfigModule.forRoot({
  envFilePath: [
    `.env.${process.env.NODE_ENV}.local`, // 優(yōu)先級最高:本地覆蓋文件
    `.env.${process.env.NODE_ENV}`,        // 當前環(huán)境配置
    '.env',                                // 兜底默認配置
  ],
  isGlobal: true,
});

?? 踩坑預警:數(shù)組里越靠前的文件優(yōu)先級越高。如果幾個文件里有相同的 key,它只會認第一個找到的值,后面的文件里重復的 key 會被靜默忽略。

3. 進階:把"散裝字符串"變成結構化對象

只用 .env 有個很煩人的點:讀出來的全是一層扁平的字符串。如果配置一多,找起來費勁,而且到處都要寫 parseInt。

NestJS 允許我們寫配置工廠函數(shù),把這些零散的環(huán)境變量組裝成有結構、有類型、有默認值的對象。

// config/database.config.ts
export default () => ({
  port: parseInt(process.env.PORT, 10) || 3000,
  database: {
    host: process.env.DATABASE_HOST || 'localhost',
    port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
    user: process.env.DATABASE_USER,
    password: process.env.DATABASE_PASSWORD,
  },
});

然后在 AppModule 里通過 load 屬性把這個工廠函數(shù)塞進去:

import databaseConfig from './config/database.config';
ConfigModule.forRoot({
  isGlobal: true,
  load: [databaseConfig], // 這里是個數(shù)組,可以塞多個配置進去,互不干擾
});

用的時候,就可以用點號(.)來讀取嵌套屬性了:

const dbHost = this.configService.get<string>('database.host');
const dbPort = this.configService.get<number>('database.port');

這比到處散落的 process.env.DATABASE_HOST 好多了,至少做到了集中管理。但這種寫法還有個問題:字符串路徑 'database.host' 沒有類型提示,寫錯了只能靠運行時暴露。下面這個方案才是真正的終極解法。

4. 終極利器:命名空間(Namespace)

registerAs() 函數(shù)是 @nestjs/config 里最值得推薦的功能。它不只是給配置起個名字,更重要的是它返回的對象帶有 .KEY 屬性,可以直接用于依賴注入——這意味著你可以注入整個配置對象,而不是一個個字符串地去 get()。

4.1 劃分命名空間

把不同業(yè)務的配置拆到各自的文件里:

// config/database.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({
  host: process.env.DATABASE_HOST || 'localhost',
  port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
  name: process.env.DATABASE_NAME,
}));
// config/redis.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs('redis', () => ({
  host: process.env.REDIS_HOST || 'localhost',
  port: parseInt(process.env.REDIS_PORT, 10) || 6379,
}));

然后在根模塊統(tǒng)一加載:

ConfigModule.forRoot({
  isGlobal: true,
  load: [databaseConfig, redisConfig],
});

4.2 享受強類型的快感

用了命名空間后,可以直接注入整個配置對象,TypeScript 類型推導完整,IDE 有完整提示,字段名寫錯了編譯階段就報錯:

import { Inject, Injectable } from '@nestjs/common';
import { ConfigType } from '@nestjs/config';
import databaseConfig from './config/database.config';
@Injectable()
export class DatabaseService {
  constructor(
    @Inject(databaseConfig.KEY)
    private readonly dbConfig: ConfigType<typeof databaseConfig>,
  ) {}
  getConnection() {
    // 敲下 this.dbConfig. 的時候,IDE 會完整提示 host, port, name
    return `${this.dbConfig.host}:${this.dbConfig.port}/${this.dbConfig.name}`;
  }
}

?? 小貼士ConfigType<typeof databaseConfig> 是官方提供的工具類型,它能自動反推工廠函數(shù)的返回值結構,省得你再去手寫一遍 Interface。

4.3 絲滑對接第三方模塊

命名空間配置還有個殺手锏:.asProvider() 方法。

平時配置 TypeORM 或 Redis 這類第三方模塊時,往往要寫一長串 useFactory、injectimports。用了 .asProvider() 后,這些樣板代碼全部省掉——它的本質是把依賴聲明和工廠函數(shù)打包成了標準的 forRootAsync 入?yún)?,NestJS 會據(jù)此建立正確的模塊初始化順序:

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true, load: [databaseConfig] }),
    // 等價于手寫 imports/inject/useFactory 的完整異步配置
    TypeOrmModule.forRootAsync(databaseConfig.asProvider()),
  ],
})
export class AppModule {}

關于為什么這能保證初始化順序,以及手寫 useFactory 時要注意什么,我們在第 7.2 節(jié)會展開說。

5. 換個口味:YAML、JSON 怎么搞?

.env 用起來最順手,但它有個天然的短板:所有值讀出來都是字符串,端口、超時時間這些數(shù)字都得自己手動 parseInt,布爾值也要寫 === 'true' 來判斷,一不小心就出錯。

相比之下,YAML 和 JSON 本身就支持數(shù)字、布爾等原生類型,寫配置的時候是什么類型讀出來還是什么類型,完全不需要轉換。而且層級結構更清晰,改起來更直觀,后期維護成本也低得多。如果項目的配置項比較多,主動選擇 YAML 或 JSON 來管理是個很合理的決定。

@nestjs/configload 機制非常靈活,只要你的工廠函數(shù)最終返回一個普通對象,它才不管數(shù)據(jù)是從哪來的。

5.1 玩轉 YAML

先裝解析庫:npm i js-yamlnpm i -D @types/js-yaml。

// config/yaml.config.ts
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import * as yaml from 'js-yaml';
export default () => {
  return yaml.load(
    readFileSync(join(__dirname, 'config.yaml'), 'utf8'),
  ) as Record<string, any>;
};

?? 踩坑預警:Nest CLI 默認打包時不會拷貝非 TS 文件。如果你用了 YAML,一定要去 nest-cli.json 里配置 assets,不然打完包跑起來絕對報找不到文件的錯。

{
  "compilerOptions": {
    "assets": [{ "include": "../config/*.yaml", "outDir": "./dist/config" }]
  }
}

5.2 遠端配置中心(如 Nacos、Apollo)

如果你們用了配置中心,最常見的有兩種接入方式:

方式一:CD 流程在容器啟動前下發(fā)配置文件,把配置寫到本地磁盤,應用啟動時當普通本地文件讀,和 5.1 的 YAML 方式一模一樣,沒有額外復雜度。

方式二:應用啟動時主動拉取,把工廠函數(shù)寫成異步的,在 ConfigModule 初始化期間完成拉?。?/p>

import { registerAs } from '@nestjs/config';
export default registerAs('secret', async () => {
  // 去遠端拉配置,拉不到就拋異常,阻止應用啟動
  const data = await fetchFromConfigCenter('/api/config/secret');
  return { apiKey: data.apiKey, jwtSecret: data.jwtSecret };
});

兩種方式的選擇取決于你們的部署流程,不存在好壞之分。

6. 防患未然:配置校驗

沒有校驗的配置就像是一顆定時炸彈。萬一線上環(huán)境少配了一個數(shù)據(jù)庫密碼,應用可能照樣啟動,直到用戶發(fā)起請求才原地爆炸。

我們必須讓應用在啟動階段就把缺少的配置暴露出來。NestJS 給了兩套方案:

6.1 簡單粗暴的 Joi 校驗

import * as Joi from 'joi';
ConfigModule.forRoot({
  validationSchema: Joi.object({
    NODE_ENV: Joi.string().valid('dev', 'prod').default('dev'),
    PORT: Joi.number().default(3000),
    DATABASE_HOST: Joi.string().required(), // 必填項,沒有就不準啟動!
  }),
  validationOptions: {
    allowUnknown: true,  // 允許出現(xiàn)沒在 schema 里定義的變量
    abortEarly: false,   // 別一遇到錯就停,把所有錯一塊報出來
  },
});

?? 踩坑預警:如果你傳了 validationOptions,那些沒寫的選項會回退到 Joi 的默認值,而不是 @nestjs/config 的默認值!比如 allowUnknown,Joi 默認是 false,但 Nest 默認是 true。為了不被坑,建議把這倆屬性老老實實寫清楚。

6.2 面向對象的 class-validator

如果你更喜歡用類和裝飾器,也可以自己寫個校驗函數(shù):

import { plainToInstance } from 'class-transformer';
import { IsNumber, IsString, validateSync } from 'class-validator';
class EnvVariables {
  @IsNumber() PORT: number;
  @IsString() DATABASE_HOST: string;
}
export function validate(config: Record<string, unknown>) {
  const validated = plainToInstance(EnvVariables, config, { enableImplicitConversion: true });
  const errors = validateSync(validated, { skipMissingProperties: false });
  if (errors.length > 0) throw new Error(errors.toString());
  return validated;
}
// app.module.ts
ConfigModule.forRoot({ validate });

兩種校驗方式效果相同,Joi 更簡潔,class-validator 在項目里已經(jīng)有 DTO 校驗體系時風格更統(tǒng)一,按喜好選就行。

7. 特殊場景

7.1 局部注冊(forFeature)的生命周期坑

如果你的項目比較大,不想把所有配置都塞在 AppModule 里,可以在各自的業(yè)務模塊里按需加載:

@Module({
  imports: [ConfigModule.forFeature(databaseConfig)],
})
export class DatabaseModule {}

?? 踩坑預警:用 forFeature 注冊的配置,千萬別在構造函數(shù)(constructor)里去讀!因為模塊初始化的順序是不確定的,這時候配置可能還沒加載完。正確的做法是用屬性注入,然后在 onModuleInit 生命周期鉤子里讀取。

import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { ConfigType } from '@nestjs/config';
import databaseConfig from './database.config';
@Injectable()
export class DatabaseService implements OnModuleInit {
  // 屬性注入,不在構造函數(shù)里觸碰配置
  @Inject(databaseConfig.KEY)
  private readonly dbConfig: ConfigType<typeof databaseConfig>;
  onModuleInit() {
    // ? 到這里所有依賴模塊都已初始化完畢,安全讀取
    console.log('db host:', this.dbConfig.host);
  }
  // ? 下面這種寫法會在某些場景下讀到 undefined
  // constructor(
  //   @Inject(databaseConfig.KEY)
  //   private readonly dbConfig: ConfigType<typeof databaseConfig>,
  // ) {
  //   console.log(this.dbConfig.host); // 危險!配置可能還沒就緒
  // }
}

7.2 配置之間的依賴鏈問題

這是一個很容易被忽略但實際開發(fā)中會真實踩到的坑。

以 TypeORM 為例,完整的啟動依賴鏈是這樣的:

ConfigModule 加載 .env / 配置文件
       ↓
databaseConfig 工廠函數(shù)讀取環(huán)境變量,生成配置對象
       ↓
TypeOrmModule 拿到配置,建立數(shù)據(jù)庫連接池
       ↓
各業(yè)務 Service / Repository 可以正常使用
       ↓
應用啟動完成,開始接受請求

這條鏈上的順序必須是確定的。如果配置還沒就緒,TypeOrmModule 就開始初始化,輕則連接參數(shù)是 undefined,重則應用直接起不來。

第 4.3 節(jié)提到的 .asProvider() 能自動保證這個順序,因為它展開后等價于:

{
  imports: [ConfigModule.forFeature(databaseConfig)], // 聲明:我依賴這個配置
  useFactory: (config: ConfigType<typeof databaseConfig>) => config,
  inject: [databaseConfig.KEY],
}

imports 字段就是向 NestJS 聲明依賴關系的地方。框架看到這個聲明,就會等 databaseConfig 就緒后再初始化 TypeOrmModule,順序由框架保證,不需要你操心。

如果不用 .asProvider(),自己手寫 useFactory,一定要記得寫 imports,不然就是在賭運氣:

TypeOrmModule.forRootAsync({
  imports: [ConfigModule],   // ← 必須寫!告訴框架:等 ConfigModule 好了再來初始化我
  inject: [ConfigService],
  useFactory: (configService: ConfigService) => ({
    type: 'mysql',
    host: configService.get<string>('database.host'),
    port: configService.get<number>('database.port'),
    // ...
  }),
}),

還有一個更細的場景:兩個配置文件之間能互相依賴嗎?比如 databaseConfig 想引用 appConfig 里的某個值。答案是不行@nestjs/config 不支持配置工廠之間的注入。正確的做法是所有工廠函數(shù)都平級地從 process.env 取原始值,誰需要哪個環(huán)境變量就自己讀,不要試圖跨工廠函數(shù)共享:

// ? 錯誤:試圖在配置工廠里引用另一個配置對象
export default registerAs('database', () => {
  const appConfig = someHowGetAppConfig(); // 根本不存在這種 API
  return { host: appConfig.defaultHost };
});
// ? 正確:直接從 process.env 取,各配置工廠平級獨立
export default registerAs('database', () => ({
  host: process.env.APP_DEFAULT_HOST || 'localhost',
}));

7.3 在 main.ts 里怎么拿配置?

像監(jiān)聽端口、CORS 域名這種配置,在 main.ts 里就要用到,這時候可以通過 app.get() 拿到 ConfigService 實例:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  const port = configService.get<number>('PORT') ?? 3000;
  await app.listen(port);
}

8. 總結:到底該怎么選?

寫了這么多,咱們來拉個表對比一下:

姿勢類型安全結構化評價
直接硬剛 process.env? 純盲寫? 扁平適合寫完就扔的臨時腳本
ConfigModule + .env弱(全靠泛型強轉)? 扁平適合小型項目快速起步
自定義工廠函數(shù)?(需手寫類型)? 嵌套對象中規(guī)中矩,能處理默認值和類型轉換
registerAs() 命名空間??(全自動推導)? 按業(yè)務隔離強烈推薦!中大型項目的標準答案

?? 個人最推薦的實戰(zhàn)組合: registerAs() 命名空間 + isGlobal: true + 啟動時 Joi 強校驗。

這套組合的核心邏輯是:配置在哪定義,就在哪描述它的結構;業(yè)務代碼只管用,根本不需要關心底層到底是 .env 還是 YAML。 這樣哪怕以后要把配置遷移到云端,業(yè)務代碼也一行都不用改,這才叫真正的優(yōu)雅。

到此這篇關于如何優(yōu)雅的書寫NestJS服務配置的文章就介紹到這了,更多相關NestJS服務配置內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 基于NodeJS開發(fā)釘釘回調接口實現(xiàn)AES-CBC加解密

    基于NodeJS開發(fā)釘釘回調接口實現(xiàn)AES-CBC加解密

    這篇文章主要介紹了基于NodeJS開發(fā)釘釘回調接口 實現(xiàn)AES-CBC加解密,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-08-08
  • package.json中overrides用法示例詳解

    package.json中overrides用法示例詳解

    在前端開發(fā)中,package.json和package-lock.json是兩個經(jīng)常打交道的文件,這篇文章主要介紹了package.json中overrides用法的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2026-02-02
  • Node.js?實現(xiàn)簡單爬蟲的示例代碼

    Node.js?實現(xiàn)簡單爬蟲的示例代碼

    本文主要介紹了Node.js?實現(xiàn)簡單爬蟲,爬取美食網(wǎng)站的菜品標題和圖片鏈接,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2025-02-02
  • node.js express框架實現(xiàn)文件上傳與下載功能實例詳解

    node.js express框架實現(xiàn)文件上傳與下載功能實例詳解

    這篇文章主要介紹了node.js express框架實現(xiàn)文件上傳與下載功能,結合具體實例形式詳細分析了node.js express框架針對文件上傳與下載的前后臺相關實現(xiàn)技巧,需要的朋友可以參考下
    2019-10-10
  • 三種Node.js寫文件的方式

    三種Node.js寫文件的方式

    這篇文章主要為大家詳細介紹了三種Node.js寫文件的方式,感興趣的小伙伴們可以參考一下
    2016-03-03
  • 一文詳解NodeJS和Javascript之間有什么區(qū)別

    一文詳解NodeJS和Javascript之間有什么區(qū)別

    在前端和后端開發(fā)的技術棧中,JavaScript?和?Node.js?經(jīng)常是新手和經(jīng)驗豐富的開發(fā)者討論的熱門話題,這篇文章主要介紹了NodeJS和Javascript之間有什么區(qū)別的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2025-11-11
  • Node學習筆記:Node.js安裝及環(huán)境配置 史詩級詳細版【含測試與鏡像說明】

    Node學習筆記:Node.js安裝及環(huán)境配置 史詩級詳細版【含測試與鏡像說明】

    這篇文章主要介紹了Node學習筆記之Node.js安裝及環(huán)境配置方法,詳細分析了node.js的基本安裝、配置、環(huán)境變量設置、以及環(huán)境測試與鏡像使用說明,需要的朋友可以參考下
    2023-05-05
  • Node安裝教程&環(huán)境變量配置方式(Window11)

    Node安裝教程&環(huán)境變量配置方式(Window11)

    這篇文章詳細介紹了如何在Windows 11上安裝和配置Node.js,包括下載、安裝、驗證安裝、創(chuàng)建目錄、配置環(huán)境變量、下載驗證以及更換淘寶鏡像等步驟
    2026-03-03
  • 在Ubuntu系統(tǒng)上安裝Node.JS的教程

    在Ubuntu系統(tǒng)上安裝Node.JS的教程

    這篇文章主要介紹了在Ubuntu系統(tǒng)上安裝Node.JS的教程,Node.JS的高性能V8解釋器運行及異步機制為其帶來了巨大的人氣,需要的朋友可以參考下
    2015-10-10
  • nodejs簡單實現(xiàn)操作arduino

    nodejs簡單實現(xiàn)操作arduino

    本文給大家分享的是使用nodejs來驅動arduino,主要是基于cylonjs 和 gort,有需要的小伙伴可以參考下
    2016-09-09

最新評論

长泰县| 健康| 稷山县| 武穴市| 中西区| 宽城| 乡宁县| 东安县| 东安县| 永丰县| 宁都县| 湘潭县| 高唐县| 丹东市| 绿春县| 南投市| 江城| 句容市| 青河县| 隆安县| 平顶山市| 罗源县| 天等县| 囊谦县| 南溪县| 土默特右旗| 牡丹江市| 怀安县| 寻乌县| 枣强县| 大悟县| 灌阳县| 黔江区| 呼伦贝尔市| 阿巴嘎旗| 临沭县| 繁昌县| 浦东新区| 广昌县| 商城县| 灌云县|