npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

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

About

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

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

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

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

Open Software & Tools

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

© 2026 – Pkg Stats / Ryan Hefner

@freshpointcz/fresh-env

v0.0.2

Published

Type-safe environment loader with schema-driven validation, encoding, and coercion

Readme

@freshpointcz/fresh-env

Type-safe environment loader with schema-driven validation, encoding, and coercion for Node.js services.

Why

process.env gives you Record<string, string | undefined>. Every service ends up with ad-hoc parsing scattered across the codebase — Number(process.env.PORT), process.env.DEBUG === "true", unchecked optional keys silently producing undefined at runtime.

fresh-env solves this with a single declarative schema that handles validation, type coercion, encoding/decoding, and deprecation warnings — all at startup, before your service serves a single request.

Installation

npm install @freshpointcz/fresh-env

Requires Node.js ≥ 20. No runtime dependencies — only uses built-in fs and crypto.

Quick start

1. Define your environment type

import type { ServiceEnv, EnvSchema } from "@freshpointcz/fresh-env";

type MyServiceEnv = ServiceEnv<{
  PORT: number;
  API_URL: string;
  DEBUG: boolean;
}>;

2. Create a schema

The schema is the single source of truth — it declares every key your service expects, how to validate it, and what type to coerce it into.

const schema: EnvSchema<MyServiceEnv> = {
  PORT: {
    rule: "required",
    type: "number",
    default: 3000,
  },
  API_URL: {
    rule: "required",
    type: "string",
    description: "Base URL for the upstream API",
  },
  DEBUG: {
    rule: "optional",
    type: "boolean",
    default: false,
  },
};

3. Create the environment

import { Environment } from "@freshpointcz/fresh-env";

const service = new Environment<MyServiceEnv>(schema);
export const env = service.env;

env.PORT;    // number — typed, coerced, validated
env.API_URL; // string
env.DEBUG;   // boolean

If API_URL is missing from process.env and has no default, the constructor throws immediately with a clear error message.

Schema reference

Each key in the schema accepts the following options:

| Field | Type | Required | Description | | ------------ | ------------------------------------------ | -------- | -------------------------------------------------------- | | rule | "required" | "optional" | "deprecated" | Yes | Validation behavior (see below) | | type | "string" | "number" | "boolean" | Yes | Target type — raw string is coerced at load time | | encoding | "plain" | "base64" | "aes-256" | No | Decoding applied before coercion. Defaults to "plain" | | default | Matches the declared type | No | Fallback when the key is absent from process.env | | description| string | No | Used in deprecation warnings and documentation |

Key rules

  • required — must be present in process.env or have a default. Missing keys cause a startup error.
  • optional — may be absent. The value will be undefined if not set and no default is provided.
  • deprecated — still functional, but logs a console.warn when set, nudging operators to migrate.

Coercion

All process.env values are strings. The type field controls how they're converted:

| Type | Accepted values | Result | | ----------- | -------------------------------- | --------- | | "string" | Any string | string | | "number" | Anything Number() doesn't NaN | number | | "boolean" | "true", "1", "false", "0" | boolean |

Invalid coercion throws an error at startup.

Encoded values

For values that shouldn't sit in plaintext in .env files, the schema supports two encoding modes.

Base64 (obfuscation)

Prevents casual reading but is not encryption — anyone can decode it.

API_URL: {
  rule: "required",
  type: "string",
  encoding: "base64",
},
# .env
API_URL=aHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20=

Generate with:

import { encodeBase64 } from "@freshpointcz/fresh-env";

console.log(encodeBase64("https://api.example.com"));
// aHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20=

AES-256-CBC (encryption)

Real encryption. Values are stored as iv:ciphertext in hex. Requires a 32-byte secret key.

DB_PASSWORD: {
  rule: "required",
  type: "string",
  encoding: "aes-256",
},
# .env
DB_PASSWORD=a1b2c3d4e5f6....:d4e5f6a1b2c3....

Generate with:

import { encryptAES } from "@freshpointcz/fresh-env";
import { randomBytes } from "crypto";

// Generate a key once, store it safely
const key = randomBytes(32).toString("hex");

console.log(encryptAES("my-secret-password", key));
// a1b2c3d4...:e5f6a7b8...

Providing the secret key

The AES-256 decoder resolves the secret key in this order:

  1. ENV_SECRET_KEY environment variable (64-character hex string), but that should only be used at local testing
  2. File at ENV_SECRET_KEY_PATH (defaults to /run/secrets/env_secret_key), that is imported via docker compose secrets

The key file encoding can be configured via ENV_SECRET_KEY_ENCODING (defaults to utf8).

Redundant variable warnings

Keys in process.env that are not declared in the schema are logged as warnings at startup:

[Environment] "OLD_UNUSED_VAR" is set but not declared in schema — ignored

This helps catch typos, leftover variables, and env drift between services.

Custom source (testing)

The Environment constructor accepts an optional second argument to replace process.env:

const testEnv = new Environment<MyServiceEnv>(schema, {
  PORT: "9999",
  API_URL: "http://localhost:4000",
});

testEnv.env.PORT; // 9999 (number)

Exported types

| Type | Description | | ---------------------- | -------------------------------------------------------------- | | PrimitiveTypes | string \| number \| boolean | | CoercibleType | "string" \| "number" \| "boolean" | | BasicRecord<T> | Record<string, T \| undefined> — base constraint for env maps | | KeyRule | "required" \| "optional" \| "deprecated" | | Encoding | "plain" \| "base64" \| "aes-256" | | AppEnvironment<T, S> | Intersection of specific keys S + string index signature | | DefaultProcessEnvType| Type-compatible alias for process.env | | ServiceEnv<K> | AppEnvironment pinned to PrimitiveTypes | | EnvSchema<E> | Declarative schema mapping for a ServiceEnv |

Exported functions

| Function | Description | | -------------- | -------------------------------------------------------- | | coerce | Converts a raw string to string, number, or boolean | | decode | Decodes an encoded env value (plain, base64, aes-256) | | encodeBase64 | Encodes a value to Base64 for .env file storage | | encryptAES | Encrypts a value with AES-256-CBC for .env file storage |

Project structure

src/
├── index.ts          # Public barrel — re-exports everything consumers need
├── types.ts          # All type definitions (no runtime code)
├── coerce.ts         # String → primitive coercion
├── decode.ts         # Encoded env value → plaintext (base64, aes-256)
├── encode.ts         # Plaintext → encoded value (base64, aes-256)
├── secret-key.ts     # ENV_SECRET_KEY resolution (env var or file)
└── environment.ts    # Environment class

License

ISC