JSON Environment Files
Envitron supports native JSON environment files alongside traditional .env files. JSON files provide better structure for complex configurations with nested objects and typed values.
Why Use JSON Environment Files?
- Native Types: Numbers, booleans, and arrays without string conversion
- Nested Structures: Complex configurations with multiple levels
- Better for Complex Config: Ideal for microservices, multi-database setups, etc.
Basic Usage
Create a .env.json file:
{
"PORT": 3000,
"DEBUG": true,
"API_KEY": "secret123",
"ALLOWED_HOSTS": ["localhost", "127.0.0.1"]
}
Define your schema:
import { createEnvSchema } from 'envitron';
const env = createEnvSchema(
(schema) => ({
PORT: schema.number(),
DEBUG: schema.boolean(),
API_KEY: schema.string(),
ALLOWED_HOSTS: schema.array(schema.string()),
}),
{ envFile: '.env.json' }
);
console.log(env.PORT); // 3000 (number, not string!)
console.log(env.DEBUG); // true (boolean)
console.log(env.ALLOWED_HOSTS); // ['localhost', '127.0.0.1'] (string[])
Nested Objects
JSON files excel at representing complex nested structures:
{
"database": {
"host": "localhost",
"port": 5432,
"credentials": {
"username": "admin",
"password": "secret"
}
},
"services": {
"api": {
"url": "https://api.example.com",
"timeout": 5000,
"retries": [1000, 2000, 3000]
},
"cache": {
"enabled": true,
"ttl": 3600
}
}
}
const env = createEnvSchema(
(schema) => ({
database: schema.object({
host: schema.string(),
port: schema.number(),
credentials: schema.object({
username: schema.string(),
password: schema.string(),
}),
}),
services: schema.object({
api: schema.object({
url: schema.string(),
timeout: schema.number(),
retries: schema.array(schema.number()),
}),
cache: schema.object({
enabled: schema.boolean(),
ttl: schema.number(),
}),
}),
}),
{ envFile: '.env.json' }
);
// Full type safety with autocomplete!
const dbHost = env.get('database').host; // string
const apiTimeout = env.get('services').api.timeout; // number
const retries = env.get('services').api.retries; // number[]
Arrays of Objects
JSON files can contain arrays of complex objects:
{
"users": [
{ "name": "Alice", "age": 30, "active": true },
{ "name": "Bob", "age": 25, "active": false }
],
"endpoints": [
{ "url": "https://api1.example.com", "timeout": 5000 },
{ "url": "https://api2.example.com", "timeout": 3000 }
]
}
const env = createEnvSchema(
(schema) => ({
users: schema.array(
schema.object({
name: schema.string(),
age: schema.number(),
active: schema.boolean(),
})
),
endpoints: schema.array(
schema.object({
url: schema.string(),
timeout: schema.number(),
})
),
}),
{ envFile: '.env.json' }
);
// Type: Array<{ name: string; age: number; active: boolean }>
const users = env.get('users');
console.log(users[0].name); // string
console.log(users[0].age); // number
console.log(users[0].active); // boolean
Auto-Detection
Envitron automatically detects JSON files and parses them accordingly. No special configuration needed:
// Automatically uses JSON parsing for .json files
const env = createEnvSchema(
(schema) => ({ /* ... */ }),
{ envFile: '.env.json' }
);
// Falls back to traditional .env parsing for .env files
const env2 = createEnvSchema(
(schema) => ({ /* ... */ }),
{ envFile: '.env' }
);
Validation Rules
- JSON files must contain a top-level object (not an array or primitive)
- All schema validation rules apply (required/optional, constraints, etc.)
- Type mismatches are caught at load time with detailed error messages
Traditional .env vs JSON
| Feature | Traditional .env | JSON |
|---|---|---|
| Native types | ❌ All strings | ✅ Yes |
| Nested objects | ❌ No | ✅ Yes |
| Arrays | ✅ CSV only | ✅ Native arrays |
| Comments | ✅ Yes (#) | ❌ No |
| Multi-line | ✅ With quotes | ✅ Native |
| Type safety | ⚠️ After parsing | ✅ Native |
Best Practices
- Use JSON for complex configurations: Multi-level nested structures, arrays of objects
- Use .env for simple configs: Single-level key-value pairs, secrets
- Keep secrets in .env files: Better tooling support for secret scanning
- Combine both: Use multiple files with different formats
const env = createEnvSchema(
(schema) => ({ /* ... */ }),
{
envFile: ['.env', '.env.json'], // Merges both files
}
);
Real-World Example
A complete microservices configuration:
{
"application": {
"name": "my-service",
"version": "1.0.0",
"environment": "production"
},
"server": {
"port": 3000,
"host": "0.0.0.0",
"cors": {
"enabled": true,
"origins": ["https://example.com", "https://api.example.com"]
}
},
"database": {
"primary": {
"host": "db.example.com",
"port": 5432,
"name": "myapp",
"pool": {
"min": 2,
"max": 10
}
},
"replica": {
"host": "replica.example.com",
"port": 5432,
"name": "myapp"
}
},
"cache": {
"redis": {
"host": "redis.example.com",
"port": 6379,
"ttl": 3600
}
},
"features": {
"enableNewUI": true,
"enableBetaFeatures": false,
"maintenanceMode": false
}
}
const env = createEnvSchema(
(schema) => ({
application: schema.object({
name: schema.string(),
version: schema.string(),
environment: schema.enum(['development', 'staging', 'production'] as const),
}),
server: schema.object({
port: schema.number(),
host: schema.string(),
cors: schema.object({
enabled: schema.boolean(),
origins: schema.array(schema.string()),
}),
}),
database: schema.object({
primary: schema.object({
host: schema.string(),
port: schema.number(),
name: schema.string(),
pool: schema.object({
min: schema.number(),
max: schema.number(),
}),
}),
replica: schema.object({
host: schema.string(),
port: schema.number(),
name: schema.string(),
}),
}),
cache: schema.object({
redis: schema.object({
host: schema.string(),
port: schema.number(),
ttl: schema.number(),
}),
}),
features: schema.object({
enableNewUI: schema.boolean(),
enableBetaFeatures: schema.boolean(),
maintenanceMode: schema.boolean(),
}),
}),
{ envFile: '.env.json' }
);
// Full type safety throughout your application
const dbHost = env.get('database').primary.host;
const poolSize = env.get('database').primary.pool.max;
const corsOrigins = env.get('server').cors.origins;
const isMaintenanceMode = env.get('features').maintenanceMode;