Skip to main content
Version: Next (2.x)

Environment Parser

Baeta provides a type-safe environment variable parser that validates and transforms environment variables at runtime.

Installation

yarn add @baeta/env

Basic Usage

import { createEnvParser } from "@baeta/env";

// Create a parser that reads from process.env
const parse = createEnvParser((key) => process.env[key]);

export const config = {
// Basic string parsing
apiKey: parse("API_KEY", {
type: "string",
required: true,
}),

// Number with default value
port: parse("PORT", {
type: "number",
default: 3000,
}),

// Boolean with custom resolver
isDevelopment: parse("NODE_ENV", {
type: "boolean",
default: true,
resolver: (value) => value === "development",
}),
};

Supported types

  • string
  • number
  • boolean

Type inference

const config = {
port: parse("PORT", {
type: "number",
default: 3000,
}),
cachePrefix: parse("CACHE_PREFIX", {
type: "string",
}),
};

// config.port is inferred as number
const port: number = config.port; // OK
const port: string = config.port; // Type error
const prefix: string = config.cachePrefix; // Error: 'string | undefined' is not assignable to 'string'

Error handling

The parser throws on:

  • a required variable that's missing,
  • a type conversion failure (e.g. "abc" for a number),
  • an error thrown by a custom resolver.

Examples

Required variables

const config = {
databaseUrl: parse("DATABASE_URL", {
type: "string",
required: true, // Will throw if not provided
}),
};

Default values

const config = {
logLevel: parse("LOG_LEVEL", {
type: "string",
default: "info", // Uses 'info' if not provided
}),
};

Custom resolvers

const config = {
isProduction: parse("NODE_ENV", {
type: "boolean",
default: false,
resolver: (value) => value === "production",
}),
};

A larger example

import { createEnvParser } from "@baeta/env";

const parse = createEnvParser((key) => process.env[key]);

export const config = {
// Basic required string
apiUrl: parse("API_URL", {
type: "string",
required: true,
}),

// Number with default
serverPort: parse("PORT", {
type: "number",
default: 80,
}),

// Boolean with custom resolver
isProduction: parse("NODE_ENV", {
type: "boolean",
default: false,
resolver: (value) => value === "production",
}),

// Optional string
cachePrefix: parse("CACHE_PREFIX", {
type: "string",
required: false,
}),

// Required with custom error handling
secretKey: parse("SECRET_KEY", {
type: "string",
required: true,
}),
};

Environment sources

The first argument to createEnvParser is a (key) => string | undefined lookup — point it at whichever environment your runtime exposes.

Node.js

process.env:

const parse = createEnvParser((key) => process.env[key]);

Deno

Deno.env:

const parse = createEnvParser((key) => Deno.env.get(key));

Bun

Bun exposes both process.env (Node-compatible) and Bun.env:

// Using process.env (Node.js compatible)
const parse = createEnvParser((key) => process.env[key]);

// Using Bun.env (Bun specific)
const parse = createEnvParser((key) => Bun.env[key]);

Vite

Vite exposes vars via import.meta.env:

const parse = createEnvParser((key) => import.meta.env[key]);

Next.js

Next.js inlines NEXT_PUBLIC_* variables at build time — they have to be accessed via literal process.env.NEXT_PUBLIC_* so the bundler can replace them. A small mapping keeps the parser API usable:

const envMap = {
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
// ... other env vars
} as const;

const parse = createEnvParser((key) => envMap[key as keyof typeof envMap]);

export const config = {
apiUrl: parse("NEXT_PUBLIC_API_URL", {
type: "string",
required: true,
}),
};