# API Reference (/docs/api-reference) Functions [#functions] defineEnv() [#defineenv] The main function for defining and validating environment variables. ```typescript function defineEnv( schema: T, config?: EnvConfig, ): ValidatedEnv; ``` Parameters [#parameters] **schema** (required) * Type: `EnvSchema` (Record of Zod schemas) * Description: An object where keys are environment variable names and values are Zod schemas **config** (optional) * Type: `EnvConfig` * Description: Configuration options for environment validation Returns [#returns] Returns a validated environment object with TypeScript types inferred from your schema. Example [#example] ```typescript import { defineEnv, z } from "nviron"; const env = defineEnv({ PORT: z.coerce.number(), DATABASE_URL: z.string().url(), }); // env is typed as: // { // PORT: number; // DATABASE_URL: string; // } ``` Configuration Options [#configuration-options] EnvConfig [#envconfig] Configuration object for customizing environment validation behavior. ```typescript interface EnvConfig { source?: EnvData; prefix?: string; } ``` source [#source] Custom environment source object. * Type: `EnvData` (Record\) * Default: `process.env` * Description: Override the default environment source **Example:** ```typescript import { defineEnv, z } from "nviron"; // Use Vite's import.meta.env const env = defineEnv( { API_URL: z.string().url(), }, { source: import.meta.env, }, ); // Use custom object const customEnv = defineEnv( { API_KEY: z.string(), }, { source: { API_KEY: "custom-value", }, }, ); ``` prefix [#prefix] Prefix to strip from environment variable names. * Type: `string` * Default: `undefined` * Description: Automatically removes specified prefix from environment variable names before validation **Example:** ```typescript import { defineEnv, z } from "nviron"; // With Vite prefix const env = defineEnv( { API_URL: z.string().url(), APP_NAME: z.string(), }, { source: import.meta.env, prefix: "VITE_", }, ); // Looks for VITE_API_URL and VITE_APP_NAME // But you access them as env.API_URL and env.APP_NAME ``` **Prefix Normalization:** When using a prefix, nviron automatically strips it before validation. For example, with `prefix: "VITE_"`, the variable `VITE_PORT` becomes `PORT` in your schema. Types [#types] EnvSchema [#envschema] The shape of your environment schema definition. ```typescript type EnvSchema = Record>; ``` **Example:** ```typescript import { z } from "nviron"; import type { EnvSchema } from "nviron"; const mySchema: EnvSchema = { PORT: z.coerce.number(), DATABASE_URL: z.string().url(), NODE_ENV: z.enum(["development", "production"]), }; ``` ValidatedEnv [#validatedenv] The inferred type of the validated environment object. ```typescript type ValidatedEnv = { [K in keyof T]: z.infer; }; ``` **Example:** ```typescript import { defineEnv, z } from "nviron"; import type { ValidatedEnv, EnvSchema } from "nviron"; const schema = { PORT: z.coerce.number(), DATABASE_URL: z.string().url(), } satisfies EnvSchema; type Env = ValidatedEnv; // { // PORT: number; // DATABASE_URL: string; // } const env: Env = defineEnv(schema); ``` EnvData [#envdata] The type for environment source objects. ```typescript type EnvData = Record; ``` **Example:** ```typescript import type { EnvData } from "nviron"; const customSource: EnvData = { API_URL: "https://api.example.com", API_KEY: "secret-key", TIMEOUT: "5000", }; ``` EnvConfig [#envconfig-1] Configuration object interface. ```typescript interface EnvConfig { source?: EnvData; prefix?: string; } ``` Zod Re-export [#zod-re-export] Nviron re-exports Zod's `z` object for convenience. ```typescript import { z } from "nviron"; // Equivalent to: // import { z } from 'zod'; ``` Available Zod Types [#available-zod-types] All Zod types and methods are available through nviron's re-export: Primitives [#primitives] ```typescript import { z } from "nviron"; z.string(); // string z.number(); // number z.boolean(); // boolean z.bigint(); // bigint z.date(); // Date z.undefined(); // undefined z.null(); // null z.any(); // any z.unknown(); // unknown z.never(); // never z.void(); // void ``` Strings [#strings] ```typescript z.string().min(5); // Min length z.string().max(10); // Max length z.string().length(5); // Exact length z.string().email(); // Email validation z.string().url(); // URL validation z.string().uuid(); // UUID validation z.string().regex(/^[A-Z]+$/); // Regex pattern z.string().startsWith("https"); // Starts with z.string().endsWith(".com"); // Ends with z.string().includes("api"); // Contains ``` Numbers [#numbers] ```typescript z.number().min(0); // Minimum value z.number().max(100); // Maximum value z.number().int(); // Integer only z.number().positive(); // Positive numbers z.number().nonnegative(); // Non-negative z.number().negative(); // Negative numbers z.number().nonpositive(); // Non-positive z.number().multipleOf(5); // Multiple of value z.number().finite(); // Finite numbers z.number().safe(); // Safe integers ``` Coercion [#coercion] ```typescript z.coerce.string(); // Convert to string z.coerce.number(); // Convert to number z.coerce.boolean(); // Convert to boolean z.coerce.date(); // Convert to Date z.coerce.bigint(); // Convert to bigint ``` Enums [#enums] ```typescript z.enum(["development", "production", "test"]); z.nativeEnum(MyEnum); ``` Optionals & Defaults [#optionals--defaults] ```typescript z.string().optional(); // string | undefined z.string().nullable(); // string | null z.string().nullish(); // string | null | undefined z.string().default("default"); // string (with default) z.string().optional().default(""); // string (optional with default) ``` Objects [#objects] ```typescript z.object({ name: z.string(), age: z.number(), }); ``` Arrays [#arrays] ```typescript z.array(z.string()); // string[] z.array(z.number()).min(1); // Non-empty array z.array(z.string()).max(10); // Max 10 items ``` Unions [#unions] ```typescript z.union([z.string(), z.number()]); // string | number z.string().or(z.number()); // string | number ``` Transforms [#transforms] ```typescript z.string().transform((val) => val.toUpperCase()); z.string().transform((val) => val.split(",")); ``` Refinements [#refinements] ```typescript z.string().refine((val) => val.length > 5, { message: "String must be longer than 5 characters", }); z.number().refine((val) => val % 2 === 0, { message: "Number must be even", }); ``` Error Handling [#error-handling] When validation fails, nviron formats errors in a user-friendly way and exits the process. Error Output Format [#error-output-format] ```bash ❌ Environment Variable Missing or Invalid 3 issues found in your environment configuration. 1. PORT β†’ Expected number, received string 2. DATABASE_URL β†’ Invalid url 3. API_KEY β†’ String must contain at least 32 character(s) πŸ’‘ Check your .env file or environment variables before starting the server. ``` Validation Errors [#validation-errors] Validation errors occur when: 1. **Missing Required Variables**: A required environment variable is not defined 2. **Type Mismatch**: The value doesn't match the expected type 3. **Validation Failure**: Custom validation rules fail (min/max, regex, etc.) **Example:** ```typescript import { defineEnv, z } from "nviron"; // This will fail if PORT is not a valid number const env = defineEnv({ PORT: z.coerce.number(), DATABASE_URL: z.string().url(), }); ``` Exit Behavior [#exit-behavior] By default, nviron exits the process when validation fails: * Exit code: `1` * Prevents application from running with invalid configuration * Displays colored error messages in the console **Production Behavior:** In production environments, ensure all environment variables are properly configured to avoid startup failures. Complete Example [#complete-example] Here's a comprehensive example using all available options: ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; import type { EnvSchema, ValidatedEnv } from "nviron"; // Define schema with type const envSchema = { // Node environment NODE_ENV: z.enum(["development", "production", "test"]), // Server configuration PORT: z.coerce.number().int().positive().default(3000), HOST: z.string().default("localhost"), // Database DATABASE_URL: z.string().url().startsWith("postgresql://"), DATABASE_POOL_SIZE: z.coerce.number().int().min(1).max(100).default(10), // API Keys API_KEY: z.string().min(32), API_SECRET: z.string().min(32), // External Services REDIS_URL: z.string().url().optional(), SMTP_HOST: z.string().optional(), SMTP_PORT: z.coerce.number().optional(), // Feature Flags ENABLE_ANALYTICS: z.coerce.boolean().default(false), ENABLE_CACHE: z.coerce.boolean().default(true), ENABLE_LOGGING: z.coerce.boolean().default(true), // Security JWT_SECRET: z.string().min(32), SESSION_SECRET: z.string().min(32), CORS_ORIGIN: z.string().url(), // Custom validation ALLOWED_HOSTS: z .string() .transform((val) => val.split(",")) .refine((hosts) => hosts.length > 0, { message: "At least one allowed host is required", }), // Optional with transform LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), } satisfies EnvSchema; // Export type export type Env = ValidatedEnv; // Export validated env export const env = defineEnv(envSchema); ``` Advanced Usage [#advanced-usage] Custom Source Object [#custom-source-object] ```typescript import { defineEnv, z } from "nviron"; const config = { API_URL: "https://api.example.com", API_KEY: "secret-key", TIMEOUT: "5000", }; const env = defineEnv( { API_URL: z.string().url(), API_KEY: z.string(), TIMEOUT: z.coerce.number(), }, { source: config }, ); ``` Multiple Environment Configs [#multiple-environment-configs] ```typescript import { defineEnv, z } from "nviron"; // Server environment export const serverEnv = defineEnv({ DATABASE_URL: z.string().url(), REDIS_URL: z.string().url(), JWT_SECRET: z.string().min(32), }); // Client environment (Next.js example) export const clientEnv = defineEnv({ NEXT_PUBLIC_API_URL: z.string().url(), NEXT_PUBLIC_SITE_NAME: z.string(), }); ``` Type-Safe Environment Variables [#type-safe-environment-variables] ```typescript import { defineEnv, z } from "nviron"; import type { ValidatedEnv } from "nviron"; const schema = { PORT: z.coerce.number(), DATABASE_URL: z.string().url(), } as const; type Env = ValidatedEnv; const env: Env = defineEnv(schema); // TypeScript knows the exact types env.PORT; // number env.DATABASE_URL; // string ``` # Best Practices (/docs/best-practices) Project Structure [#project-structure] Centralized Configuration [#centralized-configuration] Keep all environment configuration in a single, well-organized file. ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; // βœ… Good: Centralized, organized schema export const env = defineEnv({ // Application NODE_ENV: z.enum(["development", "production", "test"]), PORT: z.coerce.number().default(3000), // Database DATABASE_URL: z.string().url(), DATABASE_POOL_SIZE: z.coerce.number().default(10), // Security JWT_SECRET: z.string().min(32), SESSION_SECRET: z.string().min(32), }); ``` ```typescript title="src/server.ts" // ❌ Bad: Scattered validation const port = parseInt(process.env.PORT || "3000"); const dbUrl = process.env.DATABASE_URL; if (!dbUrl) throw new Error("DATABASE_URL required"); ``` Early Validation [#early-validation] Import and validate environment variables as early as possible to catch configuration errors at startup. ```typescript title="src/index.ts" // βœ… Good: Validate first import { env } from "./env"; // Validates immediately import express from "express"; import { connectDatabase } from "./database"; const app = express(); // Now safely use validated env app.listen(env.PORT); ``` ```typescript title="src/index.ts" // ❌ Bad: Late validation import express from "express"; const app = express(); // Error might only occur when this code runs app.listen(process.env.PORT); // What if PORT is invalid? ``` Schema Design [#schema-design] Use Specific Validation [#use-specific-validation] Be as specific as possible with your validation rules to catch errors early. ```typescript import { defineEnv, z } from "nviron"; // βœ… Good: Specific validation export const env = defineEnv({ PORT: z.coerce.number().int().positive().max(65535), DATABASE_URL: z.string().url().startsWith("postgresql://"), EMAIL: z.string().email(), API_KEY: z.string().min(32).max(256), }); // ❌ Bad: Generic validation export const env = defineEnv({ PORT: z.coerce.number(), DATABASE_URL: z.string(), EMAIL: z.string(), API_KEY: z.string(), }); ``` Provide Sensible Defaults [#provide-sensible-defaults] Use defaults for non-critical values to improve developer experience. ```typescript import { defineEnv, z } from "nviron"; // βœ… Good: Defaults for common values export const env = defineEnv({ PORT: z.coerce.number().default(3000), HOST: z.string().default("localhost"), LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), ENABLE_CACHE: z.coerce.boolean().default(true), // Still require critical values DATABASE_URL: z.string().url(), JWT_SECRET: z.string().min(32), }); // ❌ Bad: No defaults even for common values export const env = defineEnv({ PORT: z.coerce.number(), // Requires PORT in .env HOST: z.string(), // Requires HOST in .env LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]), }); ``` Document Your Schema [#document-your-schema] Add comments to explain the purpose and requirements of each variable. ```typescript import { defineEnv, z } from "nviron"; export const env = defineEnv({ /** * PostgreSQL database connection string * Format: postgresql://user:password@host:port/database * Example: postgresql://admin:secret@localhost:5432/myapp */ DATABASE_URL: z.string().url().startsWith("postgresql://"), /** * JWT secret for signing authentication tokens * Must be at least 32 characters * Generate with: openssl rand -base64 32 */ JWT_SECRET: z.string().min(32), /** * Server port number * Valid range: 1-65535 * Default: 3000 */ PORT: z.coerce.number().int().positive().max(65535).default(3000), }); ``` Environment Files [#environment-files] Use .env.example [#use-envexample] Always provide a `.env.example` file with documentation. ```bash title=".env.example" # Application Configuration NODE_ENV=development PORT=3000 # Database # PostgreSQL connection string # Format: postgresql://user:password@host:port/database DATABASE_URL=postgresql://user:password@localhost:5432/myapp DATABASE_POOL_SIZE=10 # Security # Generate with: openssl rand -base64 32 JWT_SECRET=your-secret-key-here-min-32-characters SESSION_SECRET=your-session-secret-here-min-32-chars # External Services # Optional in development REDIS_URL=redis://localhost:6379 SMTP_HOST=smtp.gmail.com SMTP_PORT=587 # Feature Flags ENABLE_ANALYTICS=false ENABLE_DEBUG_MODE=true ``` Never Commit .env Files [#never-commit-env-files] Always add `.env` files to `.gitignore`. ```bash title=".gitignore" # Environment variables .env .env.local .env.*.local # Keep example files !.env.example ``` **Security Warning:** Never commit actual `.env` files with real secrets to version control. Environment-Specific Files [#environment-specific-files] Use different `.env` files for different environments. ``` project/ β”œβ”€β”€ .env.example # Template with documentation β”œβ”€β”€ .env.development # Development settings β”œβ”€β”€ .env.production # Production settings (not in git) β”œβ”€β”€ .env.test # Test settings └── .env.local # Local overrides (not in git) ``` Type Safety [#type-safety] Export Types [#export-types] Export and reuse environment types throughout your application. ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; import type { ValidatedEnv } from "nviron"; const schema = { DATABASE_URL: z.string().url(), PORT: z.coerce.number(), NODE_ENV: z.enum(["development", "production", "test"]), } as const; export type Env = ValidatedEnv; export const env = defineEnv(schema); ``` ```typescript title="src/database.ts" import type { Env } from "./env"; export function createDatabaseClient(env: Env) { // env.DATABASE_URL is properly typed as string // env.PORT is properly typed as number return connectToDatabase(env.DATABASE_URL); } ``` Avoid Type Assertions [#avoid-type-assertions] Let TypeScript infer types from your schema. ```typescript import { defineEnv, z } from "nviron"; const env = defineEnv({ PORT: z.coerce.number(), DATABASE_URL: z.string().url(), }); // βœ… Good: Use inferred types const port: number = env.PORT; const dbUrl: string = env.DATABASE_URL; // ❌ Bad: Type assertions defeat the purpose const port = env.PORT as number; const dbUrl = env.DATABASE_URL as string; ``` Security [#security] Validate Secrets [#validate-secrets] Ensure secrets meet minimum security requirements. ```typescript import { defineEnv, z } from "nviron"; export const env = defineEnv({ // Minimum length requirements JWT_SECRET: z.string().min(32), SESSION_SECRET: z.string().min(32), ENCRYPTION_KEY: z.string().length(64), // Format validation API_KEY: z.string().regex(/^[A-Za-z0-9_-]{32,}$/), // Provider-specific validation STRIPE_SECRET_KEY: z.string().startsWith("sk_"), SENDGRID_API_KEY: z.string().startsWith("SG."), // Prevent common mistakes DATABASE_URL: z .string() .url() .refine((url) => !url.includes("password=password"), { message: 'Do not use "password" as your database password', }), }); ``` Environment-Specific Requirements [#environment-specific-requirements] Enforce stricter rules in production. ```typescript import { defineEnv, z } from "nviron"; const isProduction = process.env.NODE_ENV === "production"; export const env = defineEnv({ NODE_ENV: z.enum(["development", "production", "test"]), // Optional in development, required in production SENTRY_DSN: isProduction ? z.string().url() : z.string().url().optional(), // Strict validation in production DATABASE_URL: z .string() .url() .refine( (url) => { if (isProduction && url.includes("localhost")) { return false; } return true; }, { message: "Cannot use localhost database in production", }, ), }); ``` Sanitize Error Messages [#sanitize-error-messages] Be careful about exposing secrets in error messages. ```typescript import { defineEnv, z } from "nviron"; // βœ… Good: Validation doesn't expose values export const env = defineEnv({ API_KEY: z.string().min(32, "API key must be at least 32 characters"), }); // The error message will be: // "API_KEY β†’ API key must be at least 32 characters" // Not: "API_KEY β†’ sk_live_51abc123... must be at least 32 characters" ``` Performance [#performance] Singleton Pattern [#singleton-pattern] Create a single instance of your environment configuration. ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; // βœ… Good: Single instance export const env = defineEnv({ DATABASE_URL: z.string().url(), PORT: z.coerce.number(), }); ``` ```typescript title="src/database.ts" // βœ… Good: Import the singleton import { env } from "./env"; export function connect() { return createClient(env.DATABASE_URL); } ``` ```typescript title="src/server.ts" // ❌ Bad: Creating multiple instances import { defineEnv, z } from "nviron"; export const env = defineEnv({ DATABASE_URL: z.string().url(), PORT: z.coerce.number(), }); ``` Validate Once at Startup [#validate-once-at-startup] Don't re-validate environment variables during runtime. ```typescript // βœ… Good: Validate once at import import { env } from "./env"; export function startServer() { // Use pre-validated env server.listen(env.PORT); } ``` ```typescript // ❌ Bad: Re-validating on every call export function getPort() { return defineEnv({ PORT: z.coerce.number() }).PORT; } ``` Testing [#testing] Mock Environment for Tests [#mock-environment-for-tests] Use custom sources for testing without modifying actual environment. ```typescript title="src/env.test.ts" import { defineEnv, z } from "nviron"; // Test-specific configuration export const testEnv = defineEnv( { DATABASE_URL: z.string().url(), PORT: z.coerce.number(), }, { source: { DATABASE_URL: "postgresql://localhost:5432/test_db", PORT: "0", // Random port for tests }, }, ); ``` Environment File for Tests [#environment-file-for-tests] Use a dedicated `.env.test` file. ```bash title=".env.test" # Test environment NODE_ENV=test DATABASE_URL=postgresql://localhost:5432/test_db PORT=0 # Fast timeouts for tests REQUEST_TIMEOUT=1000 DB_QUERY_TIMEOUT=500 # Disable external services MOCK_EXTERNAL_APIS=true DISABLE_RATE_LIMITING=true # Test-specific TEST_USER_EMAIL=test@example.com TEST_USER_PASSWORD=test-password-12345 ``` Test Environment Validation [#test-environment-validation] Write tests for your environment configuration. ```typescript title="src/env.test.ts" import { defineEnv, z } from "nviron"; import { describe, it, expect } from "vitest"; describe("Environment Configuration", () => { it("should validate correct environment", () => { expect(() => { defineEnv( { PORT: z.coerce.number(), DATABASE_URL: z.string().url(), }, { source: { PORT: "3000", DATABASE_URL: "postgresql://localhost:5432/db", }, }, ); }).not.toThrow(); }); it("should reject invalid port", () => { expect(() => { defineEnv( { PORT: z.coerce.number().positive(), }, { source: { PORT: "-1", }, }, ); }).toThrow(); }); it("should reject invalid URL", () => { expect(() => { defineEnv( { DATABASE_URL: z.string().url(), }, { source: { DATABASE_URL: "not-a-url", }, }, ); }).toThrow(); }); }); ``` Framework-Specific Best Practices [#framework-specific-best-practices] Next.js [#nextjs] Validate environment at build time. ```typescript title="next.config.mjs" // βœ… Good: Validate at build time import "./src/env.ts"; /** @type {import('next').NextConfig} */ const nextConfig = {}; export default nextConfig; ``` Separate server and client variables. ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv({ // Server-only (never sent to client) DATABASE_URL: z.string().url(), API_SECRET: z.string().min(32), // Client-exposed (NEXT_PUBLIC_ prefix) NEXT_PUBLIC_API_URL: z.string().url(), NEXT_PUBLIC_APP_NAME: z.string(), }); ``` Vite [#vite] Always use prefix for Vite projects. ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; // βœ… Good: Specify prefix export const env = defineEnv( { API_URL: z.string().url(), }, { source: import.meta.env, prefix: "VITE_", }, ); ``` Common Pitfalls [#common-pitfalls] Forgetting Coercion [#forgetting-coercion] ```typescript // ❌ Wrong: Expects actual number const env = defineEnv({ PORT: z.number(), // Will fail! process.env.PORT is "3000", not 3000 }); // βœ… Correct: Coerces string to number const env = defineEnv({ PORT: z.coerce.number(), }); ``` Overusing Optional [#overusing-optional] ```typescript // ❌ Bad: Too many optionals const env = defineEnv({ DATABASE_URL: z.string().url().optional(), JWT_SECRET: z.string().optional(), API_KEY: z.string().optional(), }); // βœ… Good: Only truly optional values const env = defineEnv({ DATABASE_URL: z.string().url(), // Required JWT_SECRET: z.string().min(32), // Required CACHE_URL: z.string().url().optional(), // Truly optional }); ``` Not Using Defaults Wisely [#not-using-defaults-wisely] ```typescript // ❌ Bad: Defaults for everything const env = defineEnv({ DATABASE_URL: z.string().url().default("postgresql://localhost:5432/db"), JWT_SECRET: z.string().default("insecure-default-secret"), }); // βœ… Good: Defaults only for safe values const env = defineEnv({ DATABASE_URL: z.string().url(), // Force explicit configuration JWT_SECRET: z.string().min(32), // Force explicit configuration PORT: z.coerce.number().default(3000), // Safe default LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), }); ``` Checklist [#checklist] Use this checklist when setting up nviron in your project: * [ ] Create centralized `env.ts` file * [ ] Validate environment early in application lifecycle * [ ] Use specific validation rules for each variable * [ ] Provide sensible defaults for non-critical values * [ ] Document schema with comments * [ ] Create `.env.example` with documentation * [ ] Add `.env` to `.gitignore` * [ ] Export types for reuse across application * [ ] Validate secrets meet minimum requirements * [ ] Use `z.coerce` for type conversion * [ ] Test environment configuration * [ ] Set up environment-specific files (dev, prod, test) * [ ] Configure framework-specific settings (Next.js, Vite, etc.) * [ ] Review and remove unused environment variables Following these best practices will help you build more robust, maintainable, and secure applications with nviron. # FAQ (/docs/faq) General Questions [#general-questions] What is nviron? [#what-is-nviron] Nviron is a lightweight, type-safe environment variable management library for JavaScript and TypeScript. It uses Zod for runtime validation and provides automatic TypeScript type inference, making environment variable handling safer and more developer-friendly. Why should I use nviron? [#why-should-i-use-nviron] Without nviron, you need to: * Manually validate environment variables * Write repetitive type conversion code * Maintain separate TypeScript type definitions * Handle missing or invalid values at runtime With nviron, you get: * βœ… Automatic runtime validation * βœ… Full TypeScript type safety * βœ… Clear, colored error messages * βœ… Centralized configuration * βœ… Zero boilerplate Is nviron production-ready? [#is-nviron-production-ready] Yes! Nviron is designed for production use. It validates environment variables at startup, ensuring your application never runs with invalid configuration. Installation & Setup [#installation--setup] Do I need to install Zod separately? [#do-i-need-to-install-zod-separately] No! Nviron re-exports Zod, so you can import `z` directly from nviron: ```typescript import { defineEnv, z } from "nviron"; ``` However, if you use Zod extensively elsewhere in your application, you may want to install it separately for consistency. Does nviron work with TypeScript? [#does-nviron-work-with-typescript] Yes! Nviron is built with TypeScript and provides full type safety. It automatically infers types from your Zod schema. Does nviron work with JavaScript? [#does-nviron-work-with-javascript] Yes! While nviron is designed for TypeScript, it works perfectly with JavaScript. You just won't get the type safety benefits. What Node.js version is required? [#what-nodejs-version-is-required] Nviron requires Node.js 16.x or higher. Usage Questions [#usage-questions] Why do I need to use z.coerce? [#why-do-i-need-to-use-zcoerce] Environment variables are always strings. Use `z.coerce` to convert them to other types: ```typescript // ❌ Wrong: Expects actual number PORT: z.number(); // βœ… Correct: Converts string to number PORT: z.coerce.number(); ``` How do I make a variable optional? [#how-do-i-make-a-variable-optional] Use `.optional()` or provide a default value: ```typescript const env = defineEnv({ // Optional, can be undefined REDIS_URL: z.string().url().optional(), // Optional with default PORT: z.coerce.number().default(3000), }); ``` How do I validate environment-specific requirements? [#how-do-i-validate-environment-specific-requirements] Use conditional validation: ```typescript const isProduction = process.env.NODE_ENV === "production"; const env = defineEnv({ // Required in production only SENTRY_DSN: isProduction ? z.string().url() : z.string().url().optional(), }); ``` Can I use environment variables from different sources? [#can-i-use-environment-variables-from-different-sources] Yes! Use the `source` option: ```typescript // Vite const env = defineEnv( { ... }, { source: import.meta.env } ); // Custom object const env = defineEnv( { ... }, { source: myCustomEnv } ); ``` How do I handle prefixes like VITE*or NEXT_PUBLIC*? [#how-do-i-handle-prefixes-like-viteor-next_public] Use the `prefix` option: ```typescript const env = defineEnv( { API_URL: z.string().url(), // Schema without prefix }, { source: import.meta.env, prefix: "VITE_", // Strips VITE_ prefix }, ); // .env file has VITE_API_URL // Access as env.API_URL ``` Validation Questions [#validation-questions] What types of validation can I use? [#what-types-of-validation-can-i-use] Nviron uses Zod, which supports extensive validation: ```typescript const env = defineEnv({ // String validation EMAIL: z.string().email(), URL: z.string().url(), MIN_LENGTH: z.string().min(5), PATTERN: z.string().regex(/^[A-Z]+$/), // Number validation PORT: z.coerce.number().int().positive().max(65535), PERCENTAGE: z.coerce.number().min(0).max(100), // Enums LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]), // Custom validation API_KEY: z.string().refine((key) => key.startsWith("sk_"), { message: "API key must start with sk_", }), }); ``` How do I validate URLs? [#how-do-i-validate-urls] ```typescript const env = defineEnv({ // Basic URL validation API_URL: z.string().url(), // URL with specific protocol DATABASE_URL: z.string().url().startsWith("postgresql://"), // HTTPS only WEBHOOK_URL: z.string().url().startsWith("https://"), }); ``` How do I validate secrets? [#how-do-i-validate-secrets] ```typescript const env = defineEnv({ // Minimum length JWT_SECRET: z.string().min(32), // Exact length ENCRYPTION_KEY: z.string().length(64), // Pattern validation API_KEY: z.string().regex(/^sk_[a-zA-Z0-9]{32}$/), // Provider-specific STRIPE_KEY: z.string().startsWith("sk_"), SENDGRID_KEY: z.string().startsWith("SG."), }); ``` How do I transform values? [#how-do-i-transform-values] Use `.transform()`: ```typescript const env = defineEnv({ // Split comma-separated list ALLOWED_ORIGINS: z.string().transform((val) => val.split(",")), // Parse JSON FEATURES: z .string() .transform((val) => JSON.parse(val)) .pipe(z.record(z.boolean())), // Convert to uppercase ENV: z.string().transform((val) => val.toUpperCase()), }); ``` Error Handling [#error-handling] What happens when validation fails? [#what-happens-when-validation-fails] Nviron displays a clear error message and exits the process: ```bash ❌ Environment Variable Missing or Invalid 2 issues found in your environment configuration. 1. API_KEY β†’ String must contain at least 32 character(s) 2. DATABASE_URL β†’ Invalid url πŸ’‘ Check your .env file or environment variables before starting the server. ``` Can I customize error messages? [#can-i-customize-error-messages] Yes, provide custom messages in your validation: ```typescript const env = defineEnv({ API_KEY: z.string().min(32, { message: "API key must be at least 32 characters. Generate one with: openssl rand -base64 32", }), DATABASE_URL: z .string() .url() .refine((url) => url.startsWith("postgresql://"), { message: "Database must be PostgreSQL. Format: postgresql://user:password@host:port/db", }), }); ``` How do I prevent the process from exiting on error? [#how-do-i-prevent-the-process-from-exiting-on-error] Currently, nviron exits on validation failure by design to prevent running with invalid configuration. This is a safety feature. If you need different behavior, you can catch the error: ```typescript try { const env = defineEnv({ ... }); } catch (error) { console.error('Environment validation failed:', error); // Handle error your way } ``` Framework-Specific Questions [#framework-specific-questions] How do I use nviron with Next.js? [#how-do-i-use-nviron-with-nextjs] ```typescript // src/env.ts import { defineEnv, z } from "nviron"; export const env = defineEnv({ // Server-only DATABASE_URL: z.string().url(), // Client-side (NEXT_PUBLIC_ prefix) NEXT_PUBLIC_API_URL: z.string().url(), }); ``` ```javascript // next.config.mjs import "./src/env.ts"; // Validate at build time export default { /* config */ }; ``` How do I use nviron with Vite? [#how-do-i-use-nviron-with-vite] ```typescript import { defineEnv, z } from "nviron"; export const env = defineEnv( { API_URL: z.string().url(), }, { source: import.meta.env, prefix: "VITE_", }, ); ``` How do I use nviron with Express? [#how-do-i-use-nviron-with-express] ```typescript // src/env.ts import { defineEnv, z } from "nviron"; export const env = defineEnv({ PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), }); ``` ```typescript // src/server.ts import { env } from "./env"; import express from "express"; const app = express(); app.listen(env.PORT); ``` Does nviron work with serverless/Lambda? [#does-nviron-work-with-serverlesslambda] Yes! Nviron works with any Node.js environment: ```typescript import { defineEnv, z } from "nviron"; export const env = defineEnv({ AWS_REGION: z.string().default("us-east-1"), DATABASE_URL: z.string().url(), API_KEY: z.string().min(32), }); ``` Performance Questions [#performance-questions] Does nviron impact startup time? [#does-nviron-impact-startup-time] Validation happens once at startup. The impact is minimal (typically \< 10ms) and ensures your app doesn't run with invalid configuration. Does nviron use a lot of memory? [#does-nviron-use-a-lot-of-memory] No. Nviron creates a single validated object at startup with minimal overhead. Should I worry about bundle size? [#should-i-worry-about-bundle-size] Nviron is lightweight. The main dependency is Zod, which you'd likely use anyway for validation. Testing Questions [#testing-questions] How do I test with nviron? [#how-do-i-test-with-nviron] Use custom sources for testing: ```typescript import { defineEnv, z } from "nviron"; const testEnv = defineEnv( { DATABASE_URL: z.string().url(), API_KEY: z.string(), }, { source: { DATABASE_URL: "postgresql://localhost:5432/test_db", API_KEY: "test-key-12345", }, }, ); ``` How do I mock environment variables in tests? [#how-do-i-mock-environment-variables-in-tests] ```typescript // In your test file import { defineEnv, z } from "nviron"; describe("My Feature", () => { it("works with custom env", () => { const env = defineEnv( { API_URL: z.string().url() }, { source: { API_URL: "https://test.api" } }, ); expect(env.API_URL).toBe("https://test.api"); }); }); ``` Should I validate environment in tests? [#should-i-validate-environment-in-tests] Yes! This ensures your test environment is properly configured: ```typescript // test/setup.ts import { defineEnv, z } from "nviron"; export const testEnv = defineEnv({ DATABASE_URL: z.string().url(), TEST_API_KEY: z.string(), }); ``` Advanced Questions [#advanced-questions] Can I validate cross-field dependencies? [#can-i-validate-cross-field-dependencies] Yes, use Zod's refinement on the entire schema: ```typescript const env = defineEnv({ MIN_POOL_SIZE: z.coerce.number(), MAX_POOL_SIZE: z.coerce.number(), }).refine((data) => data.MIN_POOL_SIZE < data.MAX_POOL_SIZE, { message: "MIN_POOL_SIZE must be less than MAX_POOL_SIZE", }); ``` Can I have different schemas for different environments? [#can-i-have-different-schemas-for-different-environments] Yes: ```typescript const isDevelopment = process.env.NODE_ENV === "development"; const devSchema = { DEBUG: z.coerce.boolean().default(true), MOCK_APIS: z.coerce.boolean().default(true), }; const prodSchema = { SENTRY_DSN: z.string().url(), CDN_URL: z.string().url(), }; const baseSchema = { NODE_ENV: z.enum(["development", "production"]), DATABASE_URL: z.string().url(), }; export const env = defineEnv({ ...baseSchema, ...(isDevelopment ? devSchema : prodSchema), }); ``` Can I extend or compose schemas? [#can-i-extend-or-compose-schemas] Yes, schemas are just objects: ```typescript const baseSchema = { NODE_ENV: z.enum(["development", "production", "test"]), PORT: z.coerce.number(), }; const databaseSchema = { DATABASE_URL: z.string().url(), DATABASE_POOL_SIZE: z.coerce.number(), }; const cacheSchema = { REDIS_URL: z.string().url(), CACHE_TTL: z.coerce.number(), }; export const env = defineEnv({ ...baseSchema, ...databaseSchema, ...cacheSchema, }); ``` How do I handle secrets rotation? [#how-do-i-handle-secrets-rotation] Nviron validates at startup, so: 1. Update your environment variables (e.g., in Kubernetes secrets, AWS Secrets Manager) 2. Restart your application 3. New values are validated on startup For zero-downtime rotation, you might want to reload environment variables without restarting: ```typescript // Note: This is advanced usage let env = defineEnv({ ... }); export function reloadEnv() { env = defineEnv({ ... }); return env; } export { env }; ``` Migration Questions [#migration-questions] Can I gradually migrate to nviron? [#can-i-gradually-migrate-to-nviron] Yes! You can use both old and new approaches side-by-side: ```typescript // New validated env export const env = defineEnv({ ... }); // Keep old process.env access for unmigrated code export const legacyEnv = process.env; ``` How do I migrate from envalid/joi/dotenv-safe? [#how-do-i-migrate-from-envalidjoidotenv-safe] See our comprehensive [Migration Guide](/docs/migration-guide) for step-by-step instructions. Will migration break my existing code? [#will-migration-break-my-existing-code] Not if you maintain the same variable names and types. Migration is about moving validation logic to a centralized location. Troubleshooting [#troubleshooting] Why am I getting "Expected string, received undefined"? [#why-am-i-getting-expected-string-received-undefined] The environment variable is not set. Check: 1. Variable exists in your `.env` file 2. Variable name matches exactly (case-sensitive) 3. `.env` file is being loaded (for Node.js, install `dotenv`) Why am I getting "Expected number, received string"? [#why-am-i-getting-expected-number-received-string] Use `z.coerce.number()` instead of `z.number()`: ```typescript // ❌ Wrong PORT: z.number(); // βœ… Correct PORT: z.coerce.number(); ``` Why isn't my .env file being loaded? [#why-isnt-my-env-file-being-loaded] For Node.js projects, ensure dotenv is configured: ```typescript import "dotenv/config"; import { env } from "./env"; ``` Or use the `-r` flag: ```bash node -r dotenv/config src/index.js ``` For framework-specific solutions, see [Troubleshooting](/docs/troubleshooting). **Can't find your question?** Open an issue on [GitHub](https://github.com/your-username/nviron/issues) or check the [Troubleshooting](/docs/troubleshooting) guide. # Introduction (/docs) What is Nviron? [#what-is-nviron] **Nviron** is a lightweight, type-safe e\[nviron]ment variable management library built for modern JavaScript and TypeScript projects. It simplifies how you define, validate, and safely access environment variables using [Zod](https://zod.dev). Instead of manually checking `process.env` or writing repetitive validation logic, nviron provides a declarative, type-safe approach powered by Zod that ensures your application runs with confidenceβ€”no missing or invalid environment variables. Key Features [#key-features] * **πŸ”’ Type-Safe** - Full TypeScript support with automatic type inference from your Zod schemas * **βœ… Runtime Validation** - Validates environment variables at startup using Zod schemas * **🎯 Zero Config** - Works out of the box with sensible defaults * **⚑ Framework Agnostic** - Compatible with Next.js, Vite, Node.js, and more * **πŸ” Clear Error Messages** - Color-coded, developer-friendly error diagnostics * **πŸ“¦ Lightweight** - Minimal dependencies, maximum functionality * **πŸ”§ Flexible** - Support for custom sources and prefix stripping Why Nviron? [#why-nviron] Managing environment variables shouldn't be complicated. Without proper validation, missing or misconfigured environment variables can cause runtime errors that are hard to debug. **Before Nviron:** ```typescript // Manual validation, no type safety const port = process.env.PORT ? parseInt(process.env.PORT) : 3000; const dbUrl = process.env.DATABASE_URL; if (!dbUrl) { throw new Error('DATABASE_URL is required'); } if (!dbUrl.startsWith('postgresql://')) { throw new Error('DATABASE_URL must be a valid PostgreSQL URL'); } ``` **With Nviron:** ```typescript import { defineEnv, z } from 'nviron'; const env = defineEnv({ PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), }); // Fully typed and validated! console.log(env.PORT); // number console.log(env.DATABASE_URL); // string ``` Nviron re-exports Zod as `z`, so you can use it directly without installing Zod separately for basic environment variable validation. How It Works [#how-it-works] Nviron validates your environment variables at application startup: 1. **Define** your environment schema using Zod 2. **Validate** - Nviron checks all variables against your schema 3. **Access** - Use fully typed, validated environment variables throughout your app If validation fails, nviron displays a clear, color-coded error message and exits gracefully, helping you catch configuration issues before they become runtime problems. Quick Example [#quick-example] ```typescript import { defineEnv, z } from 'nviron'; const env = defineEnv({ NODE_ENV: z.enum(['development', 'production', 'test']), PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), API_KEY: z.string().min(32), ENABLE_ANALYTICS: z.coerce.boolean().default(false), }); // TypeScript knows the exact types! env.NODE_ENV; // "development" | "production" | "test" env.PORT; // number env.DATABASE_URL; // string env.API_KEY; // string env.ENABLE_ANALYTICS; // boolean ``` Next Steps [#next-steps] Get started with nviron in minutes Learn the basics with practical examples Explore all available options and types # Migration Guide (/docs/migration-guid) Migrating from Manual Validation [#migrating-from-manual-validation] From process.env [#from-processenv] If you're currently using `process.env` directly with manual validation: ```typescript // Scattered validation throughout codebase const port = parseInt(process.env.PORT || '3000'); const dbUrl = process.env.DATABASE_URL; if (!dbUrl) { throw new Error('DATABASE_URL is required'); } if (!dbUrl.startsWith('postgresql://')) { throw new Error('DATABASE_URL must be PostgreSQL'); } const nodeEnv = process.env.NODE_ENV; if (!['development', 'production', 'test'].includes(nodeEnv || '')) { throw new Error('Invalid NODE_ENV'); } const enableCache = process.env.ENABLE_CACHE === 'true'; ``` ```typescript // Centralized validation with nviron import { defineEnv, z } from 'nviron'; export const env = defineEnv({ PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url().startsWith('postgresql://'), NODE_ENV: z.enum(['development', 'production', 'test']), ENABLE_CACHE: z.coerce.boolean(), }); // Use anywhere with full type safety console.log(env.PORT); // number console.log(env.DATABASE_URL); // string console.log(env.NODE_ENV); // "development" | "production" | "test" console.log(env.ENABLE_CACHE); // boolean ``` Migration Steps [#migration-steps] Create env.ts [#create-envts] Create a centralized environment configuration file: ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv({ // Add your environment variables here }); ``` Convert Validations [#convert-validations] Convert your existing validation logic to Zod schemas: ```typescript // Before: Manual validation const apiKey = process.env.API_KEY; if (!apiKey || apiKey.length < 32) { throw new Error("API_KEY must be at least 32 characters"); } // After: Zod schema API_KEY: z.string().min(32); ``` Replace process.env [#replace-processenv] Replace all `process.env` usage with imported `env`: ```typescript // Before const port = parseInt(process.env.PORT || "3000"); // After import { env } from "./env"; const port = env.PORT; ``` Test Thoroughly [#test-thoroughly] Run your application and ensure all environment variables are properly validated. Migrating from dotenv-safe [#migrating-from-dotenv-safe] ```javascript require('dotenv-safe').config({ example: '.env.example', }); // Type-unsafe usage const port = parseInt(process.env.PORT); const dbUrl = process.env.DATABASE_URL; ``` ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ PORT: z.coerce.number(), DATABASE_URL: z.string().url(), }); // Fully typed const port = env.PORT; // number const dbUrl = env.DATABASE_URL; // string ``` Key Differences [#key-differences] | Feature | dotenv-safe | nviron | | -------------- | ----------------------- | ---------------------------- | | Type Safety | ❌ No | βœ… Yes | | Validation | Basic existence check | Comprehensive Zod validation | | Type Coercion | ❌ Manual | βœ… Automatic | | Error Messages | Generic | Detailed and colored | | TypeScript | ❌ Requires manual types | βœ… Automatic inference | Migrating from envalid [#migrating-from-envalid] ```javascript const { cleanEnv, str, num, url } = require('envalid'); const env = cleanEnv(process.env, { PORT: num({ default: 3000 }), DATABASE_URL: url(), NODE_ENV: str({ choices: ['development', 'production', 'test'] }), API_KEY: str(), }); ``` ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), NODE_ENV: z.enum(['development', 'production', 'test']), API_KEY: z.string(), }); ``` Conversion Table [#conversion-table] | envalid | nviron | | ------------------------- | ------------------------- | | `str()` | `z.string()` | | `num()` | `z.coerce.number()` | | `bool()` | `z.coerce.boolean()` | | `url()` | `z.string().url()` | | `email()` | `z.string().email()` | | `str({ choices: [...] })` | `z.enum([...])` | | `str({ default: 'x' })` | `z.string().default('x')` | | `str().optional()` | `z.string().optional()` | Custom Validators [#custom-validators] ```javascript const { makeValidator } = require('envalid'); const strongPassword = makeValidator((input) => { if (input.length < 12) { throw new Error('Password must be at least 12 characters'); } return input; }); const env = cleanEnv(process.env, { PASSWORD: strongPassword(), }); ``` ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ PASSWORD: z.string().min(12, { message: 'Password must be at least 12 characters', }), // Or with custom refinement PASSWORD: z.string().refine( (pwd) => pwd.length >= 12 && /[A-Z]/.test(pwd), { message: 'Password must be 12+ chars with uppercase' } ), }); ``` Migrating from t3-env [#migrating-from-t3-env] ```typescript import { createEnv } from "@t3-oss/env-core"; import { z } from "zod"; export const env = createEnv({ server: { DATABASE_URL: z.string().url(), API_SECRET: z.string().min(32), }, client: { NEXT_PUBLIC_API_URL: z.string().url(), }, runtimeEnv: process.env, }); ``` ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ // Server DATABASE_URL: z.string().url(), API_SECRET: z.string().min(32), // Client (Next.js) NEXT_PUBLIC_API_URL: z.string().url(), }); ``` Key Differences [#key-differences-1] **t3-env:** * Separates server/client explicitly * Built specifically for Next.js * More configuration required **nviron:** * No explicit separation needed (Next.js handles this with `NEXT_PUBLIC_` prefix) * Framework agnostic * Simpler API For Next.js projects, nviron relies on Next.js's built-in `NEXT_PUBLIC_` prefix convention rather than requiring explicit client/server separation. Migrating from joi [#migrating-from-joi] ```javascript const Joi = require('joi'); const envSchema = Joi.object({ PORT: Joi.number().default(3000), DATABASE_URL: Joi.string().uri().required(), NODE_ENV: Joi.string() .valid('development', 'production', 'test') .required(), }).unknown(); const { error, value: env } = envSchema.validate(process.env); if (error) { throw new Error(`Config validation error: ${error.message}`); } ``` ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), NODE_ENV: z.enum(['development', 'production', 'test']), }); ``` Conversion Table [#conversion-table-1] | Joi | nviron/Zod | | ------------------------------ | ---------------------------------- | | `Joi.string()` | `z.string()` | | `Joi.number()` | `z.coerce.number()` | | `Joi.boolean()` | `z.coerce.boolean()` | | `Joi.string().uri()` | `z.string().url()` | | `Joi.string().email()` | `z.string().email()` | | `Joi.string().valid('a', 'b')` | `z.enum(['a', 'b'])` | | `Joi.string().default('x')` | `z.string().default('x')` | | `Joi.string().optional()` | `z.string().optional()` | | `Joi.string().required()` | `z.string()` (required by default) | | `Joi.number().min(5)` | `z.coerce.number().min(5)` | | `Joi.number().max(10)` | `z.coerce.number().max(10)` | Framework-Specific Migrations [#framework-specific-migrations] Next.js Projects [#nextjs-projects] If you're using environment variables directly in Next.js: Create env.ts [#create-envts-1] ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv({ // Server-only DATABASE_URL: z.string().url(), API_SECRET: z.string().min(32), // Client-accessible (NEXT_PUBLIC_ prefix) NEXT_PUBLIC_API_URL: z.string().url(), NEXT_PUBLIC_APP_NAME: z.string(), }); ``` Update next.config.mjs [#update-nextconfigmjs] ```javascript title="next.config.mjs" // Validate at build time import "./src/env.ts"; /** @type {import('next').NextConfig} */ const nextConfig = {}; export default nextConfig; ``` Replace process.env [#replace-processenv-1] ```typescript // Before const apiUrl = process.env.NEXT_PUBLIC_API_URL; // After import { env } from "@/env"; const apiUrl = env.NEXT_PUBLIC_API_URL; ``` Vite Projects [#vite-projects] Install nviron [#install-nviron] ```bash npm install nviron ``` Create env.ts with Vite source [#create-envts-with-vite-source] ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv( { API_URL: z.string().url(), APP_NAME: z.string(), }, { source: import.meta.env, prefix: "VITE_", }, ); ``` Update .env files [#update-env-files] ```bash title=".env" VITE_API_URL=https://api.example.com VITE_APP_NAME=My App ``` Replace import.meta.env [#replace-importmetaenv] ```typescript // Before const apiUrl = import.meta.env.VITE_API_URL; // After import { env } from "./env"; const apiUrl = env.API_URL; // Note: no VITE_ prefix ``` Common Migration Patterns [#common-migration-patterns] Environment-Specific Configuration [#environment-specific-configuration] If you have different `.env` files for different environments: ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; const isProduction = process.env.NODE_ENV === "production"; export const env = defineEnv({ NODE_ENV: z.enum(["development", "production", "test"]), // Stricter validation in production DATABASE_URL: isProduction ? z.string().url().startsWith("postgresql://") : z.string().url(), // Required in production, optional in development SENTRY_DSN: isProduction ? z.string().url() : z.string().url().optional(), }); ``` Optional with Fallbacks [#optional-with-fallbacks] Converting optional variables with fallback logic: ```typescript const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379'; const maxRetries = parseInt(process.env.MAX_RETRIES || '3'); const enableCache = process.env.ENABLE_CACHE === 'true' || true; ``` ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ REDIS_URL: z.string().url().default('redis://localhost:6379'), MAX_RETRIES: z.coerce.number().default(3), ENABLE_CACHE: z.coerce.boolean().default(true), }); ``` Complex Parsing [#complex-parsing] Converting complex parsing logic: ```typescript const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || []; const featureFlags = JSON.parse(process.env.FEATURE_FLAGS || '{}'); const portRange = process.env.PORT_RANGE?.split('-').map(Number) || [3000, 4000]; ``` ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ ALLOWED_ORIGINS: z.string() .default('') .transform((val) => val.split(',').filter(Boolean)), FEATURE_FLAGS: z.string() .default('{}') .transform((val) => JSON.parse(val)) .pipe(z.record(z.boolean())), PORT_RANGE: z.string() .default('3000-4000') .transform((val) => val.split('-').map(Number)), }); ``` Testing After Migration [#testing-after-migration] Verify Validation Works [#verify-validation-works] ```typescript // Test invalid values process.env.PORT = "not-a-number"; // Should throw error with clear message process.env.DATABASE_URL = "invalid-url"; // Should throw error process.env.API_KEY = "too-short"; // Should throw error if min length specified ``` Check Type Safety [#check-type-safety] ```typescript import { env } from "./env"; // TypeScript should catch these errors: env.PORT.toUpperCase(); // ❌ Error: number doesn't have toUpperCase env.DATABASE_URL.toFixed(2); // ❌ Error: string doesn't have toFixed // These should work: env.PORT.toFixed(2); // βœ… number method env.DATABASE_URL.toUpperCase(); // βœ… string method ``` Rollback Plan [#rollback-plan] If you need to rollback the migration: 1. **Keep old code temporarily** ```typescript // Keep both during migration export const env = defineEnv({ ... }); // New export const legacyEnv = process.env; // Old ``` 2. **Gradual migration** * Migrate one module at a time * Test thoroughly before moving to the next 3. **Feature flag** ```typescript const USE_NVIRON = process.env.USE_NVIRON === "true"; export const env = USE_NVIRON ? nvironEnv : legacyEnv; ``` **Migration Tip:** Start with non-critical environments (development/staging) before migrating production. # Quick Start (/docs/quick-start) Your First Environment Configuration [#your-first-environment-configuration] This guide will walk you through creating your first nviron configuration in just a few minutes. Install Nviron [#install-nviron] ```bash npm install nviron ``` Create Environment Variables [#create-environment-variables] Create a `.env` file in your project root: ```bash title=".env" NODE_ENV=development PORT=3000 DATABASE_URL=postgresql://localhost:5432/mydb API_KEY=my-secret-key-1234567890123456 ENABLE_ANALYTICS=true ``` **Important:** Add `.env` to your `.gitignore` to prevent committing sensitive data: ```bash title=".gitignore" .env .env.local ``` Define Your Schema [#define-your-schema] Create an `env.ts` file to define and validate your environment variables: ```typescript title="src/env.ts" import { defineEnv, z } from 'nviron'; export const env = defineEnv({ NODE_ENV: z.enum(['development', 'production', 'test']), PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), API_KEY: z.string().min(32), ENABLE_ANALYTICS: z.coerce.boolean().default(false), }); ``` Use Your Environment Variables [#use-your-environment-variables] Import and use the validated environment variables anywhere in your application: ```typescript title="src/index.ts" import { env } from './env'; console.log('Environment:', env.NODE_ENV); console.log('Server Port:', env.PORT); console.log('Database:', env.DATABASE_URL); console.log('Analytics:', env.ENABLE_ANALYTICS); // TypeScript knows the exact types! env.PORT; // type: number env.NODE_ENV; // type: "development" | "production" | "test" env.ENABLE_ANALYTICS; // type: boolean ``` Understanding the Schema [#understanding-the-schema] Let's break down what each validation does: ```typescript import { defineEnv, z } from 'nviron'; const env = defineEnv({ // Ensures NODE_ENV is exactly one of these values NODE_ENV: z.enum(['development', 'production', 'test']), // Converts string to number, with a default fallback PORT: z.coerce.number().default(3000), // Validates it's a properly formatted URL DATABASE_URL: z.string().url(), // Ensures API key is at least 32 characters API_KEY: z.string().min(32), // Converts string 'true'/'false' to boolean ENABLE_ANALYTICS: z.coerce.boolean().default(false), }); ``` **Zod Coercion:** Use `z.coerce` to automatically convert string values to the desired type. This is essential because environment variables are always strings initially. Handling Validation Errors [#handling-validation-errors] When an environment variable is missing or invalid, nviron displays a clear error message: ```bash title="Terminal Output" ❌ Environment Variable Missing or Invalid 2 issues found in your environment configuration. 1. API_KEY β†’ String must contain at least 32 character(s) 2. DATABASE_URL β†’ Invalid url πŸ’‘ Check your .env file or environment variables before starting the server. ``` The process will exit with an error code, preventing your application from starting with invalid configuration. Common Validation Patterns [#common-validation-patterns] Optional Variables [#optional-variables] ```typescript const env = defineEnv({ // Optional with no default OPTIONAL_KEY: z.string().optional(), // Optional with default value MAX_CONNECTIONS: z.coerce.number().default(10), }); ``` URL Validation [#url-validation] ```typescript const env = defineEnv({ API_URL: z.string().url(), WEBSOCKET_URL: z.string().url().startsWith('wss://'), CALLBACK_URL: z.string().url().includes('/callback'), }); ``` String Validation [#string-validation] ```typescript const env = defineEnv({ // Minimum length PASSWORD: z.string().min(8), // Maximum length USERNAME: z.string().max(50), // Email validation ADMIN_EMAIL: z.string().email(), // Regex pattern API_VERSION: z.string().regex(/^v\d+$/), }); ``` Number Validation [#number-validation] ```typescript const env = defineEnv({ // Positive number PORT: z.coerce.number().positive(), // Range validation TIMEOUT: z.coerce.number().min(1000).max(30000), // Integer only MAX_RETRIES: z.coerce.number().int(), }); ``` Boolean Validation [#boolean-validation] ```typescript const env = defineEnv({ // Accepts: "true", "false", "1", "0", "yes", "no" ENABLE_FEATURE: z.coerce.boolean(), // With default DEBUG: z.coerce.boolean().default(false), }); ``` Custom Validation [#custom-validation] ```typescript const env = defineEnv({ // Custom refinement DATABASE_URL: z.string().url().refine( (url) => url.startsWith('postgresql://'), { message: 'Database URL must be PostgreSQL' } ), // Transform value ALLOWED_ORIGINS: z.string().transform((val) => val.split(',')), }); ``` Framework-Specific Examples [#framework-specific-examples] Next.js App Router [#nextjs-app-router] ```typescript title="src/env.ts" import { defineEnv, z } from 'nviron'; export const env = defineEnv({ // Server-side only DATABASE_URL: z.string().url(), API_SECRET: z.string().min(32), // Client-side (NEXT_PUBLIC_ prefix) NEXT_PUBLIC_API_URL: z.string().url(), NEXT_PUBLIC_SITE_NAME: z.string(), }); ``` ```typescript title="next.config.mjs" // Validate env at build time import './src/env.ts'; /** @type {import('next').NextConfig} */ const nextConfig = {}; export default nextConfig; ``` Vite / React [#vite--react] ```typescript title="src/env.ts" import { defineEnv, z } from 'nviron'; export const env = defineEnv( { API_URL: z.string().url(), APP_TITLE: z.string(), ANALYTICS_ID: z.string().optional(), }, { source: import.meta.env, prefix: 'VITE_', } ); ``` ```bash title=".env" VITE_API_URL=https://api.example.com VITE_APP_TITLE=My App VITE_ANALYTICS_ID=G-XXXXXXXXXX ``` Express / Node.js [#express--nodejs] ```typescript title="src/env.ts" import { defineEnv, z } from 'nviron'; export const env = defineEnv({ NODE_ENV: z.enum(['development', 'production', 'test']), PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), REDIS_URL: z.string().url(), JWT_SECRET: z.string().min(32), SESSION_SECRET: z.string().min(32), CORS_ORIGIN: z.string().url(), }); ``` ```typescript title="src/server.ts" import express from 'express'; import { env } from './env'; const app = express(); // Use validated environment variables app.listen(env.PORT, () => { console.log(`πŸš€ Server running on port ${env.PORT}`); console.log(`πŸ“Š Environment: ${env.NODE_ENV}`); }); ``` Testing with Different Environments [#testing-with-different-environments] Development vs Production [#development-vs-production] ```bash title=".env.development" NODE_ENV=development PORT=3000 DATABASE_URL=postgresql://localhost:5432/mydb_dev DEBUG=true LOG_LEVEL=debug ``` ```bash title=".env.production" NODE_ENV=production PORT=8080 DATABASE_URL=postgresql://prod-db.example.com:5432/mydb DEBUG=false LOG_LEVEL=error ``` Testing Environment [#testing-environment] ```typescript title="src/env.test.ts" import { defineEnv, z } from 'nviron'; // Override source for testing const testEnv = defineEnv( { DATABASE_URL: z.string().url(), API_KEY: z.string(), }, { source: { DATABASE_URL: 'postgresql://localhost:5432/test_db', API_KEY: 'test-key-for-testing-only-12345', }, } ); console.log(testEnv.DATABASE_URL); // Uses test values ``` What's Next? [#whats-next] Deep dive into how nviron works Complete API documentation More real-world examples Learn the recommended patterns # Troubleshooting (/docs/troubleshooting) Common Errors [#common-errors] Validation Errors [#validation-errors] String Expected, Received Undefined [#string-expected-received-undefined] **Error Message:** ```bash ❌ Environment Variable Missing or Invalid 1. DATABASE_URL β†’ Expected string, received undefined ``` **Cause:** The environment variable is not defined in your `.env` file or environment. **Solution:** 1. Check your `.env` file exists and contains the variable: ```bash DATABASE_URL=postgresql://localhost:5432/mydb ``` 2. Ensure you're loading environment variables correctly: ```bash # If using dotenv node -r dotenv/config src/index.js ``` 3. For Next.js, restart your development server after adding new variables 4. Verify the variable name matches exactly (case-sensitive): ```typescript // Schema expects DATABASE_URL DATABASE_URL: z.string().url() // .env must have DATABASE_URL, not database_url DATABASE_URL=postgresql://... ``` Expected Number, Received String [#expected-number-received-string] **Error Message:** ```bash ❌ Environment Variable Missing or Invalid 1. PORT β†’ Expected number, received string ``` **Cause:** Using `z.number()` instead of `z.coerce.number()`. **Solution:** Environment variables are always strings. Use coercion: ```typescript // ❌ Wrong const env = defineEnv({ PORT: z.number(), }); // βœ… Correct const env = defineEnv({ PORT: z.coerce.number(), }); ``` Invalid URL [#invalid-url] **Error Message:** ```bash ❌ Environment Variable Missing or Invalid 1. DATABASE_URL β†’ Invalid url ``` **Cause:** The value is not a valid URL format. **Solution:** Ensure the URL is properly formatted: ```bash # ❌ Wrong DATABASE_URL=localhost:5432/mydb # βœ… Correct DATABASE_URL=postgresql://localhost:5432/mydb ``` Common URL issues: * Missing protocol (`http://`, `https://`, `postgresql://`) * Invalid characters or spaces * Incorrect port format * Missing slashes String Must Contain at Least X Characters [#string-must-contain-at-least-x-characters] **Error Message:** ```bash ❌ Environment Variable Missing or Invalid 1. JWT_SECRET β†’ String must contain at least 32 character(s) ``` **Cause:** The value doesn't meet the minimum length requirement. **Solution:** Generate a proper secret: ```bash # Generate a secure 32+ character secret openssl rand -base64 32 # Or use a password generator # Ensure it's at least 32 characters ``` Update your `.env`: ```bash JWT_SECRET=your-generated-secret-here-min-32-chars ``` TypeScript Errors [#typescript-errors] Cannot Find Module 'nviron' [#cannot-find-module-nviron] **Error Message:** ``` Cannot find module 'nviron' or its corresponding type declarations. ``` **Solution:** 1. Ensure nviron is installed: ```bash npm install nviron ``` 2. Check your `package.json` includes nviron: ```json { "dependencies": { "nviron": "^1.0.0" } } ``` 3. Restart your TypeScript server in your IDE Type 'X' is Not Assignable to Type 'Y' [#type-x-is-not-assignable-to-type-y] **Error Message:** ```typescript Type 'string | undefined' is not assignable to type 'string'. ``` **Cause:** Using an optional value where a required value is expected. **Solution:** 1. Handle the optional case: ```typescript const env = defineEnv({ REDIS_URL: z.string().url().optional(), }); // ❌ Wrong: Might be undefined const client = createClient(env.REDIS_URL); // βœ… Correct: Handle undefined const client = env.REDIS_URL ? createClient(env.REDIS_URL) : null; ``` 2. Or use a default value: ```typescript const env = defineEnv({ REDIS_URL: z.string().url().default("redis://localhost:6379"), }); // Now env.REDIS_URL is always a string const client = createClient(env.REDIS_URL); ``` Framework-Specific Issues [#framework-specific-issues] Next.js: Environment Variables Not Available [#nextjs-environment-variables-not-available] **Problem:** Environment variables work in development but not in production. **Solution:** 1. For client-side variables, use the `NEXT_PUBLIC_` prefix: ```bash # Client-side NEXT_PUBLIC_API_URL=https://api.example.com # Server-side only DATABASE_URL=postgresql://... ``` 2. Validate environment at build time in `next.config.mjs`: ```javascript import "./src/env.ts"; /** @type {import('next').NextConfig} */ const nextConfig = {}; export default nextConfig; ``` 3. For Vercel deployment, add environment variables in project settings Vite: import.meta.env is Undefined [#vite-importmetaenv-is-undefined] **Problem:** Using `import.meta.env` in Node.js context. **Solution:** Only use `import.meta.env` in Vite/browser code: ```typescript // βœ… Correct: In Vite project const env = defineEnv( { API_URL: z.string().url(), }, { source: import.meta.env, prefix: "VITE_", }, ); ``` For server-side Vite (SSR): ```typescript // Use process.env for server-side const env = defineEnv({ DATABASE_URL: z.string().url(), }); ``` Express: process.env Variables Not Loading [#express-processenv-variables-not-loading] **Problem:** Variables in `.env` file not being loaded. **Solution:** 1. Install dotenv: ```bash npm install dotenv ``` 2. Load environment variables early: ```typescript import "dotenv/config"; // Must be first import import { env } from "./env"; import express from "express"; ``` 3. Or use the `-r` flag: ```bash node -r dotenv/config src/index.js ``` Prefix Issues [#prefix-issues] Variables Not Found with Prefix [#variables-not-found-with-prefix] **Problem:** Using prefix but variables still not found. **Solution:** Ensure your `.env` file uses the prefix: ```typescript // env.ts const env = defineEnv( { API_URL: z.string().url(), }, { prefix: "VITE_", }, ); ``` ```bash # .env - Must include prefix VITE_API_URL=https://api.example.com # ❌ Wrong - No prefix API_URL=https://api.example.com ``` Prefix Not Being Stripped [#prefix-not-being-stripped] **Problem:** Accessing variables with prefix instead of without. **Cause:** Misunderstanding how prefix works. **Explanation:** Nviron automatically strips the prefix: ```typescript const env = defineEnv( { API_URL: z.string().url(), // Schema key without prefix }, { prefix: "VITE_", }, ); // Access WITHOUT prefix console.log(env.API_URL); // βœ… Correct console.log(env.VITE_API_URL); // ❌ Wrong - doesn't exist ``` Coercion Issues [#coercion-issues] Boolean Coercion Not Working [#boolean-coercion-not-working] **Problem:** Boolean values not being converted properly. **Solution:** Understand valid boolean string values: ```bash # βœ… These work ENABLE_FEATURE=true ENABLE_FEATURE=false ENABLE_FEATURE=1 ENABLE_FEATURE=0 ENABLE_FEATURE=yes ENABLE_FEATURE=no # ❌ These don't work ENABLE_FEATURE=enabled # Not a valid boolean string ENABLE_FEATURE=TRUE # Case sensitive ``` Use in schema: ```typescript const env = defineEnv({ ENABLE_FEATURE: z.coerce.boolean(), }); ``` Number Coercion Failing [#number-coercion-failing] **Problem:** Number conversion fails with valid-looking numbers. **Cause:** Invalid number format or special characters. **Solution:** ```bash # βœ… Valid PORT=3000 MAX_SIZE=1000000 # ❌ Invalid PORT=3,000 # No commas PORT=3000px # No units PORT=three # Must be numeric PORT=3.0.0 # Not a valid number ``` Date Coercion Issues [#date-coercion-issues] **Problem:** Date coercion not working as expected. **Solution:** Use ISO 8601 date format: ```bash # βœ… Valid formats CREATED_AT=2024-01-15 CREATED_AT=2024-01-15T10:30:00Z CREATED_AT=2024-01-15T10:30:00+00:00 # ❌ Invalid formats CREATED_AT=15/01/2024 CREATED_AT=Jan 15 2024 CREATED_AT=1642247400 # Use z.coerce.number() then transform ``` Validation Issues [#validation-issues] Custom Validation Not Working [#custom-validation-not-working] **Problem:** Refinement rules not being applied. **Solution:** Ensure you're using `.refine()` correctly: ```typescript // βœ… Correct const env = defineEnv({ DATABASE_URL: z .string() .url() .refine((url) => url.startsWith("postgresql://"), { message: "Must be a PostgreSQL URL", }), }); // ❌ Wrong - refine returns undefined const env = defineEnv({ DATABASE_URL: z .string() .url() .refine( (url) => url.startsWith("postgresql://"), // Missing return ), }); ``` Transform Not Applied [#transform-not-applied] **Problem:** Transformed values not working as expected. **Solution:** Understand transform order: ```typescript const env = defineEnv({ // Transforms happen after validation ALLOWED_ORIGINS: z .string() .min(1) // Validates first .transform((val) => val.split(",")) // Then transforms .refine((arr) => arr.length > 0, { // Then refines message: "At least one origin required", }), }); ``` Performance Issues [#performance-issues] Slow Application Startup [#slow-application-startup] **Problem:** Application takes too long to start. **Cause:** Complex validations or too many environment variables. **Solution:** 1. Simplify validation rules: ```typescript // ❌ Slow: Complex regex API_KEY: z.string().regex(/^[A-Za-z0-9_-]{32,256}$/); // βœ… Faster: Simple length check API_KEY: z.string().min(32).max(256); ``` 2. Use a singleton pattern: ```typescript // env.ts - Created once export const env = defineEnv({ ... }); ``` Memory Usage [#memory-usage] **Problem:** High memory usage from environment validation. **Solution:** Ensure you're not creating multiple instances: ```typescript // ❌ Wrong: New instance every import export function getEnv() { return defineEnv({ ... }); } // βœ… Correct: Single instance export const env = defineEnv({ ... }); ``` Development vs Production [#development-vs-production] Works in Development, Fails in Production [#works-in-development-fails-in-production] **Problem:** Validation passes locally but fails in production. **Causes & Solutions:** 1. **Missing environment variables** * Add all required variables to your production environment * Use your hosting provider's environment variable settings 2. **Different defaults** ```typescript // Make critical values required const env = defineEnv({ DATABASE_URL: z.string().url(), // No default JWT_SECRET: z.string().min(32), // No default }); ``` 3. **Case sensitivity** ```bash # Development (Windows - case insensitive) database_url=postgresql://... # Production (Linux - case sensitive) DATABASE_URL=postgresql://... # Must match exactly ``` Production Builds Failing [#production-builds-failing] **Problem:** Build fails during CI/CD. **Solution:** 1. Set environment variables in CI/CD: ```yaml # GitHub Actions example env: DATABASE_URL: ${{ secrets.DATABASE_URL }} JWT_SECRET: ${{ secrets.JWT_SECRET }} ``` 2. Use build-time validation for Next.js: ```javascript // next.config.mjs import "./src/env.ts"; // Validates during build ``` 3. Provide test values for CI: ```bash # .env.ci DATABASE_URL=postgresql://test:test@localhost:5432/test JWT_SECRET=test-secret-for-ci-only-32-chars ``` Debugging [#debugging] Enable Debug Mode [#enable-debug-mode] See what's happening during validation: ```typescript import { defineEnv, z } from "nviron"; // Temporarily log values for debugging const schema = { PORT: z.coerce.number(), DATABASE_URL: z.string().url(), }; console.log("Environment source:", process.env); console.log("Schema keys:", Object.keys(schema)); const env = defineEnv(schema); console.log("Validated env:", env); ``` Check Environment Loading [#check-environment-loading] Verify environment variables are being loaded: ```typescript // Check if .env is loaded console.log("NODE_ENV:", process.env.NODE_ENV); console.log("All env vars:", Object.keys(process.env)); // Check specific variable console.log("DATABASE_URL exists:", "DATABASE_URL" in process.env); console.log("DATABASE_URL value:", process.env.DATABASE_URL); ``` Validate Step by Step [#validate-step-by-step] Debug validation by testing individual variables: ```typescript import { z } from "nviron"; // Test individual validations try { z.coerce.number().parse(process.env.PORT); console.log("βœ… PORT is valid"); } catch (error) { console.error("❌ PORT validation failed:", error); } try { z.string().url().parse(process.env.DATABASE_URL); console.log("βœ… DATABASE_URL is valid"); } catch (error) { console.error("❌ DATABASE_URL validation failed:", error); } ``` Getting Help [#getting-help] If you're still experiencing issues: 1. **Check the documentation** - Review relevant sections 2. **Search existing issues** - Someone may have had the same problem 3. **Create a minimal reproduction** - Isolate the issue 4. **Open an issue** - Provide: * Environment (Node version, framework, OS) * Minimal code example * Error message * Expected vs actual behavior For more help, visit the [GitHub Issues](https://github.com/your-username/nviron/issues) page. # Advanced Patterns (/docs/examples/advanced) Advanced Patterns [#advanced-patterns] Custom Transformations [#custom-transformations] Transform environment variables during validation. ```typescript import { defineEnv, z } from "nviron"; const env = defineEnv({ // Split comma-separated list ALLOWED_ORIGINS: z .string() .transform((val) => val.split(",")) .refine((origins) => origins.length > 0, { message: "At least one origin is required", }), // Parse JSON FEATURE_FLAGS: z .string() .transform((val) => JSON.parse(val)) .pipe(z.record(z.boolean())), // Convert to uppercase ENVIRONMENT: z.string().transform((val) => val.toUpperCase()), // Parse range ALLOWED_PORTS: z .string() .transform((val) => val.split("-").map(Number)) .refine((ports) => ports.length === 2 && ports[0] < ports[1], { message: "Invalid port range", }), }); // Usage: console.log(env.ALLOWED_ORIGINS); // ['http://localhost:3000', 'https://app.com'] console.log(env.FEATURE_FLAGS); // { analytics: true, beta: false } console.log(env.ENVIRONMENT); // 'PRODUCTION' console.log(env.ALLOWED_PORTS); // [3000, 4000] ``` Complex Validation [#complex-validation] Advanced validation patterns with custom rules. ```typescript import { defineEnv, z } from "nviron"; const env = defineEnv({ // URL with specific requirements DATABASE_URL: z .string() .url() .refine((url) => url.startsWith("postgresql://"), { message: "Database must be PostgreSQL", }) .refine( (url) => !url.includes("localhost") || process.env.NODE_ENV === "development", { message: "Localhost database not allowed in production", }, ), // Conditional validation SMTP_HOST: z.string().refine( (host) => { const nodeEnv = process.env.NODE_ENV; if (nodeEnv === "production") { return host.length > 0; } return true; }, { message: "SMTP_HOST is required in production", }, ), // Cross-field validation MIN_POOL_SIZE: z.coerce.number().int(), MAX_POOL_SIZE: z.coerce.number().int(), }).refine((data) => data.MIN_POOL_SIZE < data.MAX_POOL_SIZE, { message: "MIN_POOL_SIZE must be less than MAX_POOL_SIZE", path: ["MAX_POOL_SIZE"], }); ``` Environment-Specific Schemas [#environment-specific-schemas] Different schemas for different environments. ```typescript import { defineEnv, z } from "nviron"; const baseSchema = { NODE_ENV: z.enum(["development", "production", "test"]), PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), }; const developmentSchema = { ...baseSchema, DEBUG: z.coerce.boolean().default(true), MOCK_APIS: z.coerce.boolean().default(true), }; const productionSchema = { ...baseSchema, SENTRY_DSN: z.string().url(), NEW_RELIC_KEY: z.string(), CDN_URL: z.string().url(), }; const isProduction = process.env.NODE_ENV === "production"; export const env = defineEnv( isProduction ? productionSchema : developmentSchema, ); ``` For more examples, check out the [Best Practices](/docs/best-practices) guide. # Environments (/docs/examples/environments) Development vs Production [#development-vs-production] Separate configurations for different environments. ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ NODE_ENV: z.literal('development'), // Relaxed validation for development DATABASE_URL: z.string().default('postgresql://localhost:5432/dev_db'), PORT: z.coerce.number().default(3000), // Development-only features DEBUG: z.coerce.boolean().default(true), ENABLE_HOT_RELOAD: z.coerce.boolean().default(true), MOCK_EXTERNAL_APIS: z.coerce.boolean().default(true), // Optional in development REDIS_URL: z.string().url().optional(), SMTP_HOST: z.string().optional(), }); ``` ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ NODE_ENV: z.literal('production'), // Strict validation for production DATABASE_URL: z.string().url().startsWith('postgresql://'), PORT: z.coerce.number().int().positive(), // Required in production REDIS_URL: z.string().url(), SMTP_HOST: z.string(), SMTP_PORT: z.coerce.number(), SMTP_USER: z.string().email(), SMTP_PASSWORD: z.string().min(12), // Security JWT_SECRET: z.string().min(32), SESSION_SECRET: z.string().min(32), ENCRYPTION_KEY: z.string().length(64), // Monitoring SENTRY_DSN: z.string().url(), NEW_RELIC_LICENSE_KEY: z.string().length(40), // Production-only CDN_URL: z.string().url(), RATE_LIMIT_STRICT: z.coerce.boolean().default(true), }); ``` ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ NODE_ENV: z.literal('test'), // Test database DATABASE_URL: z.string().default('postgresql://localhost:5432/test_db'), // Test configuration PORT: z.coerce.number().default(0), // Random port // Override for tests DISABLE_RATE_LIMITING: z.coerce.boolean().default(true), MOCK_EXTERNAL_SERVICES: z.coerce.boolean().default(true), // Fast timeouts for tests REQUEST_TIMEOUT: z.coerce.number().default(1000), // Optional in tests REDIS_URL: z.string().url().optional(), SMTP_HOST: z.string().optional(), }); ``` # Examples (/docs/examples) Explore our collection of examples to see how nviron can be used in different scenarios and frameworks. Frameworks [#frameworks] Complete setup with server and client environment variables Setup for Vite applications with prefix handling Backend server configuration with comprehensive validation Use Cases [#use-cases] Environment configuration for microservice architecture Configuration for multi-tenant applications Environment configuration for AWS Lambda or serverless functions Advanced Topics [#advanced-topics] Separate configurations for different environments Custom transformations, complex validation, and more # Microservices (/docs/examples/microservices) Microservices Architecture [#microservices-architecture] Environment configuration for a microservice. ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv({ // Service Identity SERVICE_NAME: z.string().default("user-service"), SERVICE_VERSION: z.string().regex(/^\d+\.\d+\.\d+$/), SERVICE_PORT: z.coerce.number().int().positive(), // Service Discovery CONSUL_HOST: z.string().default("localhost"), CONSUL_PORT: z.coerce.number().int().default(8500), // Message Queue RABBITMQ_URL: z.string().url().startsWith("amqp://"), RABBITMQ_QUEUE: z.string(), RABBITMQ_EXCHANGE: z.string(), // Database DATABASE_URL: z.string().url(), DATABASE_READ_REPLICA_URL: z.string().url().optional(), // Distributed Tracing JAEGER_AGENT_HOST: z.string().default("localhost"), JAEGER_AGENT_PORT: z.coerce.number().int().default(6831), // Health Checks HEALTH_CHECK_INTERVAL: z.coerce.number().int().default(30000), READINESS_TIMEOUT: z.coerce.number().int().default(5000), }); ``` # Next.js Example (/docs/examples/nextjs) Next.js App Router [#nextjs-app-router] Complete Next.js setup with server and client environment variables. ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ // Server-side only NODE_ENV: z.enum(['development', 'production', 'test']), DATABASE_URL: z.string().url().startsWith('postgresql://'), DATABASE_POOL_SIZE: z.coerce.number().int().min(1).max(100).default(10), REDIS_URL: z.string().url().optional(), // Authentication NEXTAUTH_SECRET: z.string().min(32), NEXTAUTH_URL: z.string().url(), // External APIs (server-side) OPENAI_API_KEY: z.string().min(20), STRIPE_SECRET_KEY: z.string().startsWith('sk_'), // Email SMTP_HOST: z.string(), SMTP_PORT: z.coerce.number().int(), SMTP_USER: z.string().email(), SMTP_PASSWORD: z.string().min(8), // Client-side (NEXT_PUBLIC_ prefix) NEXT_PUBLIC_APP_URL: z.string().url(), NEXT_PUBLIC_API_URL: z.string().url(), NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().startsWith('pk_'), NEXT_PUBLIC_ANALYTICS_ID: z.string().optional(), }); ``` ```javascript // Validate environment variables at build time import './src/env.ts'; /** @type {import('next').NextConfig} */ const nextConfig = { // Your Next.js config here }; export default nextConfig; ``` ```bash # Node Environment NODE_ENV=development # Database DATABASE_URL=postgresql://user:password@localhost:5432/mydb DATABASE_POOL_SIZE=10 REDIS_URL=redis://localhost:6379 # Authentication NEXTAUTH_SECRET=your-nextauth-secret-min-32-chars NEXTAUTH_URL=http://localhost:3000 # External APIs OPENAI_API_KEY=sk-your-openai-api-key STRIPE_SECRET_KEY=sk_test_your_stripe_secret_key # Email SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_USER=your-email@gmail.com SMTP_PASSWORD=your-app-password # Public (Client-side) NEXT_PUBLIC_APP_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:3000/api NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_stripe_public_key NEXT_PUBLIC_ANALYTICS_ID=G-XXXXXXXXXX ``` # Node.js Example (/docs/examples/nodejs) Express.js / Node.js [#expressjs--nodejs] Backend server configuration with comprehensive validation. ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv({ // Server Configuration NODE_ENV: z.enum(['development', 'production', 'test']), PORT: z.coerce.number().int().positive().default(3000), HOST: z.string().default('0.0.0.0'), // Database DATABASE_URL: z.string().url().startsWith('postgresql://'), DATABASE_POOL_MIN: z.coerce.number().int().min(0).default(2), DATABASE_POOL_MAX: z.coerce.number().int().min(1).default(10), // Redis REDIS_HOST: z.string().default('localhost'), REDIS_PORT: z.coerce.number().int().default(6379), REDIS_PASSWORD: z.string().optional(), // Security JWT_SECRET: z.string().min(32), JWT_EXPIRES_IN: z.string().default('7d'), SESSION_SECRET: z.string().min(32), BCRYPT_ROUNDS: z.coerce.number().int().min(10).max(15).default(12), // CORS CORS_ORIGIN: z.string().transform((val) => val.split(',')), CORS_CREDENTIALS: z.coerce.boolean().default(true), // Rate Limiting RATE_LIMIT_WINDOW_MS: z.coerce.number().default(900000), // 15 minutes RATE_LIMIT_MAX_REQUESTS: z.coerce.number().default(100), // External APIs SENDGRID_API_KEY: z.string().startsWith('SG.'), AWS_ACCESS_KEY_ID: z.string().length(20), AWS_SECRET_ACCESS_KEY: z.string().length(40), AWS_REGION: z.string().default('us-east-1'), AWS_S3_BUCKET: z.string(), // Logging LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'), LOG_FORMAT: z.enum(['json', 'pretty']).default('json'), }); ``` ```typescript import express from 'express'; import cors from 'cors'; import { env } from './env'; const app = express(); // CORS configuration app.use(cors({ origin: env.CORS_ORIGIN, credentials: env.CORS_CREDENTIALS, })); app.use(express.json()); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', environment: env.NODE_ENV, timestamp: new Date().toISOString(), }); }); // Start server app.listen(env.PORT, env.HOST, () => { console.log(`πŸš€ Server running on ${env.HOST}:${env.PORT}`); console.log(`πŸ“Š Environment: ${env.NODE_ENV}`); console.log(`πŸ’Ύ Database: ${env.DATABASE_URL.split('@')[1]}`); }); ``` ```bash # Server NODE_ENV=development PORT=3000 HOST=0.0.0.0 # Database DATABASE_URL=postgresql://user:password@localhost:5432/mydb DATABASE_POOL_MIN=2 DATABASE_POOL_MAX=10 # Redis REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD= # Security JWT_SECRET=your-jwt-secret-min-32-characters-long JWT_EXPIRES_IN=7d SESSION_SECRET=your-session-secret-min-32-characters BCRYPT_ROUNDS=12 # CORS CORS_ORIGIN=http://localhost:3000,http://localhost:5173 CORS_CREDENTIALS=true # Rate Limiting RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX_REQUESTS=100 # External APIs SENDGRID_API_KEY=SG.your-sendgrid-api-key AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY AWS_REGION=us-east-1 AWS_S3_BUCKET=my-s3-bucket # Logging LOG_LEVEL=info LOG_FORMAT=json ``` # Multi-tenant SaaS (/docs/examples/saas) Multi-tenant SaaS Application [#multi-tenant-saas-application] Configuration for a multi-tenant application. ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv({ // Application APP_NAME: z.string(), APP_URL: z.string().url(), // Multi-tenant Database DATABASE_URL: z.string().url(), TENANT_DATABASE_PREFIX: z.string().default("tenant_"), MAX_TENANTS_PER_DB: z.coerce.number().int().default(100), // Tenant Isolation TENANT_ID_HEADER: z.string().default("x-tenant-id"), SUBDOMAIN_ROUTING: z.coerce.boolean().default(true), // Subscription & Billing STRIPE_SECRET_KEY: z.string().startsWith("sk_"), STRIPE_WEBHOOK_SECRET: z.string().startsWith("whsec_"), TRIAL_PERIOD_DAYS: z.coerce.number().int().default(14), // Feature Flags per Plan FREE_PLAN_MAX_USERS: z.coerce.number().int().default(5), PRO_PLAN_MAX_USERS: z.coerce.number().int().default(50), ENTERPRISE_PLAN_MAX_USERS: z.coerce.number().int().default(10000), // Storage per Tenant S3_BUCKET_PREFIX: z.string().default("tenant-"), MAX_STORAGE_PER_TENANT_GB: z.coerce.number().int().default(10), // Rate Limiting per Tenant RATE_LIMIT_FREE_TIER: z.coerce.number().int().default(100), RATE_LIMIT_PRO_TIER: z.coerce.number().int().default(1000), RATE_LIMIT_ENTERPRISE_TIER: z.coerce.number().int().default(10000), }); ``` # Serverless (/docs/examples/serverless) Serverless / Lambda Function [#serverless--lambda-function] Environment configuration for AWS Lambda or serverless functions. ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv({ // AWS Lambda Environment AWS_REGION: z.string().default("us-east-1"), AWS_EXECUTION_ENV: z.string().optional(), AWS_LAMBDA_FUNCTION_NAME: z.string().optional(), AWS_LAMBDA_FUNCTION_VERSION: z.string().optional(), // Function Configuration FUNCTION_TIMEOUT: z.coerce.number().int().max(900).default(30), FUNCTION_MEMORY: z.coerce.number().int().default(1024), // Database (with connection pooling) DATABASE_URL: z.string().url(), DATABASE_MIN_CONNECTIONS: z.coerce.number().int().default(1), DATABASE_MAX_CONNECTIONS: z.coerce.number().int().default(2), // External Services API_GATEWAY_URL: z.string().url(), SQS_QUEUE_URL: z.string().url(), SNS_TOPIC_ARN: z.string().startsWith("arn:aws:sns:"), // Secrets SECRET_NAME: z.string(), SECRETS_MANAGER_REGION: z.string().default("us-east-1"), // Observability ENABLE_XRAY: z.coerce.boolean().default(true), LOG_LEVEL: z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).default("INFO"), }); ``` # Vite Example (/docs/examples/vite) Vite + React [#vite--react] Setup for Vite applications with proper prefix handling. ```typescript import { defineEnv, z } from 'nviron'; export const env = defineEnv( { // API Configuration API_URL: z.string().url(), API_TIMEOUT: z.coerce.number().default(5000), // App Configuration APP_NAME: z.string().default('My App'), APP_VERSION: z.string().regex(/^\d+\.\d+\.\d+$/), // Feature Flags ENABLE_ANALYTICS: z.coerce.boolean().default(false), ENABLE_DEBUG: z.coerce.boolean().default(false), // External Services SENTRY_DSN: z.string().url().optional(), ANALYTICS_ID: z.string().optional(), // Environment MODE: z.enum(['development', 'production', 'staging']), }, { source: import.meta.env, prefix: 'VITE_', } ); ``` ```typescript import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { env } from './env'; // Log environment info in development if (env.MODE === 'development') { console.log('πŸš€ App Name:', env.APP_NAME); console.log('πŸ“¦ Version:', env.APP_VERSION); console.log('πŸ”— API URL:', env.API_URL); } ReactDOM.createRoot(document.getElementById('root')!).render( ); ``` ```bash # API Configuration VITE_API_URL=https://api.example.com VITE_API_TIMEOUT=5000 # App Configuration VITE_APP_NAME=My Awesome App VITE_APP_VERSION=1.0.0 # Feature Flags VITE_ENABLE_ANALYTICS=false VITE_ENABLE_DEBUG=true # External Services VITE_SENTRY_DSN=https://abc@sentry.io/123 VITE_ANALYTICS_ID=G-XXXXXXXXXX # Environment VITE_MODE=development ``` # Installation (/docs/installation) Prerequisites [#prerequisites] Before installing nviron, ensure you have: * **Node.js** 16.x or higher * A package manager: **npm**, **yarn**, **pnpm**, or **bun** Install Nviron [#install-nviron] Choose your preferred package manager to install nviron: `bash pnpm add nviron ` `bash npm install nviron ` `bash yarn add nviron ` `bash bun add nviron ` **Note:** Nviron re-exports Zod, so you don't need to install Zod separately unless you need it for other purposes in your application. TypeScript Setup [#typescript-setup] Nviron works seamlessly with TypeScript. If you're using TypeScript, no additional configuration is neededβ€”types are included automatically. tsconfig.json [#tsconfigjson] Ensure your `tsconfig.json` has the following settings for optimal compatibility: ```json title="tsconfig.json" { "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "skipLibCheck": true } } ``` Verify Installation [#verify-installation] Create a simple test file to verify nviron is working correctly: ```typescript title="test.ts" import { defineEnv, z } from "nviron"; const env = defineEnv({ NODE_ENV: z.enum(["development", "production"]), }); console.log("βœ… Nviron is installed correctly!"); console.log("Environment:", env.NODE_ENV); ``` Run the file: `bash pnpm tsx test.ts ` `bash npx tsx test.ts ` `bash yarn tsx test.ts ` `bash bun test.ts ` Framework Guides [#framework-guides] Check out our framework-specific guides for detailed setup instructions: * [Next.js](/docs/installation/nextjs) * [Vite](/docs/installation/vite) * [Node.js / Express](/docs/installation/nodejs) What's Next? [#whats-next] Now that you've installed nviron, learn how to use it effectively: Learn the basics with practical examples Understand how nviron works See real-world usage examples # Next.js Installation (/docs/installation/nextjs) Next.js [#nextjs] Nviron works perfectly with Next.js. Create your environment configuration in a separate file: ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv({ DATABASE_URL: z.string().url(), NEXT_PUBLIC_API_URL: z.string().url(), NODE_ENV: z.enum(["development", "production", "test"]), }); ``` Import and use in your app: ```typescript title="src/app/page.tsx" import { env } from '@/env'; export default function Home() { // env is fully typed! console.log(env.NODE_ENV); return
Hello World
; } ``` For Next.js, make sure to import your `env` file in `next.config.js` to validate environment variables at build time: ```javascript title="next.config.js" // Validate env on build import "./src/env.ts"; /** @type {import('next').NextConfig} */ const nextConfig = {}; export default nextConfig; ``` # Node.js Installation (/docs/installation/nodejs) Node.js / Express [#nodejs--express] For Node.js applications: ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv({ PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), JWT_SECRET: z.string().min(32), NODE_ENV: z.enum(["development", "production", "test"]), }); ``` ```typescript title="src/server.ts" import express from "express"; import { env } from "./env"; const app = express(); app.listen(env.PORT, () => { console.log(`Server running on port ${env.PORT}`); }); ``` # Vite Installation (/docs/installation/vite) Vite [#vite] For Vite projects, use the `source` and `prefix` options: ```typescript title="src/env.ts" import { defineEnv, z } from "nviron"; export const env = defineEnv( { API_URL: z.string().url(), ENABLE_ANALYTICS: z.coerce.boolean(), }, { source: import.meta.env, prefix: "VITE_", }, ); ``` Your `.env` file: ```bash title=".env" VITE_API_URL=https://api.example.com VITE_ENABLE_ANALYTICS=true ```