ConfigModule — Environment Variables va Validation

~20 daq · NestJS · Boshlang'ich daraja

Muammodan boshlaylik

Siz loyiha yozdingiz. Ma'lumotlar bazasiga ulanish uchun:

// ❌ Kod ichida to'g'ridan-to'g'ri yozilgan
const db = new DataSource({
  host: 'localhost',
  port: 5432,
  username: 'postgres',
  password: 'mypassword123',
  database: 'mydb',
});

Bu yondashuv bir nechta muammo keltirib chiqaradi:

  • Xavfsizlik: Parol GitHub ga tushib qoladi
  • Moslashuvchanlik: Lokal, test, production — har birida boshqa sozlamalar kerak, lekin kod o'zgartirilishi kerak
  • Jamoa: Har bir dasturchi o'z parolini yozishi kerak

Yechim — environment variables (muhit o'zgaruvchilari). Sozlamalar kod ichida emas, alohida .env faylda saqlanadi.

# .env fayl — kod ichida emas
DB_HOST=localhost
DB_PORT=5432
DB_PASSWORD=mypassword123

NestJS da buni boshqarish uchun @nestjs/config moduli bor.


O'rnatish

npm install @nestjs/config

@nestjs/config ichida dotenv kutubxonasi bor — .env faylni o'qiydi va process.env ga yuklaydi.


Asosiy ishlatish

1. AppModule ga ulash

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true, // barcha modullarda ishlaydi, qayta import shart emas
    }),
  ],
})
export class AppModule {}

isGlobal: true — nima uchun kerak? Har bir modulda ConfigModule ni import qilmaslik uchun. Bir marta ulasangiz — hamma joyda ishlaydi.

2. .env fayl yaratish

# .env
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=secret123
DB_NAME=myapp

JWT_SECRET=myjwtsecret
JWT_EXPIRES_IN=7d

PORT=3000

⚠️ .env faylni hech qachon git ga qo'shmang. .gitignore ga qo'shing:

.env
.env.local
.env.production

O'rniga .env.example fayl yarating — qiymatlar bo'sh, faqat kalitlar:

DB_HOST=
DB_PORT=
DB_PASSWORD=

3. ConfigService orqali o'qish

// database/database.module.ts
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Module({
  providers: [
    {
      provide: 'DATABASE',
      useFactory: (config: ConfigService) => ({
        host: config.get<string>('DB_HOST'),
        port: config.get<number>('DB_PORT'),
        password: config.get<string>('DB_PASSWORD'),
      }),
      inject: [ConfigService],
    },
  ],
})
export class DatabaseModule {}
// auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(
    private readonly config: ConfigService,
    private readonly jwt: JwtService,
  ) {}

  generateToken(userId: number) {
    return this.jwt.sign(
      { sub: userId },
      {
        secret: this.config.get<string>('JWT_SECRET'),
        expiresIn: this.config.get<string>('JWT_EXPIRES_IN'),
      },
    );
  }
}

Turli muhitlar uchun alohida fayllar

Lokal, test va production uchun turli sozlamalar kerak bo'ladi:

.env              ← standart (lokal)
.env.development  ← development
.env.test         ← test
.env.production   ← production
ConfigModule.forRoot({
  isGlobal: true,
  envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
})
# Ishga tushirish
NODE_ENV=production node dist/main.js
NODE_ENV=test jest

Konfiguratsiyani namespace larga bo'lish

Barcha o'zgaruvchilar bitta joyda bo'lsa — tartibsiz bo'ladi. registerAs bilan guruhlab bo'ladi:

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

export default registerAs('database', () => ({
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT, 10),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  name: process.env.DB_NAME,
}));
// config/jwt.config.ts
import { registerAs } from '@nestjs/config';

export default registerAs('jwt', () => ({
  secret: process.env.JWT_SECRET,
  expiresIn: process.env.JWT_EXPIRES_IN || '7d',
}));
// app.module.ts
import databaseConfig from './config/database.config';
import jwtConfig from './config/jwt.config';

ConfigModule.forRoot({
  isGlobal: true,
  load: [databaseConfig, jwtConfig],
})
// Ishlatish — namespace bilan
const dbHost = this.config.get<string>('database.host');
const jwtSecret = this.config.get<string>('jwt.secret');

Validatsiya — eng muhim qism

.env fayl yo'q bo'lsa yoki biror kalit yozilmagan bo'lsa — ilova ishga tushadi, lekin keyin noto'g'ri ishlaydi. Bu eng yomon holat: xato darhol emas, keyinroq chiqadi.

Yaxshisi: Ilova ishga tushayotganda barcha kerakli o'zgaruvchilar borligini tekshirish. Yo'q bo'lsa — darhol xato berish.

Buning uchun joi kutubxonasi ishlatiladi:

npm install joi
// app.module.ts
import * as Joi from 'joi';

ConfigModule.forRoot({
  isGlobal: true,
  validationSchema: Joi.object({
    NODE_ENV: Joi.string()
      .valid('development', 'production', 'test')
      .default('development'),

    PORT: Joi.number().default(3000),

    DB_HOST: Joi.string().required(),
    DB_PORT: Joi.number().default(5432),
    DB_USERNAME: Joi.string().required(),
    DB_PASSWORD: Joi.string().required(),
    DB_NAME: Joi.string().required(),

    JWT_SECRET: Joi.string().min(32).required(),
    JWT_EXPIRES_IN: Joi.string().default('7d'),
  }),
})

Endi DB_HOST bo'lmasa — ilova ishga tushmaydi va aniq xato beradi:

Error: Config validation error: "DB_HOST" is required

Tez-tez uchraydigan xatolar

1. config.get() undefined qaytaradi

const secret = this.config.get('JWT_SECRET'); // undefined

Sabab 1: .env faylda kalit yo'q yoki noto'g'ri yozilgan. Sabab 2: ConfigModule.forRoot() AppModule da chaqirilmagan. Sabab 3: isGlobal: true yo'q va modul import qilinmagan.

// Tekshirish uchun
console.log(this.config.get('JWT_SECRET')); // undefined bo'lsa — yuqoridagi sabablarni tekshiring

2. .env fayl o'qilmayapti

Sabab: Ilova ishga tushirilayotgan papka noto'g'ri. ConfigModule .env faylni joriy ishchi papkadan qidiradi.

# Xato — src/ papkasidan ishga tushirilsa
cd src && node main.js  # .env topilmaydi

# To'g'ri — loyiha ildizidan
node dist/main.js       # .env topiladi

3. Raqamli qiymat string sifatida keladi

const port = this.config.get('PORT'); // '3000' — string, number emas!
app.listen(port); // ishlaydi, lekin tip xato

Yechim: get<number>() yoki parseInt:

const port = this.config.get<number>('PORT');
// yoki
const port = parseInt(this.config.get('PORT'), 10);

Joi validatsiyasida Joi.number() qo'yilsa — avtomatik number ga aylantiriladi.


Xulosa

Vazifa Yechim
.env o'qish ConfigModule.forRoot()
Qiymat olish ConfigService.get<T>('KEY')
Barcha modullarda ishlash isGlobal: true
Guruhlab saqlash registerAs('namespace', () => {})
Ishga tushishda tekshirish validationSchema: Joi.object({})

Asosiy qoida: Hech qachon parol, token, API kalit kabi maxfiy ma'lumotlarni kod ichiga yozmang. Har doim .env dan o'qing va .gitignore ga qo'shing.


Keyingi dars

13-dars: Sequelize ORM + PostgreSQL — CRUD