Avatar ·

Как правильно использовать process.env в TypeScript для работы с переменными окружения?

Привет! Я новичок в TypeScript и пытаюсь использовать переменные окружения в своем Node.js приложении. Написал простой код: ```typescript const dbHost = process.env.DB_HOST; const dbPort = process.env.DB_PORT; console.log(`Connecting to ${dbHost}:${dbPort}`); ``` Но TypeScript ругается на `process.env`. Ошибка: "Cannot find name 'process'". Я установил `@types/node`, но проблема осталась. Также я заметил, что `process.env` возвращает `string | undefined`, и мне приходится постоянно проверять на `undefined`. Это очень раздражает, особенно когда я уверен, что переменная определена. Другая проблема: когда я использую `process.env.PORT` для порта сервера, мне нужно преобразовывать строку в число, и если переменная не задана, то использовать значение по умолчанию. Я делаю так: ```typescript const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; ``` Но это выглядит громоздко. Есть ли более элегантный способ? Может быть, есть библиотеки или паттерны для работы с переменными окружения в TypeScript? Как правильно типизировать `process.env` и избежать ошибок на этапе компиляции? Буду благодарен за примеры кода. <<>>

Проблема с 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.