Конфигурация приложений TypeScript: от простого к сложному
Любое современное приложение требует гибкой и безопасной настройки. Будь то API-ключи, параметры подключения к базе данных, порты сервера или флаги фич — конфигурация должна быть отделена от кода, легко читаться и валидироваться на этапе запуска. В мире TypeScript эта задача имеет свои особенности: типизация конфигурации даёт автодополнение и предотвращает ошибки времени выполнения. В этой статье мы разберём лучшие практики, популярные библиотеки и инструменты для управления конфигурацией TypeScript-приложений.
Почему не стоит использовать process.env напрямую?
На первый взгляд, чтение переменных окружения через process.env кажется простым решением. Однако такой подход имеет существенные недостатки:
- Отсутствие типизации: все значения — строки, даже если по логике это число или булево значение.
- Риск опечаток: легко написать
process.env.DB_HOTSвместоDB_HOSTи получитьundefined. - Отсутствие валидации: если обязательная переменная не задана, приложение упадёт в самый неожиданный момент, а не при старте.
Грамотная конфигурация решает все эти проблемы.
Библиотека dotenv и её TypeScript-обёртки
Стандартом де-факто для загрузки .env файлов является библиотека dotenv. Для TypeScript удобно использовать dotenv-flow или dotenv-expand, поддерживающие несколько окружений (development, staging, production).
// Установка
// npm install dotenv dotenv-expand
// config/env.ts
import dotenv from 'dotenv';
import dotenvExpand from 'dotenv-expand';
const myEnv = dotenv.config({ path: `.env.${process.env.NODE_ENV || 'development'}` });
dotenvExpand.expand(myEnv);
После вызова этой функции все переменные из .env файла будут доступны через process.env, но мы всё ещё не имеем типов.
Валидация схемы с помощью Zod
Zod — мощная библиотека для декларативной валидации схем с первоклассной поддержкой TypeScript. Она идеально подходит для конфигурации:
// config/schema.ts
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.coerce.number().positive().default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
REDIS_HOST: z.string().default('localhost'),
REDIS_PORT: z.coerce.number().default(6379),
FEATURE_NEW_UI: z
.string()
.transform((val) => val === 'true')
.default('false'),
});
export type EnvConfig = z.infer<typeof envSchema>;
Теперь мы можем безопасно распарсить и провалидировать окружение на старте:
// config/index.ts
import { envSchema, EnvConfig } from './schema';
let config: EnvConfig;
export function loadConfig(): EnvConfig {
if (config) return config; // кеширование
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('❌ Invalid environment variables:', result.error.flatten().fieldErrors);
process.exit(1);
}
config = result.data;
return config;
}
export function getConfig(): EnvConfig {
if (!config) throw new Error('Config not loaded. Call loadConfig() first.');
return config;
}
Библиотека Conf для управления конфигурацией приложения
Если ваше приложение требует не только чтения, но и записи конфигурации (например, настройки пользователя, кэш), обратите внимание на conf от Sindre Sorhus. Она хранит данные в JSON-файле и полностью типизирована.
// Установка
// npm install conf
import Conf from 'conf';
interface AppConfig {
lastOpenedProject: string;
theme: 'light' | 'dark';
windowBounds: { width: number; height: number };
}
const store = new Conf<AppConfig>({
projectName: 'my-app',
defaults: {
theme: 'light',
windowBounds: { width: 800, height: 600 },
},
});
store.set('theme', 'dark');
console.log(store.get('theme')); // 'dark'
Conf автоматически выбирает путь для хранения в зависимости от ОС (Linux: ~/.config/my-app/config.json, macOS: ~/Library/Application Support/my-app/config.json).
Инструмент ConfigCat — фиче-флаги и удалённая конфигурация
Для крупных проектов, где конфигурация должна меняться без деплоя, идеально подходит
Также в библиотеке
Рекомендаций