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

@cjkihl/env

v0.0.7

Published

Type-safe environment variable validation and management with Zod schemas

Downloads

135

Readme

@cjkihl/env

Type-safe environment variable validation and management with Zod schemas. Create validated, type-safe environment configurations for both browser and server environments.

Features

  • 🔒 Type-safe: Full TypeScript support with automatic type inference
  • 🎯 Zod validation: Built-in support for Zod schemas with comprehensive validation
  • 🌐 Universal: Works in both browser and server environments
  • 📝 Auto-generated env files: Generate .env files with documentation and defaults
  • 🔧 Flexible: Support for optional variables, defaults, and custom validation
  • 📦 Lightweight: Minimal dependencies with zero runtime overhead

Installation

bun add @cjkihl/env

Quick Start

Basic Usage

import { createEnv } from '@cjkihl/env';
import { z } from 'zod';

const env = createEnv({
  name: 'app',
  description: 'Application environment variables',
  env: {
    API_KEY: [z.string().min(1), process.env.API_KEY],
    PORT: [z.string().regex(/^\d+$/).transform(Number), process.env.PORT],
    DEBUG: [z.string().optional(), process.env.DEBUG],
    DATABASE_URL: [z.string().url(), process.env.DATABASE_URL],
  },
});

// TypeScript knows the exact types
console.log(env.API_KEY); // string
console.log(env.PORT); // number
console.log(env.DEBUG); // string | undefined
console.log(env.DATABASE_URL); // string

Browser Environment Support

The library automatically handles browser vs server environments:

import { createEnv } from '@cjkihl/env';
import { z } from 'zod';

const env = createEnv({
  name: 'client',
  description: 'Client-side environment variables',
  env: {
    // These will be available in browser
    VITE_API_URL: [z.string().url(), process.env.VITE_API_URL],
    NEXT_PUBLIC_APP_NAME: [z.string(), process.env.NEXT_PUBLIC_APP_NAME],
    
    // These will be skipped in browser (server-only)
    DATABASE_URL: [z.string().url(), process.env.DATABASE_URL],
    SECRET_KEY: [z.string().min(32), process.env.SECRET_KEY],
  },
});

Advanced Usage

Multiple Environment Groups

import { createEnv, mergeEnvs } from '@cjkihl/env';
import { z } from 'zod';

// Server environment
const serverEnv = createEnv({
  name: 'server',
  description: 'Server-side environment variables',
  env: {
    DATABASE_URL: [z.string().url(), process.env.DATABASE_URL],
    JWT_SECRET: [z.string().min(32), process.env.JWT_SECRET],
    PORT: [z.string().regex(/^\d+$/).transform(Number), process.env.PORT],
  },
});

// Client environment
const clientEnv = createEnv({
  name: 'client',
  description: 'Client-side environment variables',
  env: {
    VITE_API_URL: [z.string().url(), process.env.VITE_API_URL],
    VITE_APP_NAME: [z.string(), process.env.VITE_APP_NAME],
  },
});

// Merge environments
const env = mergeEnvs(serverEnv, clientEnv);

Optional Variables and Defaults

import { createEnv } from '@cjkihl/env';
import { z } from 'zod';

const env = createEnv({
  name: 'app',
  description: 'Application configuration',
  env: {
    // Required variable
    API_KEY: [z.string().min(1), process.env.API_KEY],
    
    // Optional variable
    DEBUG: [z.string().optional(), process.env.DEBUG],
    
    // Variable with default value
    PORT: [z.string().regex(/^\d+$/).transform(Number).default('3000'), process.env.PORT],
    
    // Variable with description
    DATABASE_URL: [
      z.string()
        .url()
        .describe('PostgreSQL database connection string'),
      process.env.DATABASE_URL
    ],
  },
});

Skip Validation

import { createEnv } from '@cjkihl/env';
import { z } from 'zod';

// Skip validation entirely (useful for development)
const env = createEnv({
  name: 'app',
  description: 'Development environment',
  skipValidation: true,
  env: {
    API_KEY: [z.string(), process.env.API_KEY],
    PORT: [z.string(), process.env.PORT],
  },
});

// Or use environment variable
// SKIP_ENV_VALIDATION=true bun run dev

Environment File Generation

Generate .env files with documentation and proper formatting:

import { createEnv, mergeEnvs } from '@cjkihl/env';
import createEnvFile from '@cjkihl/env/create-env-file';
import { z } from 'zod';

const serverEnv = createEnv({
  name: 'server',
  description: 'Server environment variables',
  env: {
    DATABASE_URL: [
      z.string().url().describe('PostgreSQL connection string'),
      process.env.DATABASE_URL
    ],
    JWT_SECRET: [
      z.string().min(32).describe('JWT signing secret'),
      process.env.JWT_SECRET
    ],
    PORT: [
      z.string().regex(/^\d+$/).transform(Number).default('3000'),
      process.env.PORT
    ],
  },
});

const clientEnv = createEnv({
  name: 'client',
  description: 'Client environment variables',
  env: {
    VITE_API_URL: [
      z.string().url().describe('API base URL'),
      process.env.VITE_API_URL
    ],
    VITE_APP_NAME: [
      z.string().describe('Application name'),
      process.env.VITE_APP_NAME
    ],
  },
});

const mergedEnv = mergeEnvs(serverEnv, clientEnv);

// Generate .env file
const envFileContent = createEnvFile('my-app', mergedEnv);
console.log(envFileContent);

This generates a well-formatted .env file:

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Env file for my-app
#                                                                                               
# Don't forget to add to .gitignore.    
#                                                            
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║                                    client                                    ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
# API base URL
VITE_API_URL=""

# Application name
VITE_APP_NAME=""

# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║                                    server                                    ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
# PostgreSQL connection string
DATABASE_URL=""

# JWT signing secret
JWT_SECRET=""

# (Default: 3000)
PORT="3000"

With Existing Values

// Preserve existing values when generating .env file
const existingValues = {
  DATABASE_URL: 'postgresql://localhost:5432/mydb',
  VITE_APP_NAME: 'My Awesome App',
};

const envFileContent = createEnvFile('my-app', mergedEnv, existingValues);

API Reference

createEnv<T>(config: EnvConfig): Environment<T>

Creates a type-safe environment configuration object.

Parameters

  • config.name: String identifier for the environment group
  • config.description: Optional description for the environment group
  • config.env: Object mapping environment variable names to [schema, value] tuples
  • config.skipValidation: Optional boolean to skip validation

Returns

A validated environment object with metadata and type-safe access to variables.

mergeEnvs<TArr>(...objects: TArr): MergeArrayOfObjects<TArr>

Merges multiple environment objects into a single object.

Parameters

  • objects: Environment objects to merge

Returns

A single merged environment object with combined metadata.

createEnvFile(projectName: string, env: Environment, existingValues?: Record<string, string>): string

Generates a formatted .env file from an environment object.

Parameters

  • projectName: Name of the project (used in file header)
  • env: Environment object with metadata
  • existingValues: Optional existing values to preserve

Returns

Formatted .env file content as a string.

Types

EnvConfig

type EnvConfig = {
  name: string;
  description?: string;
  env: {
    [key: string]: [validator: z.ZodType<unknown, unknown>, value?: string];
  };
  skipValidation?: boolean;
};

Environment<T>

type Environment<T extends EnvConfig> = {
  [K in ExtractEnvKeys<T>]: ExtractEnvValue<T, K>;
} & {
  _meta?: MetaData;
};

MetaData

type MetaData = {
  [group: string]: {
    d?: string; // Description
    v: {
      [key: string]: {
        c?: string; // Comment/description
        def?: string; // Default value
        o?: boolean; // Optional
      };
    };
  };
};

Examples

Next.js Application

// lib/env.ts
import { createEnv } from '@cjkihl/env';
import { z } from 'zod';

export const env = createEnv({
  name: 'app',
  description: 'Next.js application environment',
  env: {
    // Server-only variables
    DATABASE_URL: [z.string().url(), process.env.DATABASE_URL],
    JWT_SECRET: [z.string().min(32), process.env.JWT_SECRET],
    
    // Client variables (prefixed with NEXT_PUBLIC_)
    NEXT_PUBLIC_API_URL: [z.string().url(), process.env.NEXT_PUBLIC_API_URL],
    NEXT_PUBLIC_APP_NAME: [z.string(), process.env.NEXT_PUBLIC_APP_NAME],
    
    // Optional variables
    DEBUG: [z.string().optional(), process.env.DEBUG],
    PORT: [z.string().regex(/^\d+$/).transform(Number).default('3000'), process.env.PORT],
  },
});

Vite Application

// src/env.ts
import { createEnv } from '@cjkihl/env';
import { z } from 'zod';

export const env = createEnv({
  name: 'vite-app',
  description: 'Vite application environment',
  env: {
    // Vite variables (prefixed with VITE_)
    VITE_API_URL: [z.string().url(), process.env.VITE_API_URL],
    VITE_APP_NAME: [z.string(), process.env.VITE_APP_NAME],
    VITE_DEBUG: [z.string().optional(), process.env.VITE_DEBUG],
  },
});

Express.js Server

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

export const env = createEnv({
  name: 'server',
  description: 'Express server environment',
  env: {
    NODE_ENV: [z.enum(['development', 'production', 'test']), process.env.NODE_ENV],
    PORT: [z.string().regex(/^\d+$/).transform(Number).default('3000'), process.env.PORT],
    DATABASE_URL: [z.string().url(), process.env.DATABASE_URL],
    JWT_SECRET: [z.string().min(32), process.env.JWT_SECRET],
    CORS_ORIGIN: [z.string().url().optional(), process.env.CORS_ORIGIN],
  },
});

Error Handling

The library provides detailed error messages for validation failures:

try {
  const env = createEnv({
    name: 'app',
    env: {
      API_KEY: [z.string().min(1), process.env.API_KEY],
    },
  });
} catch (error) {
  console.error(error.message);
  // "Invalid value for environment variable API_KEY: undefined String must contain at least 1 character(s)"
}

Testing

Run the test suite:

bun test

Requirements

  • Node.js >= 18.0.0 or Bun
  • TypeScript >= 5.0.0 (for type definitions)
  • Zod >= 4.0.0

License

MIT