# env-var: typed environment variables that fail at boot

> env-var turns process.env into a chainable accessor API for Node.js and web applications, with required checks, type coercion and error messages that name the variable. It has zero dependencies and about 4.7 kB minified, but its newest npm release is from 2024 while the repository has commits into 2026.

**evanshortiss/env-var** — Verification, sanitization, and type coercion for environment variables in Node.js

- Repository: https://github.com/evanshortiss/env-var
- Stars: 625 · Forks: 37
- Language: JavaScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/evanshortiss-env-var

## Configuration read once, with a type attached

env-var is described as verification, sanitization, and type coercion for environment variables in Node.js and web applications, with TypeScript support. The design bet is that environment variables should be read once, at start up, through an accessor that both validates and types the value, rather than scattered through the codebase as `process.env.SOMETHING` lookups that are always strings and always possibly undefined.

The feature list gives the size argument. It is lightweight, with zero dependencies and about 4.7 kB minified, it fails fast if your environment is misconfigured, it produces friendly error messages with example values for better debugging, and it supports TypeScript for compile time safety. It also supports frontend projects such as React, React Native and Angular.

The API is a builder chain, and the chain is the documentation. Each step constrains the value in some way, and the final accessor is the one that gives you a typed result.

```js
const PASSWORD = env.get('DB_PASSWORD')
  // Throws an error if the DB_PASSWORD variable is not set (optional)
  .required()
  // Decode DB_PASSWORD from base64 to a utf8 string (optional)
  .convertFromBase64()
  // Call asString (or other APIs) to get the variable value (required)
  .asString();
```

The last step is required, which is what stops you from ending the chain with an unconstrained string.

## Defaults, ranges and the errors you get

The same chain shape covers defaults and range checks, and the README shows both.

```js
// Read in a port (checks that PORT is in the range 0 to 65535)
// Alternatively, use a default value of 5432 if PORT is not defined
const PORT = env.get('PORT').default('5432').asPortNumber()
```

In TypeScript the same idea is expressed with the type annotation and a stricter accessor.

```ts
const PORT: number = env.get('PORT').required().asIntPositive();
```

The README comment is explicit about the failure mode. An EnvVarError will be thrown if the variable is not set, or if it is not a positive integer. That is the fail fast behaviour, and it is the reason to prefer this over reading process.env directly: a missing or malformed variable stops the process at start up with a message naming the variable, rather than producing undefined deep inside a request handler.

The set of accessors is wider than the examples show. The API document lists many more, including asFloatPositive(), asJson() and asRegExp(), and it also documents the extraAccessors mechanism for writing your own custom accessor. The README points at API.md for the full set and at EXAMPLE.md, with a summary of the examples in the /example directory.

The repository ships that example directory with separate files for the common cases: catch-error.js and catch-error-promise.js for handling the error, custom-accessor.js and custom-accessor-2.ts, logging.js, and typescript.ts.

## from() for build tools that hide process.env

Frontend builds break the usual assumption. When you use environment variables in a web application, your tooling such as vite imposes special conventions and does not expose process.env, so the standard accessor has nothing to read.

The from function is the workaround, and you build an env object by handing it the variables you want to expose.

```ts
import { from } from 'env-var'

const env = from({
  BASE_URL: import.meta.env.BASE_URL,
  VITE_CUSTOM_VARIABLE: import.meta.env.CUSTOM_VARIABLE
})
```

The shape is the same as on the server: you get an object with get, and the chain of required, default and the typed accessors works the same way. The difference is only in where the values come from. On the server they come from the process environment, in the browser they come from whatever the bundler injected.

That symmetry is the useful part. A codebase that has a config module used by both the API and the web bundle can be written once against env-var, with the frontend side constructing the object from import.meta.env and the server side constructing it from process.env. The validation and coercion rules then hold in both places, which is exactly the class of bug that otherwise only shows up in one of the two environments.

## Logging is off by default and stays off in production

Logging environment variables is a bad idea in general, and env-var treats it that way. Logging is disabled by default in env-var to prevent accidental logging of secrets, and you have to create an instance with the from() function and pass in a logger to turn it on.

The built-in logger has its own guard.

```js
const { from, logger } =  require('env-var')
const env = from(process.env, {}, logger)

const API_KEY = env.get('API_KEY').required().asString()
```

It prints logs only when NODE_ENV is not set to either prod or production. So the default posture is quiet in the environment you care about, and noisy while you develop.

A custom logger is also supported, and the README gives the reason to want one: filtering env-var logs based on log levels, such as trace logging only, or using your own preferred logger. The pino library is named as an example, and the custom logging section of EXAMPLE.md covers it.

The README adds a caution that is worth repeating in your own review process. Always exercise caution when logging environment variables. A logger that prints every variable it validates will print every secret your application uses, and the built-in logger's production guard is a convenience rather than a guarantee, since NODE_ENV is set by whoever deploys you.

## dotenv is optional and deliberately not coupled

The README covers an optional integration with dotenv, and the reasoning is about package size rather than features. There is no coupling between dotenv and env-var, but you can easily use them together, and that loose coupling reduces package bloat and allows you to start or stop using one without being forced to do the same for the other.

That is a small paragraph with a clear architectural statement in it. env-var does not load .env files. It reads the environment it is given. If you want files on disk read into the environment, that is dotenv's job, and it does it before your application code runs.

The result is a division of responsibility that most teams will find comfortable. dotenv owns the file format and the loading rules, env-var owns the validation and the typing, and neither one knows about the other. The alternative, a library that loads files and validates them together, would tie your configuration format to your validation library, and you would have to change both together if you wanted to move to a real secret manager.

The dotenv section of EXAMPLE.md covers the mechanics of using them side by side.

## The build enforces a hundred percent coverage gate

The package.json is more revealing about how the project is maintained than the README is, because it contains the test script with its gates spelled out.

```json
    "coverage": "nyc mocha test/*.js && nyc report --reporter=lcov",
    "check-coverage": "nyc check-coverage --statements 100 --branches 100 --functions 100 --lines 100",
    "unit": "mocha test/*.js",
    "test": "npm run unit && npm run coverage && npm run check-coverage && npm run lint && npm run ts-verify",
    "ts-verify": "tsc && mocha test/types/*.js"
```

Every statement, branch, function and line must be covered at 100 percent, the lint step runs the standard style tool over the examples, the library and the tests, and the TypeScript verification compiles with tsc and then runs the type tests. The full test command chains all of it: unit, coverage, the coverage gate, lint, then the type verification.

There is also a husky pre-commit hook that runs lint and the unit tests before a commit lands, so the same checks happen before review as after it.

The toolchain itself is dated in a way worth noticing. The development dependencies pin mocha at version 8, chai at 4.2, nyc at 15, standard at 14.3, typescript at 3.9 and husky at 4, and the package declares engines node of 10 or newer. The declared minimum is old, and the type checking runs on TypeScript 3.9, so the typing support is written against a much older compiler than most projects use today.

## Versions: npm says 2024, the tree says 2026

The published version is 7.5.0, and the release history shows 7.4.1 on 2023-08-28, 7.4.2 on 2024-05-09 and 7.5.0 on 2024-05-20. The last push to the repository was on 2026-09-01.

So the tree has moved well past the version on npm, and the repository is not archived. The package metadata itself still reads version 7.5.0, which is the sort of lag that happens when the working tree carries fixes and features that have not been cut into a release.

For a library of this size, that is a low risk in one direction and an annoyance in another. The API surface is a chain of accessors over a handful of environment variables, so drift is unlikely to break you. But if you are pinning a version for reproducibility, you should decide whether you want the published 7.5.0 or a specific commit, and the difference between them is not documented anywhere in the repository, since the CHANGELOG.md is the only history and it tracks releases.

The licence is MIT, with the terms in the LICENSE file at the repository root, and the package publishes three things: the lib/ directory, env-var.js as the main entry point, and env-var.d.ts as the typings. There is no homepage set, and the bugs URL points at the GitHub issue tracker.

## Conclusion

env-var suits a Node.js or TypeScript service that wants configuration read once at start up and validated rather than read inline and hoped for, and it suits a frontend build that needs a typed bridge to import.meta.env. It does not suit a codebase that wants per-request re-reading of the environment or a secrets manager integration, since the library reads process.env and logs nothing by default. Verify first which version is on npm against the repository, because the latest published release is 7.5.0 from 2024 while the last push was on 2026-09-01, and decide deliberately whether to enable the built-in logger, which stays quiet only while NODE_ENV is not prod or production.

## FAQ

### How do I install env-var?

The README documents npm with npm install env-var and yarn with yarn add env-var. The package has zero runtime dependencies and is about 4.7 kB minified.

### What happens if a required environment variable is missing?

The chain fails fast. Calling required() makes the accessor throw an EnvVarError if the variable is not set, and accessors such as asIntPositive() also throw when the value is not a positive integer.

### How do I use env-var with Vite or another frontend build tool?

Because tooling such as vite does not expose process.env, you use the from function to build an env object from the injected values, for example from({ BASE_URL: import.meta.env.BASE_URL }). The rest of the accessor chain works the same way.

### Does env-var log my environment variables?

No, logging is disabled by default to prevent accidental logging of secrets. You enable it by creating an instance with from() and passing a logger, and the built-in logger prints only when NODE_ENV is not set to prod or production.

### What licence is env-var released under?

The package metadata records the MIT licence, and the terms are in the LICENSE file at the root of the repository.

## Sources

- [evanshortiss/env-var on GitHub](https://github.com/evanshortiss/env-var)
- [Issues](https://github.com/evanshortiss/env-var/issues)
- [License: MIT](https://github.com/evanshortiss/env-var/blob/master/LICENSE)
- [README](https://github.com/evanshortiss/env-var/blob/master/README.md)
- [Releases](https://github.com/evanshortiss/env-var/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/evanshortiss-env-var
