Skip to content

Latest commit

 

History

History
134 lines (114 loc) · 4.27 KB

File metadata and controls

134 lines (114 loc) · 4.27 KB
id env-variables-schema-validation
title Environment Variables with Schema Validation
category environment-config
skillLevel beginner
tags
environment
config
schema
validation
dotenv
lessonOrder 20
rule
description
Environment Variables with Schema Validation.
summary Environment variables power your application—database URLs, API keys, ports. But they're just strings. You load them with `process.env.DATABASE_URL`, hoping it exists and is valid. No type safety, no...

Problem

Environment variables power your application—database URLs, API keys, ports. But they're just strings. You load them with process.env.DATABASE_URL, hoping it exists and is valid. No type safety, no compile-time checks. One missing variable crashes production. You need a single source of truth for environment configuration with validation and type safety.

Solution

import { Schema, Effect } from "effect"

// 1. Define environment schema
const EnvironmentSchema = Schema.Struct({
	DATABASE_URL: Schema.String.pipe(
		Schema.annotations({ description: 'PostgreSQL connection string' }),
	),
	API_KEY: Schema.String.pipe(
		Schema.minLength(32),
		Schema.annotations({
			description: 'API authentication key (min 32 chars)',
		}),
	),
	PORT: Schema.String.pipe(
		Schema.parseNumber,
		Schema.int(),
		Schema.between(1024, 65535),
		Schema.annotations({ description: 'Server port (1024-65535)' }),
	),
	LOG_LEVEL: Schema.Literal('debug', 'info', 'warn', 'error').pipe(
		Schema.annotations({ description: 'Logging level' }),
	),
	NODE_ENV: Schema.Literal('development', 'staging', 'production').pipe(
		Schema.annotations({ description: 'Deployment environment' }),
	),
})

type Environment = typeof EnvironmentSchema.Type

// 2. Create validator
const validateEnv = Schema.decodeUnknown(EnvironmentSchema)

// 3. Load and validate environment
const loadEnvironment = Effect.fn(function* () {
	const validated = yield* validateEnv(process.env)

	console.log(`✅ Environment loaded: NODE_ENV=${validated.NODE_ENV}`)
	return validated
})

// 4. Create service to provide environment
export class EnvironmentService extends Context.Tag('@app/EnvironmentService')<
	EnvironmentService,
	Environment & {
		isDev: () => boolean
		isStaging: () => boolean
		isProd: () => boolean
	}
>() {
	static layer = Layer.effect(
		this,
		Effect.gen(function* () {
			const env = yield* loadEnvironment()
			return {
				...env,
				isDev: () => env.NODE_ENV === 'development',
				isStaging: () => env.NODE_ENV === 'staging',
				isProd: () => env.NODE_ENV === 'production',
			}
		}),
	)
}

// Usage
const program = Effect.gen(function* () {
	const envService = yield* EnvironmentService

	console.log(`Database: ${envService.DATABASE_URL}`)
	console.log(`Port: ${envService.PORT}`)
	console.log(`Log level: ${envService.LOG_LEVEL}`)
	console.log(`Is production: ${envService.isProd()}`)

	return envService.PORT
})

// Run with environment layer
Effect.runPromise(program.pipe(Effect.provide(EnvironmentService.layer)))
	.then((port) => console.log(`Server starting on port ${port}`))
	.catch((error) => console.error(`Failed to start: ${error.message}`))

Why This Works

Concept Explanation
Schema definition Single source of truth for all env vars
Type safety TypeScript knows exact env var types at compile time
Validation rules Enforce constraints (min length, numeric ranges, allowed values)
Fail early Validation errors happen on startup, not runtime
Service pattern Environment accessible throughout app via dependency injection
Immutable config Environment locked after validation, prevents accidental changes
Helper methods isDev(), isProd() encapsulate environment checks

When to Use

  • Application startup with required configuration
  • Multi-environment deployments (dev, staging, production)
  • Third-party API integrations requiring keys
  • Database connection strings with validation
  • Server configuration (port, timeouts, limits)
  • Any scenario where invalid env vars should crash fast

Related Patterns