Skip to main content

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

FeatureTraditional .envJSON
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

  1. Use JSON for complex configurations: Multi-level nested structures, arrays of objects
  2. Use .env for simple configs: Single-level key-value pairs, secrets
  3. Keep secrets in .env files: Better tooling support for secret scanning
  4. 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;

See Also