npm package: @steima/env-typecheck
Description: Type-safe environment variable validation with Zod. Zero boilerplate, smart coercion, beautiful error messages.
Author: Matthias Steinbauer
Latest version: 0.1.0

# env-typecheck

**Type-safe environment variable validation with Zod.**

Zero boilerplate. Smart coercion. Beautiful error messages.

[![npm version](https://img.shields.io/npm/v/env-typecheck)](https://www.npmjs.com/package/env-typecheck)
[![license](https://img.shields.io/npm/l/env-typecheck)](https://github.com/steima/env-typecheck/blob/main/LICENSE)

---

## The Problem

```typescript
// Every Node.js app ever:
const port = parseInt(process.env.PORT!); // NaN at runtime?
const debug = process.env.DEBUG; // "false" is truthy!
const dbUrl = process.env.DATABASE_URL; // undefined in production?
```

`process.env` is `Record<string, string | undefined>`. No types, no validation, no coercion. Your app crashes at runtime instead of at startup.

## The Solution

```typescript
import { defineEnv } from 'env-typecheck';
import { z } from 'zod';

export const env = defineEnv({
  DATABASE_URL: z.string().url(),
  PORT: z.number().default(3000),
  DEBUG: z.boolean().default(false),   // "false" → false (not true!)
  NODE_ENV: z.enum(['development', 'staging', 'production']),
  API_KEY: z.string().min(1),
  RETRY_DELAY: z.number().optional(),
});

env.PORT;         // number (fully typed)
env.DATABASE_URL; // string
env.DEBUG;        // boolean
```

One function. One import. Full type safety.

## Error Messages

When validation fails, you get this:

```
╔══════════════════════════════════════════════════╗
║  ❌ Environment validation failed (3 errors)     ║
╠══════════════════════════════════════════════════╣
║                                                  ║
║  API_KEY        ✗ Required                       ║
║                   Variable is not set            ║
║                                                  ║
║  DATABASE_URL   ✗ Invalid url                    ║
║                   Received: "not-a-url"          ║
║                                                  ║
║  PORT           ✓ 3000 (default)                 ║
║  DEBUG          ✓ false (default)                ║
║  NODE_ENV       ✓ "production"                   ║
║  RETRY_DELAY    ✓ (not set, optional)            ║
║                                                  ║
║  💡 Missing variables for .env:                  ║
║  API_KEY=                                        ║
║  DATABASE_URL=                                   ║
╚══════════════════════════════════════════════════╝
```

Every variable. At a glance. Copy-paste ready.

## Installation

```bash
npm install env-typecheck zod
```

## Why not X?

| Feature | env-typecheck | t3-env | envalid | dotenv |
|---|---|---|---|---|
| Zod schemas | ✓ | ✓ | ✗ | ✗ |
| Smart `"false"` → `false` | ✓ | ✗ | ✗ | ✗ |
| Zero boilerplate | ✓ | ✗ (runtimeEnv) | ✓ | ✓ |
| Framework agnostic | ✓ | ✗ (Next.js) | ✓ | ✓ |
| Beautiful errors | ✓ | ✗ | ✗ | ✗ |
| Type inference | ✓ | ✓ | partial | ✗ |
| `.env.example` CLI | ✓ | ✗ | ✗ | ✗ |
| Validation | ✓ | ✓ | ✓ | ✗ |

## Features

### Smart String Coercion

Environment variables are always strings. `env-typecheck` automatically coerces them based on your Zod schema:

```typescript
// Boolean — the pain point everyone has
"true"  → true       "false" → false
"1"     → true       "0"     → false
"TRUE"  → true       "FALSE" → false
""      → undefined  (so defaults kick in)

// Number
"3000"  → 3000       "3.14"  → 3.14
""      → undefined  "abc"   → Zod error

// String
unchanged, "" → undefined
```

No more `z.coerce.boolean()` turning `"false"` into `true`.

### Type Inference

Full TypeScript inference out of the box:

```typescript
const env = defineEnv({
  PORT: z.number().default(3000),
  API_KEY: z.string(),
  DEBUG: z.boolean().optional(),
});

// typeof env = {
//   readonly PORT: number;
//   readonly API_KEY: string;
//   readonly DEBUG: boolean | undefined;
// }
```

### CLI: `.env.example` Generator

Generate a `.env.example` file from your schema:

```bash
npx env-typecheck generate --input ./src/env.ts --output .env.example
```

Output:

```
DATABASE_URL=        # Required, url
PORT=3000            # Optional, number (default: 3000)
DEBUG=false          # Optional, boolean (default: false)
NODE_ENV=            # Required, enum: development | staging | production
API_KEY=             # Required, string, min: 1
RETRY_DELAY=         # Optional, number
```

## API Reference

### `defineEnv(schema, options?)`

#### Parameters

- **`schema`** — An object where keys are environment variable names and values are Zod schemas.
- **`options`** (optional):
  - **`source`** — The environment object to read from. Default: `process.env`
  - **`onError`** — Error handling strategy:
    - `'throw'` (default) — Throws an error with the formatted message
    - `'exit'` — Prints the error and calls `process.exit(1)`
    - `(errors) => void` — Custom error handler

#### Returns

A frozen, fully-typed object with validated environment values.

```typescript
export const env = defineEnv({
  DATABASE_URL: z.string().url(),
  PORT: z.number().default(3000),
}, {
  source: process.env,
  onError: 'throw',
});
```

## CLI Reference

### `env-typecheck generate`

```bash
env-typecheck generate --input <file> [--output <file>]
```

| Option | Description | Default |
|---|---|---|
| `--input`, `-i` | Path to your env schema file | (required) |
| `--output`, `-o` | Output file path | `.env.example` |

## Contributing

Contributions are welcome! Please open an issue or submit a pull request.

## License

[MIT](LICENSE) — Matthias Steinbauer