Usage
Create the env once, at boot
Call createAppEnv a single time and export the result. Because validation runs
when you call it, importing this module is what triggers the check — so a
misconfigured environment fails at startup, not mid-request:
import { createAppEnv, type AppEnv } from "@tikab-interactive/fusion-env";
export const env = createAppEnv(process.env);
export type { AppEnv };Everywhere else, import env and read typed values — never reach for
process.env again:
import { env } from "#/lib/env";
// env.DATABASE_URL is `string | undefined`, already validated as a URL.
const url = env.DATABASE_URL ?? "postgres://localhost:5432/app";Values are typed, not stringly-typed
Each field's type is inferred from its Zod rule, so optional vars are
string | undefined and the compiler makes you handle the undefined:
.;
.;Fail fast on bad config
A value that's present but invalid throws immediately — validation happens at the
createAppEnv call, so the process never starts in a half-broken state:
// throws: SERVER_URL is present but not a valid URL
createAppEnv({ SERVER_URL: "not-a-url" });
// fine: absent optional vars are simply `undefined`
createAppEnv({}).SERVER_URL; // => undefined
// fine: an empty string is treated as "not set"
createAppEnv({ SERVER_URL: "" }).SERVER_URL; // => undefinedServer vs client at runtime
The VITE_ prefix is the boundary: in a Vite app, VITE_-prefixed vars are the
only ones safe to read from browser code, while server vars stay on the server.
fusion-env doesn't read the environment for you — you pass it in (process.env
on the server) — so it works the same under Node, Bun, or a Vite server runtime.
See the @t3-oss/env-core docs for framework-specific
wiring of the client half.