npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

envguard-ts

v1.0.1

Published

Zero-dependency TypeScript environment variable validator with compile-time type inference

Readme

envguard

npm version npm downloads bundle size CI coverage TypeScript license

Zero-dependency TypeScript environment variable validator with compile-time type inference.
Validates on startup, throws human-readable errors, infers types — no string widening anywhere.

import { createEnv, str, num, bool, url, port } from 'envguard-ts'

export const env = createEnv({
  schema: {
    DATABASE_URL: url(),
    PORT:         port({ default: 3000 }),
    NODE_ENV:     str({ choices: ['development', 'production', 'test'], default: 'development' }),
    DEBUG:        bool({ default: false }),
    API_KEY:      str({ secret: true }),   // masked in error output
  },
})

env.PORT         // → number  (not string!)
env.DATABASE_URL // → string
env.DEBUG        // → boolean

If any required variable is missing or fails validation, createEnv throws a structured, readable error at startup — not a cryptic runtime crash deep in your app.

  ✖ Environment validation failed

  DATABASE_URL: is required but missing
  PORT: must be between 1 and 65535 (received: "99999")
  NODE_ENV: must be one of ["development", "production", "test"] (received: "staging")

Table of contents


Why envguard?

| Feature | envguard | envalid | envsafe | t3-env | |---|:---:|:---:|:---:|:---:| | Zero dependencies | ✅ | ❌ (dotenv) | ✅ | ❌ (zod) | | TypeScript-first types | ✅ | ⚠️ | ✅ | ✅ | | Compile-time inference | ✅ | ❌ | ✅ | ✅ | | Bundle size | < 3 KB | ~15 KB | ~4 KB | ~12 KB | | Works in Edge / Deno / Bun | ✅ | ❌ | ⚠️ | ⚠️ | | CLI (npx envguard check) | ✅ | ❌ | ❌ | ❌ | | Auto .env.example gen | ✅ | ❌ | ❌ | ❌ | | Secret masking | ✅ | ❌ | ❌ | ❌ | | choices enum narrowing | ✅ | ✅ | ✅ | ✅ |


Install

npm install envguard-ts
# or
pnpm add envguard-ts
# or
yarn add envguard-ts

No peer dependencies required. Works with Node ≥ 18, Deno, Bun, and all Edge runtimes.


Quick start

1. Create your env file

// src/env.ts
import { createEnv, str, num, bool, url, port, email } from 'envguard-ts'

export const env = createEnv({
  schema: {
    DATABASE_URL:  url({ protocols: ['postgresql:', 'postgres:'] }),
    REDIS_URL:     url({ default: 'redis://localhost:6379' }),
    PORT:          port({ default: 3000 }),
    NODE_ENV:      str({ choices: ['development', 'production', 'test'], default: 'development' }),
    DEBUG:         bool({ default: false }),
    API_KEY:       str({ secret: true }),
    ADMIN_EMAIL:   email({ default: '[email protected]' }),
    MAX_POOL_SIZE: num({ default: 10, min: 1, max: 100 }),
  },
})

2. Import it everywhere

import { env } from './env'

// Fully typed, never undefined at runtime
const db = createPool(env.DATABASE_URL)
app.listen(env.PORT)

3. Validate before deploy

npx envguard check --env .env.production --schema src/env.js

API reference

createEnv(options)

The primary API. Validates environment variables and returns a frozen, typed object.

function createEnv<S extends Schema>(options: CreateEnvOptions<S>): InferEnv<S>

| Option | Type | Default | Description | |---|---|---|---| | schema | Schema | required | Map of variable names → validators | | env | Record<string, string \| undefined> | process.env | Override the env source (great for tests) | | skipValidation | boolean | false | Return without throwing (useful in test setup) | | onError | (errors: FieldError[]) => void | — | Called before throwing, for custom logging |

Throws EnvValidationError if validation fails.


Validators

All validators accept a BaseOptions bag:

interface BaseOptions<T> {
  default?:     T        // Makes the variable optional
  description?: string  // Shown in generated .env.example
  secret?:      boolean // Mask value in error output
}

str(opts?)

Validates a string. Returned type: string.

str()
str({ choices: ['a', 'b', 'c'] })           // string literal union NOT inferred — use enums() for that
str({ minLength: 1, maxLength: 255 })
str({ pattern: /^[a-z]+$/ })
str({ default: 'hello', secret: true })

| Option | Type | Description | |---|---|---| | choices | string[] | Restrict to allowed values | | minLength | number | Minimum string length (inclusive) | | maxLength | number | Maximum string length (inclusive) | | pattern | RegExp | Must match regex |

num(opts?)

Validates a number (integer or float). Returned type: number.

num()
num({ min: 0, max: 100 })
num({ default: 42 })

bool(opts?)

Validates a boolean. Accepts: true/false/1/0/yes/no/on/off (case-insensitive). Returned type: boolean.

bool()
bool({ default: false })

url(opts?)

Validates a URL using the WHATWG URL parser. Returned type: string.

url()
url({ protocols: ['https:'] })
url({ protocols: ['postgresql:', 'postgres:'] })

port(opts?)

Validates a port number (1–65535). Returned type: number.

port()
port({ default: 3000 })

email(opts?)

Validates an email address. Returned type: string.

email()
email({ default: '[email protected]' })

json<T>(opts?)

Parses a JSON-encoded string. Returned type: T (defaults to unknown).

json()
json<string[]>({ default: [] })
json<{ apiUrl: string; timeout: number }>()

enums(values, opts?)

Validates against a const-asserted string tuple. Narrows the return type to the union.

enums(['debug', 'info', 'warn', 'error'] as const)
// → ValidatorSpec<'debug' | 'info' | 'warn' | 'error'>

generateExample(schema)

Generates the text of a .env.example file from a schema. Secrets are shown as [secret].

import { generateExample } from 'envguard'
import { schema } from './env'
import fs from 'node:fs'

fs.writeFileSync('.env.example', generateExample(schema))

Or use the CLI: npx envguard generate


validateEnvFile(parsed, schema)

Validates a pre-parsed { KEY: value } map against a schema, returning errors without throwing. Used internally by the CLI.

const errors = validateEnvFile(
  { PORT: 'not-a-number' },
  { PORT: port() },
)
// errors → [{ field: 'PORT', message: '...', received: 'not-a-number' }]

EnvValidationError

class EnvValidationError extends Error {
  readonly errors: readonly FieldError[]
}

interface FieldError {
  field:    string           // Variable name
  message:  string           // Human-readable failure reason
  received: string | undefined  // Raw value (or '[secret]' / undefined)
}

CLI

# Validate a .env file against a schema
npx envguard check
npx envguard check --env .env.production --schema dist/env.js

# Generate .env.example from a schema
npx envguard generate
npx envguard generate --schema dist/env.js --out .env.example

The schema file must export the schema object as default, schema, or the module itself:

// envguard-ts.schema.js  (default location)
const { str, url, port } = require('envguard-ts')
module.exports = {
  DATABASE_URL: url(),
  PORT: port({ default: 3000 }),
  API_KEY: str({ secret: true }),
}

Exit codes: 0 = valid, 1 = invalid or error.


Examples


Comparison vs alternatives

vs envalid

envalid is battle-tested but has a dotenv peer dependency, a larger bundle (~15 KB), no TypeScript-first design, and no CLI tooling. envguard is zero-dep and infers precise types at compile time.

vs envsafe

envsafe is TS-first and has good inference but is no longer actively maintained. envguard adds the CLI, secret masking, and .env.example generation.

vs t3-env

t3-env wraps Zod which is powerful but adds ~12 KB to your bundle and requires Zod as a peer. envguard is zero-dependency and covers 95% of real-world use cases in < 3 KB. If you already use Zod heavily, t3-env might be a better fit; otherwise envguard is the lighter choice.


Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Run tests: npm test
  4. Submit a pull request

Please ensure npm run test:coverage passes at ≥ 90% coverage before opening a PR.


License

MIT © envguard contributors