DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Environment Variable Management with Claude Code: Type-Safe Config and Secret Protection

Scattered process.env.* calls cause typos that crash production and make it easy to miss required configuration. Claude Code can design a type-safe environment configuration module that validates on startup.


CLAUDE.md for Environment Variable Management

## Environment Variable Rules

### Access pattern
- Never use process.env.* directly in code
- All env vars accessed through src/config/env.ts only
- Validate at startup — missing required vars crash with clear message

### Type safety
- Zod schema defines all environment variables
- Auto-coerce: string → number/boolean/URL
- Explicit default values only

### Secret management
- Sensitive values (API keys, DB passwords) in .env (NEVER committed)
- Keep .env.example up to date (keys only, no real values)
- CI/CD secrets: use platform's secret management

### Environments
- development: .env.local
- test: .env.test
- production: real environment variables (no .env files)

### Prohibited
- Committing .env files (add to .gitignore)
- process.env branching (other than NODE_ENV === 'production')
- Using real secrets as default values
Enter fullscreen mode Exit fullscreen mode

Generating the Type-Safe Env Module

Generate a type-safe environment variables module using Zod.

Variables:
- DATABASE_URL: string (valid PostgreSQL URL)
- REDIS_URL: string (valid Redis URL)
- JWT_SECRET: string (minimum 32 characters)
- PORT: number (default: 3000)
- NODE_ENV: 'development' | 'test' | 'production'
- LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error' (default: 'info')
- SENDGRID_API_KEY: string (optional  only needed if email enabled)
- CORS_ORIGINS: string[] (comma-separated URL list)

Requirements:
- Validate at startup  show ALL invalid variables at once, then crash
- Auto-coerce PORT from string to number
- Export TypeScript types

Save to: src/config/env.ts
Enter fullscreen mode Exit fullscreen mode

Generated Env Module

// src/config/env.ts
import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'),
  REDIS_URL: z.string().url('REDIS_URL must be a valid URL'),
  JWT_SECRET: z
    .string()
    .min(32, 'JWT_SECRET must be at least 32 characters'),
  PORT: z.coerce.number().int().min(1).max(65535).default(3000),
  NODE_ENV: z
    .enum(['development', 'test', 'production'])
    .default('development'),
  LOG_LEVEL: z
    .enum(['debug', 'info', 'warn', 'error'])
    .default('info'),
  SENDGRID_API_KEY: z.string().optional(),
  CORS_ORIGINS: z
    .string()
    .transform((val) => val.split(',').map((s) => s.trim()))
    .default('http://localhost:3000'),
});

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

if (!result.success) {
  const errors = result.error.issues.map(
    (issue) => `  - ${issue.path.join('.')}: ${issue.message}`
  );
  console.error('Environment validation failed:');
  console.error(errors.join('\n'));
  process.exit(1);
}

export const env = result.data;
export type Env = typeof env;
Enter fullscreen mode Exit fullscreen mode

Startup output when misconfigured:

Environment validation failed:
  - DATABASE_URL: Invalid url
  - JWT_SECRET: String must contain at least 32 character(s)
Enter fullscreen mode Exit fullscreen mode

Generating .env.example

Read src/config/env.ts and generate .env.example.

Requirements:
- List all environment variables
- Add descriptive comments
- Use example values (never real secrets)
- Show default values where applicable
Enter fullscreen mode Exit fullscreen mode

Generated:

# Database
# PostgreSQL connection string
DATABASE_URL=postgresql://user:password@localhost:5432/myapp

# Redis
REDIS_URL=redis://localhost:6379

# Security
# Minimum 32 characters — generate with: openssl rand -base64 32
JWT_SECRET=change-me-to-a-random-32-char-secret

# Server
PORT=3000
NODE_ENV=development

# Logging (debug|info|warn|error)
LOG_LEVEL=info

# Email (optional — leave empty to disable)
# SENDGRID_API_KEY=SG.your-key-here

# CORS — comma-separated allowed origins
CORS_ORIGINS=http://localhost:3000,http://localhost:3001
Enter fullscreen mode Exit fullscreen mode

Hook: Detect Direct process.env Access

# .claude/hooks/check_env_usage.py
import json, re, sys

data = json.load(sys.stdin)
content = data.get("tool_input", {}).get("content", "") or ""
fp = data.get("tool_input", {}).get("file_path", "")

# Skip env module itself and test files
if not fp or any(x in fp for x in ["env.ts", "test", "spec", ".env"]):
    sys.exit(0)

if not fp.endswith((".ts", ".js")):
    sys.exit(0)

if re.search(r'process\.env\.\w+', content):
    print("[ENV] Direct process.env access detected.", file=sys.stderr)
    print("[ENV] Use src/config/env.ts instead.", file=sys.stderr)
    sys.exit(2)  # Block

sys.exit(0)
Enter fullscreen mode Exit fullscreen mode

Summary

Design environment variable management with Claude Code:

  1. CLAUDE.md — Single-source access through env module, validation required
  2. Zod schema — Type-safe definitions with coercion and validation
  3. Startup validation — Show ALL missing vars at once, then crash cleanly
  4. .env.example — Keep it in sync, never commit real values
  5. Hooks — Block direct process.env access automatically

Security Pack (¥1,480) includes /security-check for secret management review — exposed secrets, missing validation, direct env access.

👉 prompt-works.jp

Myouga (@myougatheaxo) — Claude Code engineer focused on production configuration.

Top comments (0)