TypeScript Application Configuration: Overview of Tools and Libraries
A modern TypeScript application rarely works in isolation. It needs to know which database to connect to, which port to use, where third-party APIs are located, and how to behave in different environments (development, staging, production). Proper configuration is not just about storing settings; it is a discipline that includes validation, typing, and security.
In this article, we will review the key tools and libraries for managing configuration in TypeScript projects, from simple solutions to industry standards.
Why is separate configuration needed?
There are several approaches to storing settings, and each has its drawbacks:
- Hardcoding in source code — violates the 12-factor app principle, configuration is mixed with code, and behavior cannot be changed without rebuilding.
- Environment variables (process.env) — a standard method, but all values are strings, there is no validation, and it is easy to make a typo.
- JSON/YAML files — convenient, but they need to be versioned carefully to avoid exposing secrets in the repository.
TypeScript adds another layer: we want the configuration to be strictly typed and errors to be detected at compile time, not at runtime.
Libraries for working with environment variables
dotenv
The most popular library for loading variables from .env files. It is used in almost all TypeScript projects.
// npm install dotenv @types/dotenvimport dotenv from 'dotenv';
dotenv.config(); // loads .env into process.env
console.log(process.env.PORT); // '3000' — stringFeatures:
- Works with any framework (Express, NestJS, Next.js).
- Supports multiple files (
.env.local,.env.production). - Does not perform validation — only loading.
env-var
A wrapper around process.env that adds validation and type casting at runtime.
// npm install env-varimport env from 'env-var';
export const config = { PORT: env.get('PORT').required().asPortNumber(), DB_HOST: env.get('DB_HOST').required().asString(), DEBUG: env.get('DEBUG').default('false').asBool(), API_KEYS: env.get('API_KEYS').asArray()};Pros: simple API, built-in converters (number, port, boolean, array).
Cons: no TypeScript integration at the type level — the result is still string | number | boolean.
Industrial solutions: convict and node-config
Convict
A library for describing a configuration schema with validation, default values, and support for various sources (files, env, command-line arguments).
// npm install convict @types/convictimport convict from 'convict';
const config = convict({ env: { format: ['production', 'development', 'test'], default: 'development', env: 'NODE_ENV' }, server: { port: { format: 'port', default: 3000, env: 'PORT' } }, db: { host: { format: String, default: 'localhost', env: 'DB_HOST' } }});
config.validate({ allowed: 'strict' }); // will throw an error on invalid data
export default config;Convict features:
- Built-in formats:
port,email,url,int,nat(natural number). - Automatic reading from
process.envand JSON files. - Support for nested schemas.
- Disadvantage