ConfigShip: Zero-Dependency Configuration Manager for Node.js (Now with Raw Env Support!)
TL;DR: Just released ConfigShip v0.1.2 - a tiny (zero dependencies!), secure config manager with layered resolution and raw environment variable support. Perfect for both apps and npm packages.
π GitHub: https://github.com/dibakarmitra/config-ship
π¦ NPM: https://www.npmjs.com/package/config-ship
The Problem
I got tired of config management being messy. You've got:
- Hardcoded defaults scattered everywhere
-
.envfiles that are hard to work with - Different configs for dev/staging/prod
- Runtime overrides that break things
- Prototype pollution vulnerabilities (yes, really)
Existing solutions either have too many dependencies, lack features, or are over-engineered.
My Solution: ConfigShip
Zero dependencies. Seriously. Check the package.json yourself.
What Makes It Different?
- Layered Resolution (clear priority):
runtime β env β file β defaults
- Raw Env Names (NEW in v0.1.2):
config.get("APP_NAME") // Works!
config.get("name") // Also works! (transformed)
-
Secure by Default:
- Protected against prototype pollution
- TypeScript strict mode
- No crashes on missing config
Auto-Parsing:
APP_PORT=3000 # β number 3000
APP_ENABLED=true # β boolean true
APP_DATA={"x":1} # β object {x:1}
Quick Example
import { createConfig } from "config-ship";
const config = createConfig({
defaults: {
db: { host: "localhost", port: 5432 }
},
envPrefix: "APP_",
envFile: ".env"
});
// Access anywhere
config.get("db.host");
config.set("db.port", 5433);
Real-World Example: Express App
Here's how I use it in production:
// config/index.js
import { createConfig } from "config-ship";
export const config = createConfig({
defaults: {
server: {
port: 3000,
host: "localhost"
},
db: {
host: "localhost",
port: 5432,
pool: { min: 2, max: 10 }
},
redis: {
url: "redis://localhost:6379"
},
features: {
rateLimiting: true,
analytics: false
}
},
envPrefix: "MYAPP_",
envFile: ".env", // optional auto-loading of process.env
rootFile: `config/${process.env.NODE_ENV}.js`
});
// app.js
import express from "express";
import { config } from "./config/index.js";
const app = express();
app.listen(
config.get("server.port"),
() => console.log(`Running on port ${config.get("server.port")}`)
);
Production .env:
MYAPP_SERVER__PORT=8080
MYAPP_DB__HOST=prod-db.internal
MYAPP_DB__POOL__MAX=50
MYAPP_REDIS__URL=redis://prod:6379
Notice the __ for nested keys!
Environment Variables Made Easy
The syntax:
- Prefix:
APP_ - Single
_separates prefix - Double
__creates nested paths
APP_DB__HOST=localhost # β db.host
APP_DB__PORT=5432 # β db.port (parsed to number)
APP_FEATURE__BETA=true # β feature.beta (parsed to boolean)
Use Case: Feature Flags
const config = createConfig({
defaults: {
features: {
darkMode: false,
newUI: false,
betaAccess: false
}
},
envPrefix: "FEATURES_"
});
function featureEnabled(name) {
return config.get(`features.${name}`, false);
}
// Usage
if (featureEnabled("darkMode")) {
applyDarkTheme();
}
// Enable for beta users
if (user.isBetaTester) {
config.set("features.betaAccess", true);
}
Use Case: NPM Package Configuration
If you're building an npm package:
import { createConfig } from "config-ship";
export function createClient(userOptions = {}) {
const config = createConfig({
defaults: {
timeout: 5000,
retries: 3,
baseURL: "https://api.example.com"
},
envPrefix: "MY_PKG_"
});
// User options override everything
Object.keys(userOptions).forEach(key => {
config.set(key, userOptions[key]);
});
return {
config,
// your API
};
}
Users can configure via:
- Your defaults
- Env vars:
MY_PKG_TIMEOUT=10000 - Runtime:
createClient({ timeout: 3000 })
Security: Prototype Pollution Protection
This was a critical fix in v0.1.2:
// Malicious attempts are blocked β
config.set("__proto__.polluted", true);
config.set("constructor.prototype.polluted", true);
console.log({}.polluted); // undefined (safe!)
Tested with comprehensive security tests.
What's New in v0.1.2?
- β¨ Raw env names:
config.get("APP_NAME")now works - π Security: Fixed prototype pollution vulnerability
- πͺ TypeScript: Strict mode enabled
- π Docs: Better examples and use cases
- β Tests: 37 tests, all passing
Why Not Just Use...?
dotenv: Only loads env vars, no layering, no runtime overrides
config: 5+ dependencies, complex setup, no raw env names
convict: Great but heavy, validation overhead
ConfigShip: Zero deps, simple, secure, batteries-included.
Installation
npm install config-ship
Links
- π¦ NPM: https://www.npmjs.com/package/config-ship
- π GitHub: https://github.com/dibakarmitra/config-ship
- π Changelog: CHANGELOG.md
Try It
npm install config-ship
Would love your feedback! What config problems are you facing?
Edit: Thanks for all the feedback! Added clarification about env var naming and raw name support.
Subreddits to post to:
- r/node
- r/javascript
- r/typescript
- r/webdev
- r/programming (if it gains traction)
Top comments (0)