WisarWisar
Dasturlash kitobi/8-QISM — NestJS18 daqiqa

8.14-bob: Config moduli — env, validatsiya, namespaced config

8-QISM — NestJS (chuqur) · 14-mavzu


1. Kirish va motivatsiya

MongoDB'ni 8.13-bob bildik. Endi shu paytgacha deyarli har bobda ishlatgan ConfigServiceni — NestJS'ning konfiguratsiya tizimini — chuqur o'rganamiz. 5.8'da dotenv va env o'zgaruvchilarini Express'da ko'rdik; endi NestJS'da @nestjs/config bilan — professional, validatsiyalangan, tur xavfsiz, markazlashtirilgan konfiguratsiya. Bu bob barcha sozlamalarni (DB URI, JWT secret, SMTP, AWS, bot token — 8.3, 8.9, 8.10, 8.12, 8.13) bir tizimga jamlaydi.

Konfiguratsiya — ilovaning muhitga bog'liq qiymatlari: DB manzili, parollar, API kalitlar, portlar. Bular kodda bo'lmasligi kerak (12-faktorli ilova printsipi — 5.8): har muhit (dev/test/prod) har xil qiymat, va sirlar (secret) kodga tushmasligi shart (14). NestJS @nestjs/config.env fayldan o'qiydi, validatsiya qiladi (Joi — noto'g'ri/yo'q sozlama ilova ishga tushmaydi, runtime xato emas!), va ConfigService orqali DI bilan beradi.

Bu bob — promptdagi mavzuni chuqurlashtiradi: ConfigModule (isGlobal), env validatsiya (Joi schema), namespaced config (registerAs — modulli sozlama), tur xavfsiz config, turli muhit (dev/prod), va xavfsizlik (sirlar). Bu bob: ConfigModule, validatsiya, namespace, tur xavfsizlik — chuqur. To'g'ri config — barqaror, xavfsiz, ko'chiriladigan ilova asosi.

O'xshatish: config — uskunaning sozlama paneli (5.8: rozetka). Lekin bu bobda biz uni professional qilamiz: panel tekshiruvi bilan (validatsiya — noto'g'ri sozlama bilan uskuna yoqilmaydi, ishlab turib buzilmaydi); bo'limlarga ajratilgan (namespace — "DB sozlamalari", "Auth sozlamalari" alohida panellar); va har joy uchun alohida profil (dev/prod — uy/zavod sozlamalari). Sozlama panelisiz uskuna (hardcode) — har o'zgarishda korpusni ochish (kodga tegish).

Nega muhim?

  • Muhitga bog'liq — dev/prod har xil (DB, kalit — 5.8).
  • Sirlar himoyasi — kalit/parol kodda emas (14).
  • Validatsiya — noto'g'ri config darrov xato (runtime emas).
  • Markazlashtirilgan — barcha sozlama bir tizim (DI).

2. Nazariya — chuqur tushuntirish

2.1. ConfigModule sozlash (asos)

ts
// app.module.ts
import { ConfigModule } from "@nestjs/config";

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,                                 // butun ilovaga (qayta import kerak emas)
      envFilePath: ".env",                            // env fayl
      cache: true,                                    // tezlik (cache)
    }),
  ],
})
export class AppModule {}

ConfigModule.forRoot: .env faylni o'qiydi, process.envga qo'yadi. isGlobal: truebutun ilovaga (har modulda qayta import kerak emas — 8.1). cache: true (tezlik). Bir marta (root). Bu — 5.8 dotenv'ning NestJS versiyasi (DI + global).

forRoot asosiy opsiyalari (to'liq):

ts
ConfigModule.forRoot({
  isGlobal: true,                    // butun ilovaga (qayta import shart emas)
  envFilePath: [".env.local", ".env"],   // bir nechta fayl — birinchisi ustun (birlashadi)
  ignoreEnvFile: false,              // true — .env faylni o'qimaydi, faqat process.env (prod/Docker)
  ignoreEnvVars: false,              // true — process.env'ni e'tiborsiz qoldiradi (faqat fayl/load)
  cache: true,                       // qiymatlarni keshlaydi (har o'qishda process.env'ga tegmaydi — tezroq)
  expandVariables: true,             // .env ichida ${...} kengaytirish (o'zgaruvchini o'zgaruvchida)
  load: [databaseConfig],            // namespaced config fayllar (2.5, 2.6)
  validationSchema: Joi.object({}),  // env validatsiya (2.3)
});

forRoot opsiyalari batafsil:

  • envFilePath — bitta yo'l (".env") yoki massiv ([".env.local", ".env"]). Massivda birinchi fayl ustun turadi (bir xil kalit bo'lsa, oldingisi g'olib) — lokal override uchun qulay.
  • ignoreEnvFile: true.env faylni umuman o'qimaydi, faqat process.envdan oladi. Productionda odatiy (env server/Docker/CI'dan keladi — 10, 2.8), disk'da fayl yo'q.
  • ignoreEnvVars: true — aksincha, process.envni e'tiborsiz qoldiradi va faqat load/fayldan oladi (kam ishlatiladi — test izolyatsiyasida).
  • cache: true — o'qilgan qiymatlarni ichki obyektda saqlaydi. process.env — sekin (har chaqiruvda qidiruv), keshlangan get tezroq. Ishga tushgach process.env o'zgarsa, kesh yangilanmaydi (odatda muammo emas).
  • expandVariables: true.env ichida o'zgaruvchini boshqa o'zgaruvchida ishlatish (dotenv-expand). Masalan:
bash
# .env
APP_HOST=localhost
APP_PORT=3000
APP_URL=http://${APP_HOST}:${APP_PORT}    #  http://localhost:3000 (expandVariables kerak)
  • infer: true (kam ishlatiladi) — get da generic bermasangiz ham qiymat turini avtomatik aniqlashga urinadi.

2.2. ConfigService (qiymat olish)

ts
@Injectable()
export class AppService {
  constructor(private config: ConfigService) {}      // DI (8.2)

  example() {
    const port = this.config.get<number>("PORT");    // qiymat (undefined bo'lishi mumkin)
    const dbUri = this.config.get<string>("DATABASE_URI");
    const secret = this.config.get("JWT_SECRET", "default");   // default qiymat (2-argument)

    // getOrThrow — yo'q bo'lsa istisno tashlaydi (undefined qaytarmaydi)
    const uri = this.config.getOrThrow<string>("DATABASE_URI");
  }
}

ConfigService — DI bilan qiymat olish: config.get<T>("KEY") (tur bilan), ikkinchi argument — default. isGlobal bo'lsa, har joyda inject. Bu — 8.3, 8.9, 8.10 da forRootAsyncda ishlatgan ConfigService. process.env to'g'ridan o'qish o'rniga ConfigService (markazlashtirilgan, tur, validatsiya).

get va getOrThrow farqi: get — kalit yo'q bo'lsa undefined qaytaradi (yoki default). getOrThrow — kalit yo'q bo'lsa istisno tashlaydi (Configuration key "X" does not exist). Majburiy sozlamalar uchun getOrThrow afzal: const uri = config.getOrThrow<string>("DATABASE_URI") — endi TypeScript urini string deb biladi (string | undefined emas), va yo'q bo'lsa darrov xato (fail-fast — 2.10). Tip generic (<string>, <number>) — qaytgan qiymat turini belgilaydi; validatsiya PORTni number ga o'girsa ham, generic'ni to'g'ri berish sizning mas'uliyatingiz (yoki infer: true — quyida).

2.3. Env validatsiya (Joi — eng muhim)

ts
import * as Joi from "joi";

ConfigModule.forRoot({
  isGlobal: true,
  validationSchema: Joi.object({                      // validatsiya
    NODE_ENV: Joi.string().valid("development", "production", "test").default("development"),
    PORT: Joi.number().default(3000),
    DATABASE_URI: Joi.string().required(),            // majburiy (yo'q bo'lsa — xato)
    JWT_ACCESS_SECRET: Joi.string().min(32).required(),   // kuchli (14)
    JWT_REFRESH_SECRET: Joi.string().min(32).required(),
    MAIL_HOST: Joi.string().required(),
  }),
  validationOptions: { abortEarly: false },           // barcha xatoni ko'rsat
});

Env validatsiya (Joi — eng muhim): validationSchema — har env o'zgaruvchini tekshiradi (tur, majburiy, format). Yo'q yoki noto'g'ri sozlama ilova ISHGA TUSHMAYDI (runtime xato emas — darrov ma'lum). required() (majburiy), min(32) (kuchli secret — 14), valid() (ruxsat etilgan). abortEarly: false (barcha xato). Bu — config'ning eng qimmatli xususiyati (xatoni erta ushlash).

2.3b. Env validatsiya — validate (class-validator — Joi'ga muqobil)

Joi o'rniga (yoki uni yoqtirmasangiz) NestJS validate funksiyasini beradi — bu 8.5'da o'rganilgan class-validator + class-transformer bilan ishlaydi (bir xil dekoratorlar — DTO'dagidek).

ts
// env.validation.ts
import { plainToInstance } from "class-transformer";
import { IsEnum, IsNumber, IsString, MinLength, validateSync } from "class-validator";

enum Environment {                                  // ruxsat etilgan muhitlar
  Development = "development",
  Production = "production",
  Test = "test",
}

class EnvironmentVariables {
  @IsEnum(Environment)                              // faqat enum qiymatlari
  NODE_ENV: Environment;

  @IsNumber()
  PORT: number;

  @IsString()
  DATABASE_URI: string;                             // majburiy (@IsString  undefined bo'lsa xato)

  @IsString()
  @MinLength(32)                                    // kuchli secret (14)
  JWT_ACCESS_SECRET: string;

  @IsString()
  @MinLength(32)
  JWT_REFRESH_SECRET: string;
}

// Ishga tushishda chaqiriladigan funksiya
export function validate(config: Record<string, unknown>) {
  const validated = plainToInstance(EnvironmentVariables, config, {
    enableImplicitConversion: true,                 // "3000" (string)  3000 (number) avtomatik
  });
  const errors = validateSync(validated, { skipMissingProperties: false });
  if (errors.length > 0) {
    throw new Error(errors.toString());             // xato  ilova ISHGA TUSHMAYDI (fail-fast)
  }
  return validated;                                 // tekshirilgan (va o'girilgan) obyekt
}
ts
// app.module.ts — validationSchema o'rniga validate
ConfigModule.forRoot({
  isGlobal: true,
  validate,                                         // funksiya (Joi schema emas)
});

validate (class-validator): validationSchema (Joi) o'rniga — funksiya (validate), u ishga tushishda barcha env'ni oladi. Ichida plainToInstance (class-transformer — 8.5) xom obyektni klass nusxasiga o'giradi, keyin validateSync (class-validator) dekoratorlar bo'yicha tekshiradi. Xato bo'lsa throw ilova ishga tushmaydi (Joi'dagidek fail-fast). enableImplicitConversion: true — env string'larini ("3000") turga o'giradi (3000). Qaysi birini tanlash? Joi — soddaroq, mustaqil kutubxona; validate — agar loyihada allaqachon class-validator bo'lsa (DTO'lar — 8.5), bir xil uslub va dekoratorlar. Ikkalasi ham fail-fast beradi; ikkovidan birini tanlang (ikkalasini birga emas).

2.4. forRootAsync (config bog'liq sozlama)

ts
// Boshqa modul config'dan foydalanadi (8.3, 8.9, 8.13)
TypeOrmModule.forRootAsync({
  inject: [ConfigService],                            // config inject
  useFactory: (config: ConfigService) => ({
    type: "postgres",
    url: config.get("DATABASE_URI"),                  // config'dan
    autoLoadEntities: true,
  }),
});
// JwtModule, MongooseModule, MailerModule — barchasi shu naqsh (config bilan)

forRootAsync — modul sozlamasini config'dan olish (8.3: 2.4, 8.9: 2.3, 8.13: 2.1). inject: [ConfigService] + useFactory. Bu naqsh butun kitobda takrorlandi (DB, JWT, Mail, Mongo). Config — markaz (barcha modul undan oladi). isGlobal bo'lsa, ConfigModule import shart emas.

2.5. Namespaced config (registerAs — modulli)

ts
// config/database.config.ts
import { registerAs } from "@nestjs/config";

export default registerAs("database", () => ({       // "database" namespace
  uri: process.env.DATABASE_URI,
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT, 10) || 5432,
}));

// config/jwt.config.ts
export default registerAs("jwt", () => ({
  accessSecret: process.env.JWT_ACCESS_SECRET,
  accessExpires: process.env.JWT_ACCESS_EXPIRES || "15m",
  refreshSecret: process.env.JWT_REFRESH_SECRET,
}));

Namespaced config (registerAs): sozlamalarni bo'limlarga ajratish (database, jwt, mail). Har bo'lim — alohida fayl (tashkillangan). Tur va guruhlash. parseInt (env — string number). Bu — katta ilovada config'ni boshqarish (tartibli). 2.6'da yuklash, 2.7'da olish.

2.6. Namespace yuklash (load)

ts
ConfigModule.forRoot({
  isGlobal: true,
  load: [databaseConfig, jwtConfig, mailConfig],     // namespace'larni yuklash
  validationSchema: Joi.object({ /* ... */ }),
});

load — namespace config fayllarni yuklash (load: [databaseConfig, ...]). Endi config "bo'limlar" bilan tashkillangan. Validatsiya 2.3-bob baribir env darajasida. Bu — 2.5 namespace'larni ulash.

2.7. Namespace olish (tur xavfsiz)

ts
// 1-usul: get bilan (namespace.kalit)
const uri = this.config.get<string>("database.uri");
const secret = this.config.get("jwt.accessSecret");

// 2-usul: ConfigType (tur xavfsiz — afzal)
import { ConfigType } from "@nestjs/config";

@Injectable()
export class AuthService {
  constructor(
    @Inject(jwtConfig.KEY)                            // namespace inject
    private jwt: ConfigType<typeof jwtConfig>,        // TUR XAVFSIZ
  ) {}
  example() {
    const secret = this.jwt.accessSecret;            // avtomatik tugatish (7)
  }
}

Namespace olish: config.get("database.uri") (nuqta bilan), yoki ConfigType (tur xavfsiz — afzal): @Inject(jwtConfig.KEY) + ConfigType<typeof jwtConfig> this.jwt.accessSecret (avtomatik tugatish, tur tekshiruvi — 7). Tur xavfsizlik — config'da xato kam (typo ushlanadi). Eng yaxshi amaliyot.

2.8. Turli muhit (dev/prod/test)

ts
// Muhitga qarab env fayl
ConfigModule.forRoot({
  isGlobal: true,
  envFilePath: `.env.${process.env.NODE_ENV || "development"}`,   // .env.development / .env.production
});
text
  .env.development   — lokal (DB localhost, debug log)
  .env.production    — server (real DB, kalit, info log)
  .env.test          — test (test DB)
  .env.example       — namuna (git'ga — qiymatsiz kalitlar ro'yxati)

Turli muhit: envFilePath: .env.${NODE_ENV} — muhitga qarab fayl (dev/prod/test). .env.example (git'ga — kalitlar ro'yxati, qiymatsiz — jamoa uchun). Production'da odatda env serverdan (Docker/CI — 10), fayl emas. NODE_ENV — markaziy o'zgaruvchi.

2.9. Xavfsizlik — sirlar (14)

text
   .env GIT'GA TUSHMASIN (.gitignore — eng muhim! 14)
   .env.example git'ga (qiymatsiz — namuna)
   Production sirlar — secrets manager (Vault, AWS Secrets, env)
   Kuchli secret (min 32 belgi — Joi tekshiradi — 2.3)
   Har muhit — har xil secret (dev/prod alohida)
   Kalit oshkor bo'lsa — DARROV almashtirish (rotatsiya)

Sirlar xavfsizligi (14 — eng muhim): .env git'ga tushmasligi kerak (.gitignore — 4.5; oshkor bo'lsa — barcha kalit kompromis). .env.example (namuna — git'ga). Production — secrets manager (Vault/AWS — fayl emas). Kuchli secret (Joi min 32 — 2.3). Bu — kitobning eng muhim xavfsizlik qoidasi.

2.10. Config validatsiya foydasi (fail-fast)

text
  Validatsiyasiz (yomon):
  Ilova ishga tushadi  100 so'rovdan keyin  "DATABASE_URI undefined" (runtime!)

  Validatsiya bilan (yaxshi — fail-fast):
  Ilova ishga tushmaydi  "DATABASE_URI is required" (DARROV, deploy'da)
   muammo production'ga yetib bormaydi

Fail-fast (2.3 foydasi): validatsiya bilan noto'g'ri config darrov (ishga tushishda) ma'lum — production'da 100 so'rovdan keyin emas. Bu — eng katta foyda (xato erta, deploy'da ushlanadi — 10). Konfiguratsiya xatosi — eng ko'p production muammosi; validatsiya buni oldini oladi.

2.11. Best practices (config)

text
   ConfigModule isGlobal (bir marta — 2.1)
   ConfigService (process.env emas — 2.2)
   Joi validatsiya (fail-fast — 2.3, 2.10)
   forRootAsync (modul sozlamasi config'dan — 2.4)
   Namespace (registerAs — katta ilova — 2.5)
   ConfigType (tur xavfsiz — 2.7)
   Turli muhit (.env.NODE_ENV — 2.8)
   .env gitignore; .env.example git'ga (14, 2.9)
   Kuchli secret; secrets manager (prod — 2.9)

3. Sintaksis — tez ma'lumotnoma

ts
// Sozlash (2.1, 2.3)
ConfigModule.forRoot({ isGlobal: true, validationSchema: Joi.object({...}), load: [dbConfig] })

// Olish (2.2, 2.7)
this.config.get<string>("KEY")                     // undefined bo'lishi mumkin
this.config.getOrThrow<string>("KEY")              // yo'q bo'lsa istisno (majburiy uchun)
this.config.get("KEY", "default")                  // default qiymat
this.config.get("namespace.kalit")
@Inject(dbConfig.KEY) private db: ConfigType<typeof dbConfig>   // tur xavfsiz

// Validatsiya (2.3, 2.3b) — ikkovidan biri
validationSchema: Joi.object({ ... })              // Joi
validate: validateFunksiyasi                        // yoki class-validator

// Namespace 2.5-bob: registerAs("database", () => ({ uri: process.env.DATABASE_URI }))
// forRootAsync 2.4-bob: inject: [ConfigService], useFactory: (c) => ({ url: c.get("...") })

4. Batafsil kod namunalari

Misol 1 — ConfigModule + Joi validatsiya (2.1, 2.3)

ts
// app.module.ts
import * as Joi from "joi";

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV || "development"}`,
      cache: true,
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid("development", "production", "test").default("development"),
        PORT: Joi.number().default(3000),

        // Database
        DATABASE_URI: Joi.string().required(),

        // JWT (14 — kuchli)
        JWT_ACCESS_SECRET: Joi.string().min(32).required(),
        JWT_ACCESS_EXPIRES: Joi.string().default("15m"),
        JWT_REFRESH_SECRET: Joi.string().min(32).required(),
        JWT_REFRESH_EXPIRES: Joi.string().default("7d"),

        // Mail
        MAIL_HOST: Joi.string().required(),
        MAIL_USER: Joi.string().email().required(),
        MAIL_PASS: Joi.string().required(),

        // Redis (8.15)
        REDIS_HOST: Joi.string().default("localhost"),
        REDIS_PORT: Joi.number().default(6379),
      }),
      validationOptions: { abortEarly: false },       // barcha xato (2.3)
    }),
  ],
})
export class AppModule {}

Misol 2 — Namespaced config fayllar (2.5)

ts
// config/database.config.ts
import { registerAs } from "@nestjs/config";

export default registerAs("database", () => ({
  uri: process.env.DATABASE_URI,
  type: process.env.DB_TYPE || "postgres",
  synchronize: process.env.NODE_ENV === "development",   // prod'da false (6.13)
  logging: process.env.NODE_ENV === "development",
}));

// config/jwt.config.ts
export default registerAs("jwt", () => ({
  accessSecret: process.env.JWT_ACCESS_SECRET,
  accessExpires: process.env.JWT_ACCESS_EXPIRES || "15m",
  refreshSecret: process.env.JWT_REFRESH_SECRET,
  refreshExpires: process.env.JWT_REFRESH_EXPIRES || "7d",
}));

// config/app.config.ts
export default registerAs("app", () => ({
  port: parseInt(process.env.PORT, 10) || 3000,
  env: process.env.NODE_ENV || "development",
  clientUrl: process.env.CLIENT_URL || "http://localhost:5173",
}));

Misol 3 — Namespace yuklash va validatsiya birga (2.6)

ts
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [databaseConfig, jwtConfig, appConfig, mailConfig],   // namespace
      validationSchema: Joi.object({ /* Misol 1 */ }),            // env validatsiya
      validationOptions: { abortEarly: false },
    }),
  ],
})
export class AppModule {}

Misol 4 — forRootAsync (DB config bilan — 2.4)

ts
// TypeORM 8.3-bob — config'dan
TypeOrmModule.forRootAsync({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    type: "postgres",
    url: config.get("database.uri"),                  // namespace (2.7)
    synchronize: config.get("database.synchronize"),
    autoLoadEntities: true,
  }),
}),

// MongoDB (8.13)
MongooseModule.forRootAsync({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({ uri: config.get("database.uri") }),
}),

// JWT (8.9)
JwtModule.registerAsync({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    secret: config.get("jwt.accessSecret"),
    signOptions: { expiresIn: config.get("jwt.accessExpires") },
  }),
}),

Misol 5 — ConfigType (tur xavfsiz — 2.7)

ts
// auth.service.ts — namespace tur xavfsiz inject
import { ConfigType } from "@nestjs/config";
import jwtConfig from "../config/jwt.config";

@Injectable()
export class AuthService {
  constructor(
    @Inject(jwtConfig.KEY)
    private jwtConf: ConfigType<typeof jwtConfig>,    // TUR (avtomatik tugatish)
  ) {}

  tokenSozlama() {
    return {
      secret: this.jwtConf.accessSecret,              // tur tekshiriladi (typo  xato)
      expiresIn: this.jwtConf.accessExpires,
    };
  }
}

Misol 6 — main.ts da config (2.2)

ts
// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const config = app.get(ConfigService);              // app'dan olish

  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));   // (8.5)
  app.enableCors({ origin: config.get("app.clientUrl"), credentials: true });   // (5.20)

  const port = config.get<number>("app.port");
  await app.listen(port);
  console.log(`Ilova ${port}-portda (${config.get("app.env")})`);
}
bootstrap();

Misol 7 — .env fayllar (2.8, 2.9)

bash
# .env.development (lokal — git'ga TUSHMAYDI)
NODE_ENV=development
PORT=3000
DATABASE_URI=postgresql://postgres:parol@localhost:5432/mana_dev
JWT_ACCESS_SECRET=dev_secret_kamida_32_belgi_bolishi_kerak_123
JWT_REFRESH_SECRET=dev_refresh_secret_kamida_32_belgi_456789
MAIL_HOST=smtp.mailtrap.io
REDIS_HOST=localhost

# .env.example (git'ga TUSHADI — namuna, qiymatsiz)
NODE_ENV=
PORT=
DATABASE_URI=
JWT_ACCESS_SECRET=
JWT_REFRESH_SECRET=
MAIL_HOST=

# .gitignore (14 — eng muhim)
.env
.env.*
!.env.example

Misol 8 — Custom config service (validatsiya + tur — kengaytma)

ts
// Tur xavfsiz config wrapper (ixtiyoriy — qattiq tur)
@Injectable()
export class AppConfigService {
  constructor(private config: ConfigService) {}

  get port(): number { return this.config.get<number>("app.port"); }
  get databaseUri(): string { return this.config.get<string>("database.uri"); }
  get jwtAccessSecret(): string { return this.config.get<string>("jwt.accessSecret"); }
  get isProduction(): boolean { return this.config.get("app.env") === "production"; }
}
// Ishlatish: this.appConfig.databaseUri (to'liq tur xavfsiz, avtomatik tugatish)

Bu AppConfigServiceni modulda provider sifatida ro'yxatga olib, boshqa joyga inject qilamiz (DI — 8.2):

ts
// config.module.ts — o'z config modulingiz (ixtiyoriy — kengaytma)
import { Global, Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";

@Global()                                           // AppConfigService butun ilovaga
@Module({
  imports: [ConfigModule],                          // ConfigService'dan foydalanadi
  providers: [AppConfigService],                    // provider sifatida (8.2)
  exports: [AppConfigService],                      // boshqa modul inject qilishi uchun
})
export class AppConfigModule {}

// Endi istalgan servisda:
@Injectable()
export class SomeService {
  constructor(private appConfig: AppConfigService) {}   // DI — tur xavfsiz
  ish() {
    if (this.appConfig.isProduction) { /* ... */ }
  }
}

ConfigService DI (provider): ConfigServicening o'zi @nestjs/config beradigan provider'dir (isGlobal bo'lsa har joyda inject — 2.2). O'z wrapper'ingizni (AppConfigService) ham oddiy provider sifatida ro'yxatga olib, exports bilan tashqariga chiqarasiz. main.tsda esa DI ishlamaydi (konstruktor yo'q) — u yerda app.get(ConfigService) (Misol 6) orqali qo'lda olamiz.

Misol 9 — Muhitga bog'liq xulq (2.8)

ts
@Injectable()
export class LoggerService {
  constructor(private config: ConfigService) {}

  setup() {
    const env = this.config.get("app.env");
    if (env === "production") {
      return { level: "info", format: "json" };       // prod (5.12)
    }
    return { level: "debug", format: "pretty" };      // dev (rangli)
  }
}
// Swagger — faqat dev (8.8, xavfsizlik)
if (config.get("app.env") !== "production") {
  SwaggerModule.setup("api", app, document);
}

Misol 10 — Config testda (8.11)

ts
// Test'da config mock (8.11)
const module = await Test.createTestingModule({
  providers: [
    AuthService,
    {
      provide: ConfigService,
      useValue: { get: jest.fn((key) => ({ "jwt.accessSecret": "test_secret" }[key])) },
    },
  ],
}).compile();

// Yoki test config moduli
ConfigModule.forRoot({ envFilePath: ".env.test", isGlobal: true })

5. To'g'ri va noto'g'ri holatlar

1) process.env to'g'ridan o'qish

text
 process.env.DATABASE_URI (validatsiyasiz, tarqoq — 2.2)
 ConfigService.get (markazlashtirilgan, validatsiyalangan)

2) Validatsiyasiz config

text
 noto'g'ri config  runtime xato (production'da — 2.10)
 Joi validatsiya (fail-fast — ishga tushmaydi)

3) .env git'da

text
 .env commit (kalitlar oshkor — 14, falokat!)
 .gitignore + .env.example

4) Hardcode sozlama

text
 const SECRET = "abc123" (kodda — 14)
 env + config

5) Bir secret hamma muhitda

text
 dev/prod bir secret (dev sizsa, prod ham — 2.9)
 har muhit alohida secret

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — ConfigService inject bo'lmaydi

Sababi: ConfigModule import yo'q (isGlobal emas — 2.1). Yechimi: isGlobal: true; yoki modulda import.

Xato 2 — Ilova ishga tushmaydi (validation error)

Sababi: env yo'q/noto'g'ri (2.3 — bu yaxshi! fail-fast). Yechimi: .env faylni to'ldiring; xato xabarini o'qing.

Xato 3 — config.get undefined

Sababi: kalit noto'g'ri, yoki namespace yuklanmagan 2.6-bob. Yechimi: kalitni aniqlang; load'ga qo'shing.

Xato 4 — Number string sifatida

Sababi: env har doim string 2.5-bob. Yechimi: parseInt yoki Joi.number (transform).

Xato 5 — Production'da dev config

Sababi: NODE_ENV noto'g'ri 2.8-bob. Yechimi: NODE_ENV=production; envFilePath.

Xato 6 — .env o'zgardi, lekin ta'sir yo'q

Sababi: cache yoki restart yo'q. Yechimi: ilovani qayta ishga tushiring; cache:true bo'lsa restart.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • Env 5.8-bob: dotenv — NestJS @nestjs/config.
  • DB (8.3, 8.13): forRootAsync config'dan.
  • Auth 8.9-bob: JWT secret.
  • Mail 8.10-bob: SMTP config.
  • Bot 8.12-bob: BOT_TOKEN.
  • Redis 8.15-bob: REDIS config.
  • Xavfsizlik (14): sirlar, .gitignore.
  • DevOps (10): muhit, secrets manager.
  • Test 8.11-bob: config mock.

8. Eng yaxshi amaliyotlar (best practices)

  • ConfigModule isGlobal (bir marta — 2.1).
  • ConfigService (process.env emas — 2.2).
  • Joi yoki validate (validatsiya — fail-fast — 2.3, 2.3b, 2.10).
  • getOrThrow (majburiy sozlama — undefined emas, istisno — 2.2).
  • forRootAsync (modul sozlamasi config'dan — 2.4).
  • Namespace (registerAs — katta ilova — 2.5).
  • ConfigType (tur xavfsiz — 2.7).
  • Turli muhit (.env.NODE_ENV — 2.8).
  • .env gitignore; .env.example git'ga (14, 2.9).
  • Kuchli secret (min 32 — Joi); secrets manager (prod — 2.9).
  • Muhitga bog'liq xulq (Swagger faqat dev — 2.8).

9. Amaliy loyiha: "Professional Config Tizimi"

NestJS config'ni mustahkamlash.

Maqsad

To'liq, validatsiyalangan, namespaced, tur xavfsiz config tizimi qurish.

Talablar (requirements)

  1. ConfigModule: isGlobal, cache, muhit fayl (Misol 1, 2.1, 2.8).
  2. Joi validatsiya: barcha env (required, min, valid — Misol 1, 2.3).
  3. Namespace: database, jwt, app, mail config (Misol 2, 2.5).
  4. Load: namespace'larni yuklash (Misol 3, 2.6).
  5. forRootAsync: DB/JWT/Mail config'dan (Misol 4, 2.4).
  6. ConfigType: tur xavfsiz inject (Misol 5, 2.7).
  7. main.ts: port/cors config'dan (Misol 6, 2.2).
  8. .env fayllar: dev/prod/example + .gitignore (Misol 7, 2.9).
  9. Muhitga bog'liq: Swagger faqat dev, log darajasi (Misol 9, 2.8).
  10. Xavfsizlik: kuchli secret, .env gitignore (2.9, 14).

Maslahatlar (hint)

  • isGlobal (qayta import emas — 2.1, 1-xato).
  • Joi required/min (fail-fast — 2.3, 2-xato).
  • registerAs namespace 2.5-bob.
  • ConfigType tur xavfsiz 2.7-bob.
  • .env gitignore + example (14, 3-holat).
  • env string parseInt/Joi.number (4-xato).

"Tayyor" mezonlari (acceptance criteria)

  • ConfigModule (isGlobal, muhit).
  • Joi validatsiya (barcha env).
  • Namespace (4 config).
  • Load.
  • forRootAsync (DB/JWT/Mail).
  • ConfigType (tur xavfsiz).
  • main.ts config.
  • .env fayllar + gitignore.
  • Muhitga bog'liq xulq.
  • Xavfsizlik (secret, gitignore).

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda NestJS config tizimini chuqur o'rgandik:

  • ConfigModule (isGlobal — 2.1); ConfigService (DI — 2.2); Joi validatsiya (fail-fast — eng muhim — 2.3, 2.10).
  • forRootAsync (modul config'dan — 2.4); namespace (registerAs — 2.5, 2.6); ConfigType (tur xavfsiz — 2.7).
  • Turli muhit (.env.NODE_ENV — 2.8); xavfsizlik (sirlar, .gitignore, secrets manager — 14 — 2.9).

Keyingi bob — 8.15-bob: Caching (Redis), Logger, Error handling. Config'ni bildik; endi ishlash va kuzatuvni — caching (CacheModule + Redis — 5.21 — tezlik), logger (Pino/Winston — 5.12 — kuzatuv), error handling (global exception filter — 8.6) — chuqur ko'ramiz. Bu — production ilovaning tezligi, kuzatuvi, barqarorligi.


Foydalanilgan rasmiy manbalar

  • docs.nestjs.com/techniques/configuration (ConfigModule, validationSchema, registerAs, ConfigType)
  • 12factor.net (config — env'da sirlar)

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.14-bob: Config moduli — env, validatsiya, namespaced config — Wisar