Overview

Validate environment variables at startup with Zod. Provides typed access to configuration.

Features

  • Zod schema validation
  • Startup validation
  • Type-safe getters
  • Environment helpers

Installation

npx hanma add config-provider --framework nest

Dependencies

zod

Code

import { Injectable, OnModuleInit, Logger } from "@nestjs/common";
import { z } from "zod";

// Define your configuration schema
const ConfigSchema = z.object({
  // Application
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
  PORT: z.coerce.number().default(3000),
  API_PREFIX: z.string().default("api"),
  
  // Database
  DATABASE_URL: z.string().url(),
  DB_POOL_SIZE: z.coerce.number().min(1).max(100).default(10),
  
  // Redis
  REDIS_HOST: z.string().default("localhost"),
  REDIS_PORT: z.coerce.number().default(6379),
  REDIS_PASSWORD: z.string().optional(),
  
  // JWT
  JWT_SECRET: z.string().min(32),
  JWT_EXPIRES_IN: z.string().default("7d"),
  JWT_REFRESH_EXPIRES_IN: z.string().default("30d"),
  
  // CORS
  CORS_ORIGIN: z.string().default("*"),
  
  // Optional external services
  AWS_ACCESS_KEY_ID: z.string().optional(),
  AWS_SECRET_ACCESS_KEY: z.string().optional(),
  AWS_REGION: z.string().default("us-east-1"),
  S3_BUCKET: z.string().optional(),
  
  // Logging
  LOG_LEVEL: z.enum(["error", "warn", "info", "debug", "verbose"]).default("info"),
});

export type Config = z.infer<typeof ConfigSchema>;

@Injectable()
export class ConfigService implements OnModuleInit {
  private readonly logger = new Logger(ConfigService.name);
  private config: Config;

  onModuleInit() {
    this.validateConfig();
  }

  private validateConfig() {
    const result = ConfigSchema.safeParse(process.env);

    if (!result.success) {
      const errors = result.error.issues.map(
        (issue) => `  - ${issue.path.join(".")}: ${issue.message}`
      );

      this.logger.error(
        `Configuration validation failed:\n${errors.join("\n")}`
      );

      throw new Error("Invalid configuration. Check environment variables.");
    }

    this.config = result.data;
    this.logger.log("Configuration validated successfully");
  }

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

  getOrThrow<K extends keyof Config>(key: K): NonNullable<Config[K]> {
    const value = this.config[key];
    if (value === undefined || value === null) {
      throw new Error(`Configuration key "${key}" is not set`);
    }
    return value as NonNullable<Config[K]>;
  }

  getAll(): Readonly<Config> {
    return Object.freeze({ ...this.config });
  }

  // Type-safe getters for common configurations
  get isDevelopment(): boolean {
    return this.config.NODE_ENV === "development";
  }

  get isProduction(): boolean {
    return this.config.NODE_ENV === "production";
  }

  get isTest(): boolean {
    return this.config.NODE_ENV === "test";
  }

  get port(): number {
    return this.config.PORT;
  }

  get databaseUrl(): string {
    return this.config.DATABASE_URL;
  }

  get jwtConfig() {
    return {
      secret: this.config.JWT_SECRET,
      expiresIn: this.config.JWT_EXPIRES_IN,
      refreshExpiresIn: this.config.JWT_REFRESH_EXPIRES_IN,
    };
  }

  get redisConfig() {
    return {
      host: this.config.REDIS_HOST,
      port: this.config.REDIS_PORT,
      password: this.config.REDIS_PASSWORD,
    };
  }

  get awsConfig() {
    return {
      accessKeyId: this.config.AWS_ACCESS_KEY_ID,
      secretAccessKey: this.config.AWS_SECRET_ACCESS_KEY,
      region: this.config.AWS_REGION,
      s3Bucket: this.config.S3_BUCKET,
    };
  }

  get corsOrigins(): string[] {
    return this.config.CORS_ORIGIN.split(",").map((o) => o.trim());
  }
}

// Usage:
/*
import { ConfigService } from "./providers/config.service";

@Injectable()
export class SomeService {
  constructor(private readonly config: ConfigService) {}

  doSomething() {
    const port = this.config.get("PORT");
    const dbUrl = this.config.databaseUrl;
    
    if (this.config.isDevelopment) {
      // Development-only code
    }
  }
}
*/

Usage

const port = this.config.get('PORT');
const jwt = this.config.jwtConfig;

Dependencies

Frequently Asked Questions

How do I install Config Service?
Run the following command: npx hanma add config-provider --framework nest
What dependencies are required?
This snippet requires: zod. Make sure to install them before using the snippet.
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to src/providers/config.service.ts.
Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.