Back to browse
GitHub Repository

Type-safe config for TypeScript. Read typed values from any source, .env files, process.env, Workers bindings, browser bundles, or any object you bind. Zero runtime deps, one API across Node, Bun, Deno, Workers, and the browser.

12 starsTypeScript

envapt, typed env config for decoupled TS codebases (Node to edge)

by materwelon·Jul 8, 2026·1 point·0 comments

Post Description

Hi HN. I'm Dhruv and I've been working on envapt for about a year now and I've FINALLY completed the roadmap I scope creeped and QA'd over the past few months. It reads environment config in TypeScript and returns the typed value instead of string | undefined, from whatever source you bind. It runs on Node, Bun, Deno, Cloudflare Workers, the browser, and well, anywhere.

Most typed-env libraries have you declare every variable in one central schema and read the result from one object. That works well for a single application. Mostly. That didn't work for me because in a framework or a monorepo, decoupled packages each read their own config values, and sharing one config object across every package just doesn't make sense to me, plus some other nits.

envapt does the opposite. You bind a source once at startup, and on Node/Deno/Bun it binds your .env files and process.env for you, with cascading profiles per environment. After that, ANY typed read in ANY file uses that source, and each value is validated at the place that reads it. All you gotta do is import the reader from 'envapt'.

Say different files each need their own config. Each one just reads what it needs.

// db.ts import { Envapter, Converters } from 'envapt'; export const dbUrl = Envapter.getRequired('DATABASE_URL', Converters.Url); export const poolSize = Envapter.getNumber('POOL_SIZE', 10); // getRequired throws if the var is missing. fail fast!

// http.ts (your api file with CORS) import { Envapter, Converters } from 'envapt'; export const allowedOrigins = Envapter.getUsing( 'ALLOWED_ORIGINS', Converters.array({ of: Converters.Url }), [] ); // 'a.com,b.com' -> URL[]

No config object is passed between them. Each reads from the source you bound once.

Built-in converters cover the usual primitives plus JSON, URL, RegExp, Date, duration, port, email, and typed arrays. A fallback removes undefined from the return type so you don't need to do stuff like value ?? defaultValue everywhere. You can also provide your own converter or validator as a custom function.

Oh. It also has both TC39 and legacy decorators: @EnvTime('TTL', '1h') static accessor ttl. Fully type checked at compile time.

If you already validate with zod, valibot, or arktype, hand that schema to envapt and it runs the value through it (if it exists).

import { z } from 'zod'; import { Envapter } from 'envapt'; const logLevel = Envapter.parse( 'LOG_LEVEL', z.enum(['debug', 'info', 'warn', 'error']), 'info' ); // 'debug' | 'info' | 'warn' | 'error'

envapt depends on the Standard Schema interface, so any conformant validator works and none is added to your lockfile. Aaaand it has zero runtime and zero peer dependencies.

Oh, also. env values can reference each other and resolve at read time, like DATABASE_URL=postgres://${DB_HOST}/${DB_NAME}. A reference cycle is caught and left as text so it doesn't crash your app.

The last four majors, mainly v8, came out of me using envapt in my own projects and finding more to offload to it. seedcord, a Discord bot framework I maintain, builds bots that run on a gateway server and on Cloudflare Workers. On the edge there is no filesystem and no process.env, and before v8 that split needed a different import per runtime and some manual wiring. Now it is one import. The package exports resolve the file build on Node/Bun/Deno and the portable build on Workers, edge, and the browser. seedcord binds the Worker env as the source in its build step, so a user writes their config once and the same code reads .env files in dev and the injected Worker env in production, with no per-runtime branch.

Anyhow, it is on npm <https://www.npmjs.com/package/envapt> and JSR <https://jsr.io/@materwelon/envapt>. Docs <https://envapt.materwelon.dev> (It can do a lot more).

Pls try :)!!!

Similar Projects

Developer Tools●●Solid

Sideffect – Effect like syntax for Cloudflare Workflows

Vite plugin auto-generates Wrangler config from TypeScript AST — clever DX win.

Niche GemBig Brain
Sreenington
3019d ago
Security●●Solid

U-Boot Fw_env.config Bruteforcer

CRC-aware MTD scanner for U-Boot firmware config—real embedded security tooling.

Niche GemSolve My Problem
bootbloopers
104mo ago
Developer Tools●●Solid

Env-rx – Catch missing .env variables before they break your CI

Interactive autofix plus a --ci flag is the practical combo here: run locally to prompt-and-append missing keys, or fail your CI when examples diverge. It’s not reinventing the wheel, but the single-file, zero-config approach and explicit CI mode make it a useful small tool to stop one of those 'works on my machine' headaches—just watch out for secrets and how you auto-append placeholder values.

Solve My ProblemShip ItNiche Gem
xserhio
344mo ago