Пошук уроків, статей та іншого контенту
Створите окремі налаштування для development, test і production без дублювання конфігураційного коду.
Застосунок зазвичай працює щонайменше у трьох середовищах:
development — локальна розробка;
test — автоматичні тести;
production — розгорнутий застосунок.
У кожному середовищі можуть відрізнятися:
порт;
URL бази даних;
рівень логування;
секретні ключі;
параметри сторонніх сервісів.
Не варто створювати окремий конфігураційний модуль для кожного середовища. Структура конфігурації та код залишаються спільними, а значення зберігаються в різних .env-файлах.
@nestjs/configДля роботи з конфігурацією встановіть пакет:
npm install @nestjs/configПакет використовує змінні середовища та бібліотеку dotenv для завантаження значень із .env-файлів.
Наприклад, конфігурація може мати таку структуру:
src/
├── config/
│ └── configuration.ts
├── app.module.ts
└── main.ts
.env
.env.development
.env.test
.env.productionФайл .env можна використовувати для спільних значень або значень за замовчуванням. Файли з назвою середовища міститимуть специфічні значення.
.env.developmentNODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://localhost:5432/app_development
LOG_LEVEL=debug.env.testNODE_ENV=test
PORT=3001
DATABASE_URL=postgresql://localhost:5432/app_test
LOG_LEVEL=warn.env.productionNODE_ENV=production
PORT=8080
DATABASE_URL=postgresql://user:password@db.example.com:5432/app
LOG_LEVEL=errorФайл .env.production зазвичай не зберігають у репозиторії, якщо він містить паролі або інші секрети. У production такі значення часто передають безпосередньо через змінні середовища платформи розгортання.
Значення NODE_ENV має бути доступним до запуску NestJS, адже за ним визначається назва .env-файлу.
Приклади запуску:
NODE_ENV=development npm run start
NODE_ENV=test npm run start
NODE_ENV=production npm run startУ Windows PowerShell:
$env:NODE_ENV="development"; npm run start
$env:NODE_ENV="test"; npm run start
$env:NODE_ENV="production"; npm run startЯкщо NODE_ENV не передано, можна використовувати development як середовище за замовчуванням.
ConfigModuleУ AppModule потрібно підключити ConfigModule і визначити файл, який слід завантажити:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import appConfig from './config/configuration';
const environment = process.env.NODE_ENV ?? 'development';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [`.env.${environment}`, '.env'],
load: [appConfig],
}),
],
})
export class AppModule {}Тут:
isGlobal: true робить ConfigService доступним у всіх модулях без повторного імпорту ConfigModule;
envFilePath спочатку шукає файл конкретного середовища;
.env використовується як запасний файл;
load підключає типізовану конфігурацію;
process.env.NODE_ENV читається до завантаження .env-файлу.
Перший файл у масиві має пріоритет над наступними. Значення, передані безпосередньо через системне середовище, мають пріоритет над значеннями з .env-файлів.
Замість того щоб розподіляти назви змінних середовища по всьому застосунку, створіть один конфігураційний файл:
import { registerAs } from '@nestjs/config';
export default registerAs('app', () => ({
environment: process.env.NODE_ENV ?? 'development',
port: Number(process.env.PORT ?? 3000),
databaseUrl: process.env.DATABASE_URL ?? '',
logLevel: process.env.LOG_LEVEL ?? 'info',
}));Функція registerAs створює іменований простір конфігурації app. Після цього значення можна отримувати за ключами:
app.environment;
app.port;
app.databaseUrl;
app.logLevel.
Код застосунку не знає, з якого саме .env-файлу прийшли значення. Він працює з єдиною конфігураційною структурою.
Помилка в конфігурації має бути виявлена під час запуску, а не після першого запиту до застосунку. Для цього можна додати функцію validate:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import appConfig from './config/configuration';
function validateConfig(config: Record<string, unknown>) {
const environment = config.NODE_ENV ?? 'development';
const port = Number(config.PORT ?? 3000);
const databaseUrl = config.DATABASE_URL;
const allowedEnvironments = ['development', 'test', 'production'];
if (!allowedEnvironments.includes(String(environment))) {
throw new Error(
`NODE_ENV має бути одним із: ${allowedEnvironments.join(', ')}`,
);
}
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('PORT має бути цілим числом від 1 до 65535');
}
if (typeof databaseUrl !== 'string' || databaseUrl.length === 0) {
throw new Error('DATABASE_URL є обов’язковою змінною');
}
return {
...config,
NODE_ENV: environment,
PORT: port,
DATABASE_URL: databaseUrl,
};
}
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [
`.env.${process.env.NODE_ENV ?? 'development'}`,
'.env',
],
load: [appConfig],
validate: validateConfig,
}),
],
})
export class AppModule {}Якщо змінна має неправильне значення, NestJS не запустить застосунок і покаже помилку конфігурації.
Для доступу до конфігурації використовується ConfigService:
import { Controller, Get } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Controller()
export class AppController {
constructor(private readonly configService: ConfigService) {}
@Get('info')
getInfo() {
return {
environment: this.configService.getOrThrow<string>('app.environment'),
port: this.configService.getOrThrow<number>('app.port'),
logLevel: this.configService.getOrThrow<string>('app.logLevel'),
};
}
}getOrThrow повертає значення або генерує помилку, якщо ключ не знайдено. Це допомагає не продовжувати роботу з неповною конфігурацією.
Для необов’язкових значень можна використовувати get із запасним значенням:
const timeout = this.configService.get<number>('app.timeout', 5000);Однак значення за замовчуванням краще задавати в одному місці — у конфігураційній фабриці:
import { registerAs } from '@nestjs/config';
export default registerAs('app', () => ({
environment: process.env.NODE_ENV ?? 'development',
port: Number(process.env.PORT ?? 3000),
databaseUrl: process.env.DATABASE_URL ?? '',
logLevel: process.env.LOG_LEVEL ?? 'info',
requestTimeout: Number(process.env.REQUEST_TIMEOUT ?? 5000),
}));Так конфігураційні правила не дублюються в сервісах і контролерах.
src/config/configuration.tsimport { registerAs } from '@nestjs/config';
export default registerAs('app', () => ({
environment: process.env.NODE_ENV ?? 'development',
port: Number(process.env.PORT ?? 3000),
databaseUrl: process.env.DATABASE_URL ?? '',
logLevel: process.env.LOG_LEVEL ?? 'info',
}));src/app.module.tsimport { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import appConfig from './config/configuration';
function validateConfig(config: Record<string, unknown>) {
const environment = String(config.NODE_ENV ?? 'development');
const port = Number(config.PORT ?? 3000);
const databaseUrl = config.DATABASE_URL;
if (!['development', 'test', 'production'].includes(environment)) {
throw new Error(`Невідоме середовище: ${environment}`);
}
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('PORT має бути цілим числом від 1 до 65535');
}
if (typeof databaseUrl !== 'string' || databaseUrl.length === 0) {
throw new Error('DATABASE_URL є обов’язковою змінною');
}
return config;
}
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [
`.env.${process.env.NODE_ENV ?? 'development'}`,
'.env',
],
load: [appConfig],
validate: validateConfig,
}),
],
})
export class AppModule {}src/main.tsimport { NestFactory } from '@nestjs/core';
import { ConfigService } from '@nestjs/config';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
const port = configService.getOrThrow<number>('app.port');
await app.listen(port);
console.log(
`Застосунок запущено в середовищі ${configService.getOrThrow<string>('app.environment')} на порту ${port}`,
);
}
bootstrap();Запуск для development:
NODE_ENV=development npm run startУ цьому випадку NestJS:
визначить середовище як development;
завантажить .env.development;
за потреби використає значення з .env;
виконає перевірку конфігурації;
створить об’єкт app через configuration.ts;
запустить застосунок на порту 3000.
Для test і production зміниться лише NODE_ENV та набір значень у відповідному файлі.
Не потрібно створювати такі файли:
src/config/development.ts
src/config/test.ts
src/config/production.tsякщо структура конфігурації у всіх середовищах однакова. Достатньо:
однієї фабрики конфігурації;
окремих файлів зі значеннями;
спільної функції валідації.
Наприклад, код фабрики залишається одним і тим самим:
export default registerAs('app', () => ({
port: Number(process.env.PORT ?? 3000),
databaseUrl: process.env.DATABASE_URL ?? '',
logLevel: process.env.LOG_LEVEL ?? 'info',
}));Відрізняються тільки значення:
# .env.development
PORT=3000
LOG_LEVEL=debug# .env.test
PORT=3001
LOG_LEVEL=warn# .env.production
PORT=8080
LOG_LEVEL=errorNODE_ENV задано лише всередині .env.developmentЯкщо NODE_ENV є тільки в .env.development, застосунок не зможе використати його для вибору цього файлу. На момент визначення envFilePath файл ще не завантажено.
Передавайте NODE_ENV до запуску:
NODE_ENV=development npm run startprocess.env у кожному сервісіТакий код розподіляє конфігураційні правила по всьому застосунку:
const port = Number(process.env.PORT ?? 3000);Краще один раз перетворити значення у фабриці конфігурації та отримувати його через ConfigService.
Усі змінні середовища спочатку є рядками:
process.env.PORT; // string | undefinedТому PORT потрібно перетворити на число і перевірити, що результат коректний:
const port = Number(process.env.PORT ?? 3000);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('Некоректний PORT');
}Якщо DATABASE_URL потрібен для запуску, не використовуйте порожній рядок як непомітне значення за замовчуванням. Перевіряйте змінну під час старту застосунку.
Не додавайте реальні паролі, токени та ключі до .env-файлів, які комітяться в репозиторій. Для локальної роботи використовуйте окремі файли, а в production передавайте секрети через змінні середовища.
Для різних середовищ створюють окремі .env-файли.
NODE_ENV визначає, який файл потрібно завантажити.
ConfigModule.forRoot підключає файли середовища до NestJS.
registerAs допомагає створити спільну структуровану конфігурацію.
Різні середовища мають містити різні значення, а не дубльований TypeScript-код.
Функція validate дозволяє зупинити застосунок при некоректній конфігурації.
Доступ до значень у сервісах і контролерах краще виконувати через ConfigService.