env-var-manager
v3.2.0
Published
A tiny helper tool that lets you define a set of environment variables including validation rules
Maintainers
Readme
env-var-manager 🌐
Define, validate, transform, and read environment variables with one small TypeScript-friendly helper.
Installation
npm install env-var-managerUsage
import { createEnvVarManager } from "env-var-manager";
const envVarManager = createEnvVarManager({
API_BASE_URL: {
retrieve: () => process.env.API_BASE_URL,
transform: (value) => new URL(value),
},
PORT: {
retrieve: () => process.env.PORT,
transform: (value) => Number.parseInt(value, 10),
validate: (value) => Number.isInteger(value) && value > 0,
invalidValueErrorMsg: "PORT must be a positive integer.",
},
});
envVarManager.validateAll();
const apiBaseUrl = envVarManager.getEnvVar("API_BASE_URL");
const port = envVarManager.getEnvVar("PORT");validateAll() checks the whole config and throws a summarized error by default. getEnvVar() reads one variable lazily, caches successful transforms, and throws by default when the value is missing or invalid.
If you want to define the config against an explicit string union and get missing-key enforcement, use defineEnvVarConfig():
import { createEnvVarManager, defineEnvVarConfig } from "env-var-manager";
type RequiredEnvVars = "APP_ENV" | "SERVER_URL";
const envVarConfig = defineEnvVarConfig<RequiredEnvVars>()({
APP_ENV: {
retrieve: () => process.env.APP_ENV,
transform: (value) => value as "development" | "production",
},
SERVER_URL: {
retrieve: () => process.env.SERVER_URL,
transform: (value) => new URL(value),
},
});
const envVarManager = createEnvVarManager(envVarConfig);That helper is the recommended way to get both:
- exact key enforcement for a declared union
- good editor autocomplete while writing the config object
If you want to handle failures yourself, pass false as the second argument and handle undefined:
const optionalPort = envVarManager.getEnvVar("PORT", false);Optional variables can be declared explicitly:
const envVarManager = createEnvVarManager({
OPTIONAL_TOKEN: {
retrieve: () => process.env.OPTIONAL_TOKEN,
transform: (value) => value,
optional: true,
},
});Default values are supported and participate in validation:
const envVarManager = createEnvVarManager({
PORT: {
retrieve: () => process.env.PORT,
transform: (value) => Number.parseInt(value, 10),
defaultValue: 3000,
validate: (value) => value > 0,
},
});Sensitive variables can be redacted from validation output:
const envVarManager = createEnvVarManager({
API_TOKEN: {
retrieve: () => process.env.API_TOKEN,
transform: (value) => value,
sensitive: true,
},
});When sensitive: true is set, validation reports and wrapped transform errors redact the value and error details.
Validation can also depend on other env vars through the validation context:
const envVarManager = createEnvVarManager(
defineEnvVarConfig<"APP_ENV" | "SERVER_URL">()({
APP_ENV: {
retrieve: () => process.env.APP_ENV,
transform: (value) => value as "development" | "production",
},
SERVER_URL: {
retrieve: () => process.env.SERVER_URL,
transform: (value) => new URL(value),
validate: (value, { getEnvVar }) => {
const appEnv = getEnvVar("APP_ENV");
if (appEnv === "production" && value.protocol !== "https:") {
return {
valid: false,
errorMessage:
"SERVER_URL must use https because APP_ENV is production.",
};
}
return { valid: true };
},
},
}),
);validate supports both styles:
validate: (value) => value > 0validate: (value, context) => ({
valid: value > 0,
errorMessage: value > 0 ? undefined : "PORT must be greater than zero.",
})Validation error message precedence is:
validatereturns{ valid: false, errorMessage: "..." }: use that messagevalidatereturnsfalse: fall back toinvalidValueErrorMsg- neither provides a message: use the default fallback text in the report
That makes it possible to keep simple validators simple, while still supporting dynamic, context-aware error messages for more complex setups.
Group Constraints
Some setups need relationships between multiple variables, not just per-variable validation.
createEnvVarManager() accepts an optional second argument with groups for that:
import { createEnvVarManager, defineEnvVarConfig } from "env-var-manager";
const envVarConfig = defineEnvVarConfig<
"API_TOKEN" | "API_TOKEN_FILE" | "AUTH_HEADER"
>()({
API_TOKEN: {
retrieve: () => process.env.API_TOKEN,
transform: (value) => value,
optional: true,
sensitive: true,
},
API_TOKEN_FILE: {
retrieve: () => process.env.API_TOKEN_FILE,
transform: (value) => value,
optional: true,
},
AUTH_HEADER: {
retrieve: () => process.env.AUTH_HEADER,
transform: (value) => value,
optional: true,
},
});
const envVarManager = createEnvVarManager(envVarConfig, {
groups: [
{
name: "token-input",
mode: "exactly-one",
members: ["API_TOKEN", "API_TOKEN_FILE"],
errorMessage:
"Choose exactly one token source: API_TOKEN or API_TOKEN_FILE.",
},
{
name: "auth-material",
mode: "at-least-one",
members: ["API_TOKEN_FILE", "AUTH_HEADER"],
},
],
});Supported modes:
at-least-one: one or more members must be definedexactly-one: one member must be defined, but not multiple
Important semantics:
- group membership is typed from your config keys, so editor autocomplete works and unknown keys are rejected
- one variable can appear in multiple groups
- group checks are about effective presence, not validator success
- retrieved values, transformed values, and
defaultValueall count as defined for group checks - missing optional variables do not count as defined
- transform failures still count as defined, so
exactly-onegroups still catch over-specified setups
When group validation fails, validateAll(false, false) returns an invalidGroups array:
const result = envVarManager.validateAll(false, false);
console.log(result.invalidGroups);
// [
// {
// name: "token-input",
// mode: "exactly-one",
// members: ["API_TOKEN", "API_TOKEN_FILE"],
// definedMembers: ["API_TOKEN", "API_TOKEN_FILE"],
// reason: "Choose exactly one token source: API_TOKEN or API_TOKEN_FILE.",
// },
// ]That same information is also included in the thrown validation report when you call validateAll() with the default throwError = true.
You can also clear the cache explicitly:
envVarManager.resetCache();Development
This repository uses npm, not Yarn or pnpm.
Useful commands:
npm test
npm run test:types
npm run build
npm run verifyCommits are expected to use conventional commit messages. Local raw git commit is blocked by Husky on purpose; use the interactive helper instead:
npm run commitThat helper is intentionally a small local script instead of commitizen to avoid extra transitive maintenance and vulnerability surface.
Release Workflow
Releases are automated with semantic-release in GitHub Actions.
- CI runs
npm ciandnpm run verify. - Releases run on pushes to
mainormaster, plus manual workflow dispatch. - The workflow is designed for protected branches and does not rely on pushing version or changelog commits back to git.
- Git tags, npm releases, and GitHub Releases are the release source of truth.
- Because of that, the
package.jsonversion committed onmaincan lag behind the latest published version. - The release workflow is set up for npm Trusted Publishing via GitHub Actions OIDC. No long-lived
NPM_TOKENshould be required once the npm package is configured for trusted publishing on the npm side.
You can preview the release flow locally with:
npm run release:dry-runThe latest published version on npm is expected to be tagged in git as vX.Y.Z. If historical tags are missing, add the tag at the commit that actually shipped instead of relying on the current branch tip.
