envalid v4

Last week I tagged version 4.0.0 of envalid, a Node library for environment variable validation that helps you avoid several environment variable antipatterns. For those who have been using it (and may be wary of a major version bump) I wanted to do a quick write-up of the notable additions. The backwards-compatible changes are small, but there is some great stuff in the latest version!

Calling envalid "my" library is no longer accurate, as the last few months have brought contributions from several others. In particular @SimenB contributed most of the new code that culminated in this latest major release đź‘Ź.

What's not new

I've never really described envalid's features in detail here, but briefly, here are some of the things it handles for you out of the box:

  • Allows you to declaratively describe the environment your app expects to run in
  • Provides several useful env var validators (number, json, email, etc) so that the plain strings you get from process.env are converted to a useful format centrally, rather than all across your app
  • It's super easy to define custom validators if the built-in ones don't cover all the shapes of your data
  • Wraps the awesome dotenv package, so you can drop your development/testing default values in a .env file instead of hardcoding them somewhere.
  • Handy shorthands env.isDev, env.isProduction, and env.isTest to replace the if (process.env.NODE_ENV === 'production') checks that tend to get strewn about.

New in v4

Two nice little additions are the use of prettier for code formatting and new host and port validators. In my experience, hosts and ports are extremely common env vars so it's nice to have some extra validation baked in for these.

The major (and only breaking) change is to envalid's "strict mode", which gives you some extra guarantees about your validated environment object. In v4, strict mode will throw if you try to set, or even access, an invalid property on your environment object. If the mistake was a simple typo, envalid will helpfully suggest a related property that you may have meant.

The following (contrived) example shows how this helps in practice. Note how well it works when destructuring your env imports– the errors happen at startup time, rather than later on during the execution of your server route.

// Oops! I forgot a 'T' in there.
// But in v4.x envalid will throw instead of returning `undefined`
const { STRIPE_SECRE_KEY } = require('path/to/my/env/object')

app.get('/customer/info', (req, res) => {
    const stripe = require('stripe')(STRIPE_SECRE_KEY)

    stripe.customers.retrieve(req.user.customerId)
        .then(customer => res.json(customer))
})

The future

It feels like envalid is "mostly done", but I probably would have said the same thing three months ago, before all of the features above landed. It's exciting to see adoption pick up (currently just a shade under 10k monthly downloads) and lots of great community feedback and contributions. Here are a few random ideas I've thought of that might be useful in the future:

  • A custom eslint rule to warn when accessing process.env directly – this would in effect force you to access the cleaned env object that you get from envalid.
  • Built-in support for designating env vars that are used client-side, and returning a separate validated object that you can import from your webpack config.
  • Bindings for Reason

If you have any other ideas, please drop by the envalid issue tracker!