Как правильно использовать process.env в TypeScript для работы с переменными окружения?
Проблема с process.env в TypeScript
Вы столкнулись с типичной ситуацией: TypeScript строго типизирован, а process.env — это динамический объект, который может содержать любые строки или быть undefined. Рассмотрим решение по шагам.
1. Установка типов для Node.js
Если вы видите ошибку "Cannot find name 'process'", убедитесь, что установили @types/node:
npm install --save-dev @types/node
И добавьте в tsconfig.json:
{
"compilerOptions": {
"types": ["node"]
}
}
2. Типизация process.env
По умолчанию process.env имеет тип NodeJS.ProcessEnv, где все значения — string | undefined. Чтобы сделать код чище, создайте обертку:
// env.ts
export function getEnvVar(name: string, fallback?: string): string {
const value = process.env[name] || fallback;
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
export function getEnvNumber(name: string, fallback?: number): number {
const value = process.env[name];
if (value === undefined && fallback !== undefined) return fallback;
const num = parseInt(value!, 10);
if (isNaN(num)) {
throw new Error(`Environment variable ${name} must be a number, got: ${value}`);
}
return num;
}
// Использование
const dbHost = getEnvVar('DB_HOST', 'localhost');
const dbPort = getEnvNumber('DB_PORT', 5432);
3. Использование библиотеки dotenv
Для загрузки переменных из файла .env:
npm install dotenv
// В самом начале вашего приложения
import dotenv from 'dotenv';
dotenv.config();
// Теперь process.env содержит переменные из .env файла
4. Продвинутая типизация с помощью declare
Вы можете расширить типы для process.env:
// global.d.ts
declare namespace NodeJS {
interface ProcessEnv {
DB_HOST: string;
DB_PORT: string;
NODE_ENV: 'development' | 'production' | 'test';
PORT?: string; // необязательная переменная
}
}
// После этого TypeScript будет знать о ваших переменных
const dbHost: string = process.env.DB_HOST; // без ошибки
const port: number = parseInt(process.env.PORT || '3000', 10);
5. Современный подход: библиотека envalid
envalid — отличное решение для валидации и типизации:
npm install envalid
import { cleanEnv, str, num, port } from 'envalid';
const env = cleanEnv(process.env, {
DB_HOST: str({ default: 'localhost' }),
DB_PORT: port({ default: 5432 }),
NODE_ENV: str({ choices: ['development', 'production', 'test'] }),
PORT: port({ default: 3000 }),
});
// Теперь env имеет полную типизацию
console.log(env.DB_HOST); // string
console.log(env.DB_PORT); // number (уже преобразован)
Итоговый пример
import dotenv from 'dotenv';
import { cleanEnv, str, num } from 'envalid';
dotenv.config();
const env = cleanEnv(process.env, {
DB_HOST: str({ default: 'localhost' }),
DB_PORT: num({ default: 5432 }),
PORT: num({ default: 3000 }),
});
// Используем типизированные переменные
async function startServer() {
console.log(`Server starting on port ${env.PORT}`);
console.log(`Connecting to ${env.DB_HOST}:${env.DB_PORT}`);
}
startServer().catch(console.error);
Этот подход гарантирует, что все переменные окружения проверены, типизированы и имеют значения по умолчанию. Ошибки на этапе выполнения будут выбрасываться с понятными сообщениями.
<<Альтернативный подход: использование класса-обертки
Если вы не хотите подключать дополнительные библиотеки, можно создать простой класс для управления переменными окружения:
class Config {
private static instance: Config;
public readonly dbHost: string;
public readonly dbPort: number;
public readonly port: number;
private constructor() {
this.dbHost = process.env.DB_HOST || 'localhost';
this.dbPort = parseInt(process.env.DB_PORT || '5432', 10);
this.port = parseInt(process.env.PORT || '3000', 10);
this.validate();
}
private validate(): void {
if (isNaN(this.dbPort)) throw new Error('DB_PORT must be a number');
if (isNaN(this.port)) throw new Error('PORT must be a number');
}
static getInstance(): Config {
if (!Config.instance) {
Config.instance = new Config();
}
return Config.instance;
}
}
// Использование
const config = Config.getInstance();
console.log(config.dbHost, config.dbPort);
Преимущества этого подхода:
- Единая точка доступа к конфигурации (синглтон)
- Валидация при инициализации
- Все переменные типизированы и доступны как свойства класса
- Не требует дополнительных зависимостей
Этот метод особенно хорош для небольших проектов, где не нужна сложная валидация, но хочется избежать повторяющихся проверок на undefined.