Environment Configuration
Type-safe environment variable validation with Zod. Fail fast at startup and access fully typed configuration throughout your application.
The Environment Configuration block provides a centralized, type-safe way to validate your application's environment variables using Zod.
Instead of accessing process.env throughout your application, define your environment schema once, validate it during startup, and import the resulting env object anywhere in your project. If any required variable is missing or invalid, the application immediately throws a formatted error before the server starts.
Features
- Runtime validation powered by Zod
- Fully type-safe environment object
- Fail-fast startup validation
- Human-readable validation errors
- Supports default values
- Supports type coercion (numbers, booleans, etc.)
- Test-friendly by allowing custom environment sources
- Zero runtime overhead after initialization
File Structure
env-config
└── index.ts- index.ts —
parseEnv()function andEnvValidationErrorclass.
Installation
pnpm dlx blockend-cli add env-configDetect Project
Blockend detects your project configuration and determines the correct output location.
Install Dependencies
zod and dotenv are installed automatically.
Generate Files
The environment configuration block is generated inside your configured blocks directory.
Copy the file below into your project's blocks directory.
Peer Dependencies
| Package | Required |
|---|---|
zod | Yes |
dotenv | Yes |
blocks/env-config/index.ts
parseEnv() function and EnvValidationError class.
import { z, ZodType } from "zod";
/**
* Thrown when environment variable validation fails.
*
* This error is designed to fail fast during application startup,
* preventing the application from running with an invalid configuration.
*/
export class EnvValidationError extends Error {
public readonly issues: readonly z.core.$ZodIssue[];
constructor(issues: readonly z.core.$ZodIssue[]) {
const formattedIssues = issues
.map(({ path, message }) => ` • ${path.length ? path.join(".") : "(root)"} → ${message}`)
.join("\n");
super(
[
"",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"❌ Environment Configuration Validation Failed",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"",
formattedIssues,
"",
"Please verify your environment variables before restarting.",
""
].join("\n")
);
this.name = "EnvValidationError";
this.issues = issues;
Object.setPrototypeOf(this, new.target.prototype);
Object.freeze(this);
}
}
/**
* Validates and parses environment variables using a Zod schema.
*
* The application should invoke this function exactly once during startup.
* If validation fails, an {@link EnvValidationError} is thrown immediately,
* ensuring the application never starts with an invalid configuration.
*
* @template T
* The inferred type of the validated environment object.
*
* @param schema
* Zod schema describing the expected environment variables.
*
* @param source
* Environment source to validate.
* Defaults to `process.env`.
*
* @returns
* Fully validated and strongly typed environment configuration.
*
* @throws {EnvValidationError}
* When one or more environment variables are missing or invalid.
*/
export function parseEnv<T>(
schema: ZodType<T>,
source: NodeJS.ProcessEnv | Record<string, string | undefined> = process.env
): T {
const result = schema.safeParse(source);
if (!result.success) {
throw new EnvValidationError(result.error.issues);
}
return result.data;
}
Configuration
| Export | Description |
|---|---|
parseEnv() | Validates an environment object against a Zod schema and returns typed data |
EnvValidationError | Error thrown when validation fails, containing Zod issues |
Architecture
Application Startup
│
▼
dotenv.config()
│
▼
parseEnv(schema, process.env)
│
├── Success → Export typed env object
│ │
│ ▼
│ Application uses env.PORT, env.DATABASE_URL, etc.
│
└── Failure → EnvValidationError thrown
│
▼
Application exits with formatted error messageThe environment is validated exactly once during startup. If validation fails, the application exits immediately with a readable error message. If validation succeeds, the returned env object is fully typed and can be imported anywhere in your application.
When to Use
- You want type-safe access to environment variables instead of raw
process.env. - You want to fail fast during startup when configuration is invalid.
- You need coercion (e.g.,
PORTas a number,ENABLE_CACHEas a boolean).
When Not to Use
- You prefer a different validation library.
- You don't need runtime validation of environment variables.
- Your environment is fully static and can be validated at build time.
Usage
Define Your Schema
import { config } from "dotenv";
import { z } from "zod";
import { parseEnv } from "@/blocks/env-config";
config(); // load .env
const envSchema = z.object({
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
REDIS_URL: z.string().url().optional()
});
export const env = parseEnv(envSchema);Use Anywhere in Your App
import { env } from "@/config/env";
console.log(env.PORT); // number
console.log(env.NODE_ENV); // "development" | "production" | "test"
await app.listen(env.PORT);Testing with Custom Environment
const env = parseEnv(schema, {
PORT: "3000",
DATABASE_URL: "mongodb://localhost/test"
});
expect(env.PORT).toBe(3000);API Reference
parseEnv
function parseEnv<T>(
schema: ZodType<T>,
source: NodeJS.ProcessEnv | Record<string, string | undefined> = process.env
): T;Validates an environment object against a Zod schema. Defaults to process.env. Returns a fully typed configuration object. Throws EnvValidationError on failure.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
schema | ZodType<T> | Yes | — | Zod schema for env validation |
source | NodeJS.ProcessEnv | Record<string, string | undefined> | No | process.env | Environment source to validate |
Returns — Fully typed T inferred from the schema.
Throws — EnvValidationError when validation fails.
EnvValidationError
class EnvValidationError extends Error {
public readonly issues: readonly z.core.$ZodIssue[];
}| Property | Type | Description |
|---|---|---|
issues | readonly z.core.$ZodIssue[] | Original Zod validation issues |
message | string | Formatted human-readable message |
The error message is formatted as:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
❌ Environment Configuration Validation Failed
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• DATABASE_URL → Required
• JWT_SECRET → String must contain at least 32 character(s)
Please verify your environment variables before restarting.Examples
Required Secrets
Enforce minimum lengths for sensitive values:
const envSchema = z.object({
JWT_SECRET: z.string().min(32, "JWT_SECRET must be at least 32 characters"),
ENCRYPTION_KEY: z.string().length(64, "ENCRYPTION_KEY must be exactly 64 characters"),
DATABASE_URL: z.string().url(),
PORT: z.coerce.number().default(3000)
});
export const env = parseEnv(envSchema);FAQ
What happens when validation fails?
EnvValidationError is thrown with a formatted message listing every invalid or missing variable. The application should let this error bubble up during startup, preventing the server from running with invalid configuration.
Can I use this without dotenv?
Yes. parseEnv() accepts any object as the source. Load your environment however you like and pass it as the second argument.
Does this work with Cloudflare Workers, Bun, or Deno?
The function itself is framework-agnostic. You may need to adapt the source parameter for non-Node.js environments where process.env is not available.
Is the EnvValidationError object frozen?
Yes. Object.freeze(this) is called in the constructor to prevent mutation of the error's issues.