Configuration
Bunty provides type-safe, schema-validated environment configuration with support for hot-reloading via mounted config files. Schema validation is built into @bunty/common - no extra packages needed.
Basic Usage
Use the env() function to read environment variables with schema validation:
import { env, t } from "@bunty/common";
// Define your configuration with type-safe schemas
const dbUrl = env(
"DB_URL",
t.String()
.Default("postgres://postgres:password@localhost:5432/bunty_dev")
.Required()
.Description("Database connection URL")
);
const port = env(
"PORT",
t.Number()
.Default(3000)
.Min(1)
.Max(65535)
.Description("Server port")
);
const nodeEnv = env(
"NODE_ENV",
t.Enum(["development", "production", "test"])
.Default("development")
.Description("Application environment")
);
// Use your config
console.log(`Connecting to database: ${dbUrl}`);
console.log(`Server will run on port: ${port}`);
📚 Schema Types Reference
The t schema builder is built into @bunty/common. For a complete reference of all available schema types and validators, see the Schema Types documentation.
Common Configuration Patterns
String
const apiKey = env(
"API_KEY",
t.String()
.Required()
.MinLength(32)
.MaxLength(128)
.Pattern(/^[A-Za-z0-9_-]+$/)
.Description("External API authentication key")
);
const appName = env(
"APP_NAME",
t.String()
.Default("Bunty App")
.Description("Application display name")
);
Number
const maxConnections = env(
"MAX_CONNECTIONS",
t.Number()
.Default(100)
.Min(1)
.Max(1000)
.Integer()
.Description("Maximum database connections")
);
const timeout = env(
"REQUEST_TIMEOUT",
t.Number()
.Default(30000)
.Min(1000)
.Description("Request timeout in milliseconds")
);
Boolean
const enableCaching = env(
"ENABLE_CACHING",
t.Boolean()
.Default(true)
.Description("Enable response caching")
);
const debugMode = env(
"DEBUG",
t.Boolean()
.Default(false)
.Description("Enable debug logging")
);
Enum
const logLevel = env(
"LOG_LEVEL",
t.Enum(["debug", "info", "warn", "error"])
.Default("info")
.Description("Logging level")
);
const dbDialect = env(
"DB_DIALECT",
t.Enum(["mysql", "postgres", "sqlite"])
.Required()
.Description("Database dialect")
);
URL
const redisUrl = env(
"REDIS_URL",
t.URL()
.Default("redis://localhost:6379")
.Description("Redis connection URL")
);
const apiEndpoint = env(
"API_ENDPOINT",
t.URL()
.Required()
.Secure() // Must be HTTPS
.Description("External API endpoint")
);
JSON
const features = env(
"FEATURE_FLAGS",
t.JSON()
.Default({ newUI: false, betaFeatures: false })
.Description("Feature flags configuration")
);
const corsOrigins = env(
"CORS_ORIGINS",
t.JSON()
.Default(["http://localhost:3000"])
.Description("Allowed CORS origins")
);
Environment Files
Create a .env file in your project root:
# Database
DB_URL=postgres://postgres:secret@localhost:5432/bunty_prod
DB_DIALECT=postgres
MAX_CONNECTIONS=50
# Server
PORT=8080
NODE_ENV=production
API_KEY=sk_live_abc123def456xyz789
# Features
ENABLE_CACHING=true
DEBUG=false
LOG_LEVEL=info
# External Services
REDIS_URL=redis://localhost:6379
API_ENDPOINT=https://api.example.com
# JSON Config
FEATURE_FLAGS={"newUI":true,"betaFeatures":false}
CORS_ORIGINS=["https://app.example.com","https://www.example.com"]
Note: Bun automatically loads
.envfiles - no additional setup needed!
Configuration Object
Organize your config into a single object for easy import:
// src/config/index.ts
import { env, t } from "@bunty/common";
export const config = {
// Server
server: {
port: env("PORT", t.Number().Default(3000)),
host: env("HOST", t.String().Default("localhost")),
env: env("NODE_ENV", t.Enum(["development", "production", "test"]).Default("development")),
},
// Database
database: {
url: env("DB_URL", t.String().Required().Description("Database connection URL")),
maxConnections: env("MAX_CONNECTIONS", t.Number().Default(100).Min(1).Max(1000)),
ssl: env("DB_SSL", t.Boolean().Default(false)),
},
// Logging
logging: {
level: env("LOG_LEVEL", t.Enum(["debug", "info", "warn", "error"]).Default("info")),
pretty: env("LOG_PRETTY", t.Boolean().Default(true)),
},
// Security
security: {
apiKey: env("API_KEY", t.String().Required().MinLength(32)),
corsOrigins: env("CORS_ORIGINS", t.JSON().Default(["*"])),
jwtSecret: env("JWT_SECRET", t.String().Required().MinLength(64)),
},
// Features
features: {
caching: env("ENABLE_CACHING", t.Boolean().Default(true)),
rateLimit: env("ENABLE_RATE_LIMIT", t.Boolean().Default(true)),
debug: env("DEBUG", t.Boolean().Default(false)),
},
};
Use throughout your app:
import { config } from "./config";
const app = createApp();
app.listen(config.server.port, () => {
console.log(`Server running on ${config.server.host}:${config.server.port}`);
console.log(`Environment: ${config.server.env}`);
});
Hot-Reloading with Mounted Config Files
Bunty supports hot-reloading via mounted configuration files - perfect for Docker/Kubernetes deployments:
Mount a Config File
import { watchConfig } from "@bunty/config";
// Watch a mounted config file for changes
const configWatcher = watchConfig("/etc/bunty/config.json", (newConfig) => {
console.log("Configuration updated:", newConfig);
// Update your application config
Object.assign(config, newConfig);
// Trigger reconnections, refresh caches, etc.
reconnectDatabase();
});
// Stop watching when app shuts down
process.on("SIGTERM", () => {
configWatcher.stop();
});
Mounted Config Example
/etc/bunty/config.json:
{
"database": {
"maxConnections": 200,
"ssl": true
},
"features": {
"newUI": true,
"betaFeatures": false
},
"logging": {
"level": "warn"
}
}
When this file changes, your application automatically picks up the new values without restart!
Merge Strategies
import { watchConfig, MergeStrategy } from "@bunty/config";
// Deep merge (default)
watchConfig("/etc/bunty/config.json", onUpdate, {
strategy: MergeStrategy.Deep
});
// Shallow merge
watchConfig("/etc/bunty/config.json", onUpdate, {
strategy: MergeStrategy.Shallow
});
// Replace entire config
watchConfig("/etc/bunty/config.json", onUpdate, {
strategy: MergeStrategy.Replace
});
Validation Errors
Bunty config provides clear, actionable error messages:
// Missing required variable
env("API_KEY", t.String().Required())
// ❌ Error: Environment variable "API_KEY" is required but not set
// Invalid type
env("PORT", t.Number())
// ❌ Error: Environment variable "PORT" must be a number, got "abc"
// Out of range
env("MAX_CONNECTIONS", t.Number().Min(1).Max(1000))
// ❌ Error: Environment variable "MAX_CONNECTIONS" must be between 1 and 1000, got 5000
// Invalid enum value
env("LOG_LEVEL", t.Enum(["debug", "info", "warn", "error"]))
// ❌ Error: Environment variable "LOG_LEVEL" must be one of: debug, info, warn, error. Got: trace
Best Practices
- Always validate - Use schemas for all environment variables
- Provide defaults - Make local development easy with sensible defaults
- Add descriptions - Help developers understand what each variable does
- Never commit secrets - Use
.env.exampleas a template,.envin.gitignore - Fail fast - Load all config at startup, not lazily during requests
- Use type inference - Let TypeScript infer types from your schemas
- Document requirements - List all required env vars in README
.env.example Template
Create a .env.example file to help other developers:
# Server Configuration
PORT=3000
HOST=localhost
NODE_ENV=development
# Database
DB_URL=postgres://postgres:password@localhost:5432/bunty_dev
DB_DIALECT=postgres
MAX_CONNECTIONS=100
DB_SSL=false
# Logging
LOG_LEVEL=info
LOG_PRETTY=true
DEBUG=false
# Security (REQUIRED in production)
API_KEY=your-secret-api-key-min-32-chars
JWT_SECRET=your-jwt-secret-min-64-chars-long
# External Services
REDIS_URL=redis://localhost:6379
# Features
ENABLE_CACHING=true
ENABLE_RATE_LIMIT=true
# CORS
CORS_ORIGINS=["http://localhost:3000"]
⚠️ Security Warning
Never commit .env files or secrets to version control. Use .env.example as a template and keep actual secrets in environment variables or secret management systems.
Next Steps
- Build your first HTTP API
- Set up Database connections (optional)
- Learn about Dependency Injection