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