Bun Environment Variables
Bun provides powerful environment variable management, automatically loading .env files, and offering multiple access methods. This chapter introduces Bun environment variables usage.
Auto-loading .env Files
Basic Usage
Bun automatically loads .env files in the project root:
bash
# .env
APP_NAME=myapp
APP_PORT=3000
DATABASE_URL=postgresql://localhost:5432/mydb
API_KEY=your-secret-key-here
DEBUG=truetypescript
// app.ts
console.log(Bun.env.APP_NAME); // "myapp"
console.log(Bun.env.APP_PORT); // "3000"
console.log(Bun.env.DATABASE_URL); // "postgresql://localhost:5432/mydb"Run:
bash
bun app.tsLoading Order
Bun loads environment variables in the following order (later values override earlier ones):
- System environment variables
.envfile.env.localfile.env.development/.env.production(based on NODE_ENV).env.development.local/.env.production.local
Accessing Environment Variables
Using Bun.env
typescript
// Bun recommended way
const appName = Bun.env.APP_NAME;
const port = Bun.env.APP_PORT;
// With defaults
const debug = Bun.env.DEBUG ?? "false";
const timeout = Bun.env.TIMEOUT || "5000";Using process.env
typescript
// Node.js compatible way
const appName = process.env.APP_NAME;
const port = process.env.APP_PORT;
// Equivalent to Bun.env
console.log(Bun.env === process.env); // trueType-safe Access
typescript
// Define environment variable interface
interface Env {
APP_NAME: string;
APP_PORT: string;
DATABASE_URL: string;
API_KEY: string;
DEBUG?: string;
}
// Type assertion
const env = Bun.env as unknown as Env;
// Or create config object
const config = {
appName: Bun.env.APP_NAME!,
port: parseInt(Bun.env.APP_PORT || "3000"),
databaseUrl: Bun.env.DATABASE_URL!,
apiKey: Bun.env.API_KEY!,
debug: Bun.env.DEBUG === "true",
};Multi-environment Configuration
Environment Files
bash
# .env - Shared across all environments
APP_NAME=myapp
# .env.development - Development environment
NODE_ENV=development
API_URL=http://localhost:3000
DEBUG=true
# .env.production - Production environment
NODE_ENV=production
API_URL=https://api.example.com
DEBUG=false
# .env.local - Local overrides (not committed to Git)
API_KEY=my-local-key.gitignore Configuration
gitignore
# Don't commit sensitive local config
.env.local
.env.*.local
.env.development.local
.env.production.localSpecifying Environment Files
bash
# Use --env-file to specify file
bun --env-file .env.staging app.ts
# Can be used multiple times
bun --env-file .env --env-file .env.staging app.tsEnvironment Variable Syntax
Basic Syntax
bash
# Simple assignment
KEY=value
# With quotes (preserve spaces)
MESSAGE="Hello World"
SINGLE='Hello World'
# Multi-line value
MULTILINE="Line 1
Line 2
Line 3"
# Empty value
EMPTY=
EMPTY_QUOTED=""Variable References
bash
# Reference other variables
BASE_URL=https://api.example.com
API_ENDPOINT=${BASE_URL}/v1
# With defaults
PORT=${APP_PORT:-3000}
HOST=${APP_HOST:-localhost}Comments
bash
# This is a comment
APP_NAME=myapp # End-of-line comment
# Multi-line comment
# API_KEY=old-key
API_KEY=new-keyEscape Characters
bash
# Include special characters
PASSWORD="pass\$word"
PATH_WITH_SPACES="/path/to/my\ folder"Configuration Validation
Startup Validation
typescript
// config.ts
function getRequiredEnv(key: string): string {
const value = Bun.env[key];
if (!value) {
throw new Error(`Missing required environment variable: ${key}`);
}
return value;
}
function getOptionalEnv(key: string, defaultValue: string): string {
return Bun.env[key] || defaultValue;
}
export const config = {
// Required environment variables
databaseUrl: getRequiredEnv("DATABASE_URL"),
apiKey: getRequiredEnv("API_KEY"),
// Optional environment variables
port: parseInt(getOptionalEnv("PORT", "3000")),
debug: getOptionalEnv("DEBUG", "false") === "true",
logLevel: getOptionalEnv("LOG_LEVEL", "info"),
};
console.log("Config loaded successfully:", config);Using Zod Validation
typescript
// config.ts
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
APP_PORT: z.string().transform(Number).default("3000"),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(10),
DEBUG: z.string().transform(v => v === "true").default("false"),
});
export const config = envSchema.parse(Bun.env);
// Type inference
// config.NODE_ENV: "development" | "production" | "test"
// config.APP_PORT: number
// config.DEBUG: booleanDynamically Setting Environment Variables
Setting in Code
typescript
// Set environment variables
Bun.env.NEW_VAR = "new value";
process.env.ANOTHER_VAR = "another value";
// Read
console.log(Bun.env.NEW_VAR); // "new value"
console.log(Bun.env.ANOTHER_VAR); // "another value"
// Delete
delete Bun.env.NEW_VAR;
console.log(Bun.env.NEW_VAR); // undefinedSubprocess Environment Variables
typescript
import { $ } from "bun";
// Pass environment variables to subprocess
const result = await $`echo $MY_VAR`.env({
MY_VAR: "Hello from parent"
}).text();
console.log(result); // "Hello from parent"Security Best Practices
Sensitive Information Handling
typescript
// ❌ Don't do this
console.log("API Key:", Bun.env.API_KEY);
// ✅ Safe logging
console.log("API Key:", Bun.env.API_KEY ? "***SET***" : "NOT SET");
// ✅ Use masking
function maskSensitive(value: string): string {
if (!value || value.length < 8) return "***";
return value.slice(0, 4) + "***" + value.slice(-4);
}
console.log("API Key:", maskSensitive(Bun.env.API_KEY || ""));Environment Variable Template
Create .env.example as a template:
bash
# .env.example - Commit to version control
# Copy this file to .env and fill in actual values
# App configuration
APP_NAME=myapp
APP_PORT=3000
# Database configuration (required)
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# API key (required)
API_KEY=your-api-key-here
# Optional configuration
DEBUG=false
LOG_LEVEL=infoSecret Management
typescript
// For production, use secret management service
import { SecretsManager } from "@aws-sdk/client-secrets-manager";
async function loadSecrets() {
if (Bun.env.NODE_ENV === "production") {
const client = new SecretsManager({ region: "ap-northeast-1" });
const response = await client.getSecretValue({ SecretId: "my-app/prod" });
const secrets = JSON.parse(response.SecretString!);
// Merge into environment variables
Object.assign(Bun.env, secrets);
}
}
await loadSecrets();bunfig.toml Environment Configuration
toml
# bunfig.toml
[run]
# Specify environment files
env-file = [".env", ".env.local"]
# Set environment variables
[run.env]
NODE_ENV = "development"
LOG_LEVEL = "debug"Special Environment Variables
Bun-specific Variables
| Variable | Description |
|---|---|
BUN_INSTALL | Bun installation directory |
BUN_CONFIG_VERBOSE_FETCH | Print fetch request details |
BUN_CONFIG_MAX_HTTP_CONNECTIONS | Max HTTP connections |
BUN_JSC_* | JavaScriptCore engine options |
Node.js Compatible Variables
| Variable | Description |
|---|---|
NODE_ENV | Runtime environment |
NODE_PATH | Module search path |
NODE_DEBUG | Debug module |
Practical Examples
Complete Configuration Management
typescript
// src/config/index.ts
import { z } from "zod";
// Environment variable schema
const envSchema = z.object({
// App
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
APP_NAME: z.string().default("my-app"),
APP_PORT: z.coerce.number().default(3000),
// Database
DATABASE_URL: z.string().url(),
DATABASE_POOL_SIZE: z.coerce.number().default(10),
// Redis
REDIS_URL: z.string().url().optional(),
// Authentication
JWT_SECRET: z.string().min(32),
JWT_EXPIRES_IN: z.string().default("7d"),
// External services
API_KEY: z.string().optional(),
// Logging
LOG_LEVEL: z.enum(["error", "warn", "info", "debug"]).default("info"),
// Feature flags
FEATURE_NEW_UI: z.coerce.boolean().default(false),
});
// Parse and export
const parsed = envSchema.safeParse(Bun.env);
if (!parsed.success) {
console.error("❌ Environment variable configuration error:");
console.error(parsed.error.format());
process.exit(1);
}
export const config = parsed.data;
// Derived config
export const isDev = config.NODE_ENV === "development";
export const isProd = config.NODE_ENV === "production";
export const isTest = config.NODE_ENV === "test";Using Configuration
typescript
// src/index.ts
import { config, isDev } from "./config";
const server = Bun.serve({
port: config.APP_PORT,
fetch(request) {
if (isDev) {
console.log("Received request:", request.url);
}
return new Response(`Welcome to ${config.APP_NAME}!`);
},
});
console.log(`${config.APP_NAME} running on port ${server.port}`);Summary
This chapter covered:
- ✅ Auto-loading .env files
- ✅ Multiple ways to access environment variables
- ✅ Multi-environment configuration management
- ✅ Environment variable syntax and variable references
- ✅ Configuration validation and type safety
- ✅ Security best practices
Next Steps
Continue reading File I/O to learn about Bun's high-performance file handling capabilities.