Skip to content

Environment Configuration

Environment Variables Schema

typescript
// infrastructure/config/env.schema.ts
import { z } from 'zod';

export const envSchema = z.object({
  // Application
  NODE_ENV: z.enum(['development', 'staging', 'production']),
  PORT: z.coerce.number().default(3000),
  API_PREFIX: z.string().default('api/v1'),

  // Database
  DATABASE_URL: z.string().url(),
  DATABASE_POOL_MIN: z.coerce.number().default(2),
  DATABASE_POOL_MAX: z.coerce.number().default(10),

  // Redis (optional)
  REDIS_URL: z.string().url().optional(),

  // JWT
  JWT_SECRET: z.string().min(32),
  JWT_ACCESS_EXPIRES_IN: z.string().default('15m'),
  JWT_REFRESH_EXPIRES_IN: z.string().default('7d'),

  // Payments (Stripe)
  STRIPE_SECRET_KEY: z.string().optional(),
  STRIPE_WEBHOOK_SECRET: z.string().optional(),

  // Payments (YooKassa)
  YOOKASSA_SHOP_ID: z.string().optional(),
  YOOKASSA_SECRET_KEY: z.string().optional(),

  // Notifications
  SENDGRID_API_KEY: z.string().optional(),
  SENDGRID_FROM_EMAIL: z.string().email().optional(),
  TWILIO_ACCOUNT_SID: z.string().optional(),
  TWILIO_AUTH_TOKEN: z.string().optional(),
  TWILIO_FROM_NUMBER: z.string().optional(),

  // Storage
  S3_BUCKET: z.string().optional(),
  S3_REGION: z.string().optional(),
  S3_ACCESS_KEY: z.string().optional(),
  S3_SECRET_KEY: z.string().optional(),

  // Frontend URL (for CORS)
  FRONTEND_URL: z.string().url(),

  // Feature flags
  FEATURE_INVESTMENTS_ENABLED: z.coerce.boolean().default(true),
  FEATURE_ECOMMERCE_ENABLED: z.coerce.boolean().default(true),
});

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

Configuration Service

typescript
// infrastructure/config/config.service.ts
@Injectable()
export class ConfigService {
  private readonly env: Env;

  constructor() {
    const result = envSchema.safeParse(process.env);

    if (!result.success) {
      const errors = result.error.flatten().fieldErrors;
      console.error('Invalid environment variables:');
      Object.entries(errors).forEach(([key, messages]) => {
        console.error(`  ${key}: ${messages?.join(', ')}`);
      });
      process.exit(1);
    }

    this.env = result.data;
  }

  get<K extends keyof Env>(key: K): Env[K] {
    return this.env[key];
  }

  get isProduction(): boolean {
    return this.env.NODE_ENV === 'production';
  }

  get isDevelopment(): boolean {
    return this.env.NODE_ENV === 'development';
  }

  get database() {
    return {
      url: this.env.DATABASE_URL,
      poolMin: this.env.DATABASE_POOL_MIN,
      poolMax: this.env.DATABASE_POOL_MAX,
    };
  }

  get jwt() {
    return {
      secret: this.env.JWT_SECRET,
      accessExpiresIn: this.env.JWT_ACCESS_EXPIRES_IN,
      refreshExpiresIn: this.env.JWT_REFRESH_EXPIRES_IN,
    };
  }

  get payments() {
    return {
      stripe: this.env.STRIPE_SECRET_KEY ? {
        secretKey: this.env.STRIPE_SECRET_KEY,
        webhookSecret: this.env.STRIPE_WEBHOOK_SECRET,
      } : null,
      yookassa: this.env.YOOKASSA_SHOP_ID ? {
        shopId: this.env.YOOKASSA_SHOP_ID,
        secretKey: this.env.YOOKASSA_SECRET_KEY,
      } : null,
    };
  }
}

Example .env Files

Development (.env.example)

bash
# .env.example (commit this)
NODE_ENV=development
PORT=3000
API_PREFIX=api/v1

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/iwm_dev

# JWT (generate with: openssl rand -base64 32)
JWT_SECRET=your-secret-here-min-32-chars
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d

# Frontend
FRONTEND_URL=http://localhost:5173

# Optional integrations (leave empty in dev)
STRIPE_SECRET_KEY=
YOOKASSA_SHOP_ID=
SENDGRID_API_KEY=

Production (.env.production)

bash
# .env.production (DO NOT commit, use secrets manager)
NODE_ENV=production
DATABASE_URL=postgresql://...  # From secrets manager
JWT_SECRET=...                  # From secrets manager
STRIPE_SECRET_KEY=sk_live_...   # From secrets manager