back

by jtwaleson·3y ago·view on hn ↗
Is it just me or does anyone else hate .env files? They accumulate unused cruft, it's not typed, and typically many systems in the same repo point at the same .env file. It all feels so ... implicit ... I guess.

For example in a code base I work in, docker-compose uses it, laravel picks up the same file and Vue / webpack does too. It's a big mess. There must be better solutions out there.

12 comments
> it's not typed

It kind of is, environment variables can only be strings, so why "type" it when you 100% know that everything within is 100% a String, always?

It’d be useful to have actual booleans and nuns.

Strings are fine for keys and verbatim configs, but suck for flags/toggles/options.

But you can't set a boolean or number to the variable. Environment variables are always strings.
Doesn't seem very hard to work around. You just need to format values.

* "[String|any old content]" * "[Boolean|true]" * "[Number|1]"

There’s no guarantee one is set right?
There’s also no guarantee the application will be able to reach a database, bind to a port, or read something off of a file system.
You can use validator like populate-env[1] to check for missing env variables

[1] https://www.npmjs.com/package/populate-env

A type system doesn't really solve that issue tho.
A "runtime type system" does, meaning something like io-ts or zod.

It also allows statically typing and transforming env strings into much more useful and complex structures.

No more so than that any other kind of config file is present and complete, I suppose.
I don't know that this is the best way, but I use a module that instantiates a proxy object. It populates the config object behind the proxy at startup and crashes with a useful message if a value is missing. If something tries to get an undefined value at runtime, the proxy also throws an error. This way the module documents the config, and the app crashes if something is missing rather than silently trying to run with an undefined value. You can also use object.freeze or what have you to make the config object immutable for most practical purposes.
I don't know if that's the best way either, but it's the model all the most successful engineering cultures I've been part of have used, which certainly says a lot for it in my view at least.
Typescript types them to be string | undefined.
I personally prefer to stick to config files in whatever format is native to ecosystem (toml, yaml, ini). Env files fill the same role for apps which are configured by env variables, but you don't really need to use env variables there. It was forced by Docker some time ago as a single supported configuration format, but nowadays k8s happily accepts configs and mounts them as a file, so why jump the hoops with .env -> vars -> app?
I personally don't feel the same, I have no idea why you'd want a typed environment variable setup. And I find they work great.

I don't see any problem with 4 different things using it.

The point of it is basic, rather then VARIABLE=WHATEVER you just copy and paste a .env and it picks it up.

But you can also just use regular environment variables if you want.

> I have no idea why you'd want a typed environment variable setup.

Because you want to know what type your variables are?

Of course, in this case you do know, they're strings.

You can trivially write a schema file and a method that checks the existence and type of the env var on startup. I actually did on some projects. It's not needed most of the time so I could only see this as an npm module.
Manually writing reliable file IO, serialisation and schema validation on every project is a colossal waste of time effort and energy collectively.

I for one am glad that with 2 lines of code I get type safe environment variables with config overrides in go, and never have to think about it ever again.

Colossal waste?

- have a .json with format [{ name: AUTH_TOKEN, type: String }] (you can add extras like regex match etc)

- have a method that goes through all items on startup and checks process.env existence plus the type.

- use this method as a getter method based on name

- search replace all other process.env usage and disable it via linting so that no one can get around the restriction

voila.

Oooor use these libs from other people who had the same idea (I just found them as well)

https://www.npmjs.com/package/envalid

https://www.npmjs.com/package/env-vars-validator

I like them for defining a small number of env vars for my app that only get injected at run time. I don't like them when other random tools that aren't my app use them. Docker's insistence on automatically using the .env in your home directory is absurd.
The reason why we continue to use those is that they are easy to load in bash (source the file), can be hidden away in the env in production, don't have inheritance, and aren't yaml.

I feel you on the typing, but the format could accomodate such a tool. Code away! :)

Environment variables are all strings. That’s not .env files’ faults, it’s how env vars work.

If you don’t want/need to read env vars… why use .env? If you do need to, then you’re stuck treating them as string input, sure, but that’s got nothing to do with .env files. They just help set env vars. If you get rid of your .env file but are still reading from env vars, all you’ve done is tie one hand behind your back.

Not perfect, but you can use a validation library to explicitly pull in ENV variables and they will be typed. https://sergiodxa.com/articles/using-zod-to-safely-read-env-...
You can create a class or something that reads it in at application startup and throws up if something you expect isn't there. You can't really enforce a rule to not access `process.env` directly, but it at leasts gives you types to access the env should you choose to use the wrapper.
I have utilized this to decent effect: https://github.com/eslint-community/eslint-plugin-n/blob/mas... and then just had agreement/dictator state that the environment loader module was the only place in code that was allowed to disable that rule.
yeah I don't like environment variables because it's all stringly typed and the lack of a thing you deserialize the config file into means that practically speaking, you can read the values from anywhere in a program; figuring out what's permittable in environment variables is very often a disorganized mess. .env files take all of the messiness of environment variables and then ... put them into a file, which is typically the thing that people who are using environment variables are trying to avoid, so ... I dunno, just use a configuration file imho, .env files are the worst of both worlds.
You can easily have both .env and types. On any TypeScript project you can just pass your process.env through an io-ts, zod or similar library and keep it in a side-effectful module which validates on first import and keeps it globally available.
Wait until these folks realize all the dozens of applications and daemons their program needs to build & run are all taking lots and lots of string input and performing various tests and transformations on it to ensure correctness and do type conversion. It’s totally normal and happens constantly any time you’re using a computer.

Is this attitude/phobia a consequence of folks not starting their programmer journey with simple command line programs, anymore?

I've used env-smart[1] for typed .env files before. It works OK.

[1] https://www.npmjs.com/package/env-smart

This is what Dhall is designed to solve. If I was on a big team I’d be looking closely at it https://dhall-lang.org/
The "Can you spot the mistake?" thing just instantly killed any interest I had in that presentation. It's the job of that page to sell me on it, not to try and make me do homework.
> Is it just me or does anyone else hate .env files? They accumulate unused cruft, it's not typed

Try cuelang.org