config, dotenv, nconf, properties-reader, and rc are utilities designed to manage application settings across different environments. dotenv loads environment variables from a .env file into process.env. config provides a hierarchical configuration system based on files in a config directory. nconf offers a flexible key-value store with multiple sources like files, environment variables, and arguments. properties-reader parses Java-style .properties files. rc loads configuration from files, environment variables, and command line arguments for Node.js applications.
Managing application settings is a critical part of building reliable software. The packages config, dotenv, nconf, properties-reader, and rc all solve this problem but with different approaches. Some focus on environment variables, while others handle file hierarchies or specific formats. Let's compare how they work in real engineering scenarios.
How you load settings affects when your app is ready to run.
dotenv loads variables immediately into process.env.
// dotenv: Load at startup
require('dotenv').config();
console.log(process.env.DB_HOST);
config loads files from a config directory automatically.
config.get().process.env.// config: Load from directory
const config = require('config');
const dbHost = config.get('db.host');
nconf requires you to define sources manually.
// nconf: Define sources
const nconf = require('nconf');
nconf.env().file({ file: 'config.json' });
const dbHost = nconf.get('db:host');
properties-reader reads specific Java-style files.
// properties-reader: Read file
const properties = require('properties-reader');
const props = properties('app.properties');
const dbHost = props.get('db.host');
rc looks for config in standard locations automatically.
// rc: Load from standards
const rc = require('rc');
const conf = rc('appname');
const dbHost = conf.db.host;
When you have different settings for local development versus production, merging logic matters.
config excels at deep merging.
default.json and production.json merge safely.// config: Hierarchical merge
// config/default.json + config/production.json
// { db: { host: 'localhost', port: 5432 } } + { db: { host: 'prod' } }
// Result: { db: { host: 'prod', port: 5432 } }
dotenv does not handle hierarchies.
.env files manually.// dotenv: Manual hierarchy
require('dotenv').config({ path: '.env.local' });
require('dotenv').config({ path: '.env.production' }); // Overwrites
nconf allows priority stacking.
// nconf: Priority stack
nconf.argv().env().file({ file: 'config.json' });
// Command line args override env vars, which override file
properties-reader does not support merging.
// properties-reader: No merge
const base = properties('base.properties');
const prod = properties('prod.properties');
// Manual merge required
rc supports basic hierarchy.
config but more than dotenv.// rc: Auto merge
// Matches .appnamerc, $HOME/.appnamerc, etc.
const conf = rc('appname');
Your team might prefer different file types for settings.
config supports JSON, YAML, and more.
default.yaml into the folder.// config: YAML support
// config/default.yaml
// db:
// host: localhost
dotenv uses key-value text files.
KEY=VALUE.# dotenv: .env file
DB_HOST=localhost
DB_PORT=5432
nconf supports JSON and INI.
.ini files easily.// nconf: INI support
nconf.file({ file: 'settings.ini', format: nconf.formats.ini });
properties-reader specializes in .properties.
# properties-reader: app.properties
db.host=localhost
db.port=5432
rc supports JSON and INI.
// rc: .appnamerc
{ "db": { "host": "localhost" } }
Security updates and maintenance status impact long-term stability.
config is actively maintained.
// config: Secure access
// Validates types if using extensions
const port = config.get('port');
dotenv is highly active and standard.
// dotenv: Standard practice
// Ensure .env is in .gitignore
nconf has had security issues in the past.
// nconf: Check version
// npm list nconf
// Ensure version is patched
properties-reader is stable but niche.
// properties-reader: Niche use
// Verify input files are trusted
rc is minimal and stable.
config.// rc: Minimal footprint
// Good for CLIs, less for servers
While they differ in features, these tools share common goals.
// All: Server-side usage
// Used in Express, Next.js API routes, or build scripts
// config: dot notation
config.get('db.host');
// nconf: colon notation
nconf.get('db:host');
// dotenv: env object
process.env.DB_HOST;
// dotenv: NODE_ENV check
if (process.env.NODE_ENV === 'production') { ... }
// config: file based
// Automatically loads production.json
# All: Install command
npm install config dotenv nconf properties-reader rc
// All: Check repos
// github.com/node-config/node-config
// github.com/motdotla/dotenv
| Feature | Shared by All |
|---|---|
| Runtime | 🟢 Node.js |
| Access | 🔑 Key-Value Lookup |
| Install | 📦 npm / yarn |
| License | ✅ Open Source |
| Use Case | 🌍 Environment Settings |
| Feature | config | dotenv | nconf | properties-reader | rc |
|---|---|---|---|---|---|
| Hierarchy | 🗂️ Deep Merge | ❌ Manual | 🗂️ Priority Stack | ❌ None | 🗂️ Basic |
| Format | 📄 JSON/YAML | 📝 Key-Value | 📄 JSON/INI | 📝 Java Props | 📄 JSON/INI |
| Maintenance | 🟢 Active | 🟢 Active | 🟡 Less Active | 🟡 Stable | 🟡 Stable |
| Best For | 🏢 Enterprise | 🚀 Standard | 🧩 Flexible | ☕ Javainterop | 🖥️ CLI |
| Security | 🔒 High | 🔒 High | ⚠️ Check Version | ⚠️ Niche | 🔒 Minimal |
dotenv is the default choice for most modern web apps 🚀. It is simple, standard, and works everywhere. Use it for loading secrets and flags into process.env.
config is the power tool for complex systems 🏢. If you need safe merging of nested settings across many environments, this is the robust option.
nconf offers flexibility but requires caution 🧩. It is powerful for combining sources but check maintenance status before committing.
properties-reader is a specialist tool ☕. Only use it if you specifically need to read Java-style property files.
rc is perfect for command line tools 🖥️. It handles standard config locations automatically with minimal setup.
Final Thought: For most frontend and full-stack projects, start with dotenv for secrets and config for structured settings. Avoid over-engineering unless your hierarchy demands it.
Choose config if you need a robust hierarchy for different environments like development, staging, and production. It is ideal for large applications where settings need to merge safely without overwriting entire objects. This package handles complex nested structures well.
Choose dotenv if you simply need to load environment variables into process.env at startup. It is the standard choice for Twelve-Factor Apps and works well with modern frameworks like Next.js or Express. Use this for secrets and basic flags.
Choose nconf if you need a flexible store that combines files, environment variables, and command line arguments with priority control. Be aware that maintenance is less active than other options, so verify security patches before use.
Choose properties-reader if you must integrate with legacy Java systems or tools that output .properties files. It is niche and not suitable for general JavaScript configuration needs.
Choose rc if you are building a CLI tool that needs to read configuration from multiple standard locations quickly. It is minimal and best for simple use cases where heavy hierarchy is not required.
Node-config organizes hierarchical configurations for your app deployments.
It lets you define a set of default parameters, and extend them for different deployment environments (development, qa, staging, production, etc.).
Configurations are stored in configuration files within your application, and can be overridden and extended by environment variables, command line parameters, or external sources.
This gives your application a consistent configuration interface shared among a growing list of npm modules also using node-config.
The following examples are in JSON format, but configurations can be in other file formats.
Install in your app directory, and edit the default config file.
$ npm install config
$ mkdir config
$ vi config/default.json
{
// Customer module configs
"Customer": {
"dbConfig": {
"host": "localhost",
"port": 5984,
"dbName": "customers"
},
"credit": {
"initialLimit": 100,
// Set low for development
"initialDays": 1
}
}
}
Edit config overrides for production deployment:
$ vi config/production.json
{
"Customer": {
"dbConfig": {
"host": "prod-db-server"
},
"credit": {
"initialDays": 30
}
}
}
Use configs in your code:
const config = require('config');
//...
const dbConfig = config.get('Customer.dbConfig');
db.connect(dbConfig, ...);
if (config.has('optionalFeature.detail')) {
const detail = config.get('optionalFeature.detail');
//...
}
config.get() will throw an exception for undefined keys to help catch typos and missing values.
Use config.has() to test if a configuration value is defined.
Start your app server:
$ export NODE_ENV=production
$ node my-app.js
Running in this configuration, the port and dbName elements of dbConfig
will come from the default.json file, and the host element will
come from the production.json override file.
Type declarations are published under types/ and resolved via typesVersions. Subpath typings are included for config/async, config/defer, config/parser, config/raw, and config/lib/util in addition to the main config entrypoint.
If you still don't see what you are looking for, here are some more resources to check:
node-config contributors.May be freely distributed under the MIT license.
Copyright (c) 2010-2026 Loren West and other contributors