Skip to content

Environment Configuration

Environment Configuration provides type-safe configuration management with Zod validation. Prevent runtime errors from misconfiguration.

Overview

Flowfull uses environment variables for configuration with:

  • ✅ Zod validation
  • ✅ Fail-fast on startup
  • ✅ Type-safe access
  • ✅ Clear error messages
  • ✅ Auto-detection

Basic Configuration

.env File

env
# Server
PORT=3000
NODE_ENV=development

# Database
DATABASE_TYPE=postgresql
DATABASE_URL=postgresql://localhost:5432/mydb

# Flowless Integration
FLOWLESS_URL=https://your-instance.pubflow.com
BRIDGE_SECRET=your-bridge-secret-here

# Security
VALIDATION_MODE=STANDARD

# Cache (Optional)
CACHE_ENABLED=true
REDIS_URL=redis://localhost:6379

# PASETO (Optional)
PASETO_SECRET_KEY=your-paseto-secret-key

Validation Schema

typescript
import { z } from 'zod';

const envSchema = z.object({
  // Server
  PORT: z.string().default('3000'),
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  
  // Database
  DATABASE_TYPE: z.enum(['postgresql', 'mysql', 'libsql']),
  DATABASE_URL: z.string().url(),
  DATABASE_AUTH_TOKEN: z.string().optional(),
  
  // Flowless
  FLOWLESS_URL: z.string().url(),
  BRIDGE_SECRET: z.string().min(32),
  
  // Security
  VALIDATION_MODE: z.enum(['DISABLED', 'STANDARD', 'ADVANCED', 'STRICT']).default('STANDARD'),
  
  // Cache
  CACHE_ENABLED: z.string().transform(val => val === 'true').default('true'),
  REDIS_URL: z.string().url().optional(),
  
  // PASETO
  PASETO_SECRET_KEY: z.string().optional(),
});

export type Env = z.infer<typeof envSchema>;

// Validate on startup
export const env = envSchema.parse(process.env);

Usage

typescript
import { env } from './config/environment';

// Type-safe access
console.log(env.PORT);              // string
console.log(env.CACHE_ENABLED);     // boolean
console.log(env.VALIDATION_MODE);   // 'DISABLED' | 'STANDARD' | 'ADVANCED' | 'STRICT'

// TypeScript knows the types!
const port = parseInt(env.PORT);    // ✅ Type-safe
const cacheEnabled = env.CACHE_ENABLED;  // ✅ boolean

Fail-Fast Validation

If configuration is invalid, the app fails on startup:

bash
$ bun run dev

 Environment validation failed:
  - FLOWLESS_URL: Invalid URL
  - BRIDGE_SECRET: String must contain at least 32 character(s)
  - DATABASE_TYPE: Invalid enum value. Expected 'postgresql' | 'mysql' | 'libsql'

Fix your .env file and try again.

Required vs Optional

typescript
const envSchema = z.object({
  // Required
  DATABASE_URL: z.string().url(),
  
  // Optional with default
  PORT: z.string().default('3000'),
  
  // Optional without default
  REDIS_URL: z.string().url().optional(),
});

// Usage
const port = env.PORT;           // Always defined (has default)
const redisUrl = env.REDIS_URL;  // May be undefined

Transformations

typescript
const envSchema = z.object({
  // String to boolean
  CACHE_ENABLED: z.string()
    .transform(val => val === 'true')
    .default('true'),
  
  // String to number
  PORT: z.string()
    .transform(val => parseInt(val))
    .default('3000'),
  
  // String to array
  ALLOWED_ORIGINS: z.string()
    .transform(val => val.split(','))
    .default('http://localhost:3000'),
});

// Usage
const cacheEnabled: boolean = env.CACHE_ENABLED;
const port: number = env.PORT;
const origins: string[] = env.ALLOWED_ORIGINS;

Environment-Specific Config

typescript
const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']),
  
  // Development only
  DEBUG: z.string().optional(),
  
  // Production only
  SENTRY_DSN: z.string().url().optional(),
});

// Conditional validation
if (env.NODE_ENV === 'production') {
  if (!env.SENTRY_DSN) {
    throw new Error('SENTRY_DSN required in production');
  }
}

Best Practices

✅ Do

  • Validate all environment variables
  • Use Zod for type safety
  • Fail fast on invalid config
  • Provide clear error messages
  • Use .env.example for documentation

❌ Don't

  • Skip validation
  • Use process.env directly
  • Hardcode secrets
  • Commit .env to git
  • Use default values for secrets

.env.example

Create a .env.example file for documentation:

env
# Server Configuration
PORT=3000
NODE_ENV=development

# Database Configuration
DATABASE_TYPE=postgresql
DATABASE_URL=postgresql://user:password@localhost:5432/mydb

# Flowless Integration
FLOWLESS_URL=https://your-instance.pubflow.com
BRIDGE_SECRET=your-bridge-secret-here

# Security Configuration
VALIDATION_MODE=STANDARD

# Cache Configuration (Optional)
CACHE_ENABLED=true
REDIS_URL=redis://localhost:6379

# PASETO Configuration (Optional)
PASETO_SECRET_KEY=your-paseto-secret-key

Loading Environment

typescript
// Load .env file
import 'dotenv/config';

// Or with custom path
import { config } from 'dotenv';
config({ path: '.env.local' });

// Validate
import { env } from './config/environment';

Multiple Environments

bash
# Development
.env.development

# Production
.env.production

# Test
.env.test

Load based on NODE_ENV:

typescript
import { config } from 'dotenv';

const envFile = `.env.${process.env.NODE_ENV || 'development'}`;
config({ path: envFile });

Secrets Management

Development

env
# .env (not committed)
BRIDGE_SECRET=dev-secret-key

Production

Use environment variables from your hosting provider:

  • Vercel: Environment Variables
  • Railway: Environment Variables
  • Fly.io: Secrets
  • AWS: Parameter Store
  • Google Cloud: Secret Manager

Next Steps

Need Help?

Released under the MIT License.