Node.js 20.6.0 will include built-in support for .env files
twitter.com
twitter.com
Everyone should read the PR https://github.com/nodejs/node/pull/48890. It doesn't traverse parent directories, it doesn't have good overwrite/merge logic, multiline support, or variable expansion - things that dotenv has supported for years (sometimes with add-ons). This is another half-useful implementation that the majority will end up ignoring, and keep using better-suited third party packages for.
Node is committee'd to death, and the comments in that PR are further proof. I've been in the Node ecosystem since 2010, and my money is on Bun and Deno to lead the way forward.
Easy things should be easy, and hard things should be possible through npm install.
Maybe it could traverse up to where it finds a package.json actually.
For me, Deno seems very well thought out, I’ve used it a fair bit and found it to work reliably and fast. I like the whitelist approach to system access although I worry that it could be a hurdle for adoption. I haven’t used Bun, just read about it. My sense is that Bun is more of a 1:1 replacement for Node, still tied to npm as a first class citizen, while Deno has a long term strategy to get away from npm (while supporting it as a legacy thing). Overall I’d prefer Deno to win, but I wonder if Bun has a better chance due to its closer parity with Node.
I'm not disagreeing necessarily, but wow, I really hope it doesn't come to that. The timeline where languages, runtimes (and by extension libraries, frameworks, and even tools) are chosen based on how well they're supported by LLM tooling sounds horrifying.
It would block adoption of better designed languages, tools and frameworks. If evolution is going to be stifled by LLMs, we'll miss out on paradigm shifts like JS Promises and ES6.
Tooling concerns are already are a major source of friction for adoption, nothing really special about LLMs in that regard.
It’s early still but my sense is that Bun will gradually gain ground as a Node replacement, but Deno will gain ground as something else. I think that’s actually the original intent behind both projects, too. Bun wants to be a better Node, Deno wanted to diverge in order to avoid what the author considers fundamental mistakes in Node’s design.
So there is chance it won't be massive security hole enabled by default! Great!
> it doesn't have good overwrite/merge logic, multiline support, or variable expansion - things that dotenv has supported for years (sometimes with add-ons)
99% of what I used env files for was just a bunch of key-values for db passwords so their minimal implementation is probably good enough for most. Then again, that's experience from other than node ecosystems, and if config requires "multiline support, or variable expansion" we either make proper config file or a template for CM to deploy.
True that. Sometimes committees lead to bloat. And sometimes committees lead to not adding magic to everything that ends up being abused by malicious actors. I love that deno and bun exist and are getting rapidly developed. By I also love the Node has been and continues to be a rock for the industry.
So another mark in the “why have this feature in the first place?” column. The whole concept is for dev ergonomics, but this one’s making that worse for security reasons? Ugh. Just leave it out entirely, then.
Sounds good to me. If I need more one day I’ll use dotenv, but having basic support in the stdlib is great
The primary missing feature here seems to be multiline support. That's super common for keys, certificates, and other JSON configuration. Based on the Node PR, they appear open to adding that later (big +1 on shipping incrementally).
The other missing feature that folks tend to heavily rely on is variable expansion. For that to truly work well, I recommend using a holistic platform like Doppler. That allows for expansion/referencing across environments and projects, like when you have multiple independent services that need access to the same set of secrets (e.g. database creds, error reporting tool, stripe key, etc). You can then update the secret once and have the change propagate to all the places it's used.
Lastly, I'd be remiss if I didn't mention the Doppler CLI and our own fairly unique support for .env files. We've traditionally taken a dim view of .env files because they represent a static, long-lived collection of sensitive information that lives offline. Often, these get checked into a git repo. This is probably fine for personal projects, but a major issue for companies and the security aware. However, .env files are a pseudo standard and folks want a way of continuing to consume their secrets via them. Our CLI's approach is to mount a named pipe that we can write secrets to when a reader attaches. That allows us to limit the amount of times the "file" can be read (e.g. once), it guarantees that the file's contents are unavailable once the application process dies, and it uses the same open/read interface as a standard file.
In all, this is an exciting development for Node. I'm glad to see more standard features make it into core and hope that multiline support is a fast follow.
Easy enough to just Base64 encode the value the way Kubernetes does
There's rudimentary HTTP server as well (no TLS and such though). All in C++. Should be fairly fast, based on expertise of the Chromium team.
FOO="abc
BAR=123"
Is this one or two vars? What is the value of FOO? export TWILIO_SID="123123123123"
export TWILIO_SECRET="123123123123"
And then `source .env` in my terminal, then `rails server` or whatever to run my apps. The environment variables will be present to be consumed from within the app.Curious what features you use beyond this.
There’s plenty of outstanding questions.
Is the export required?
Can variables reference other variables?
Can variables reference existing variables in the environment?
Can you use other forms of expressions?
Can you set overridable defaults for values?
So, yes it's parsed and a valid use case.
Example, where we have:
env_type="dev"
env_name="myappenv"
primary_subnet_pod="${env_type}-${env_name}-gke-pod-subnet"
primary_subnet_service="${env_type}-${env_name}-gke-service-subnet"
Which I then might want to use further down, override with defaults, etc and so forth. It strikes a decent balance in that it's a bit more powerful than a static .env file, but doesn't require you to go full-on templating language with Jinja and the like. It's also cross-platform enough in that you could throw a .env in somewhere and expect most languages to have a client library to parse and load it into env.
If your use-case is right in that sweet spot it's a pretty good tool. But the behavior, as noted, isn't specced so I can't trust go_dotenv or rust_dotenv or whatever other library to treat it the same way.
Interestingly, in practice, it seems like the Node lib is the canonical version that inspired these other ones so in a way it serves as some sort of unofficial spec. It's not something I would write production grade code trusting the unofficial spec on though.
Historically this has been my approach, but in production environments it’s convenient to have a static list of variables with no “export” instead since eg systemd’s EnvironmentFile only supports that.
So the spec is the same as regular environment variables?
Well there is but that’s a much deeper concept in Linux, than what most people get exposed to, in the execve family of functions, where it is defined in C as an array of strings with the key separated from value only by =.
Unless you refer to how to do it in shell. That wouldn’t be applicable to a configuration language. Shell has a lot richer syntax and more complex logic, allowing variable templating and even running processes inside a string definition using $(). All the quoting rules around that must be different.
If you’re not ultimately going to be reading actual env vars, then yeah—why even consider .env in the first place? Of course don’t use it.
I would argue that most of the time, you don't need to do this in the first place. Far too often people shoehorn things into environment variables that really shouldn't live there, like secrets and app configuration.
If you're interfacing with existing software that reads from the environment, obviously you don't have control over that, so using a different format is out of the question (unless you're willing to convert it to env vars at runtime, but that sounds like more effort than it's worth).
Shipping this specific thing with nodejs seems deeply pointless to me.
Plus, having my program auto-monkey with its own environment, even optionally, feels… wrong. I should define it (or something should on my behalf) and the program should accept what it gets. Else, why even use env vars? Just use a config toml or some shit. But that part reasonable people could disagree over.
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.
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.
Because you want to know what type your variables are?
Of course, in this case you do know, they're strings.
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.
- 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)
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 also allows statically typing and transforming env strings into much more useful and complex structures.
Strings are fine for keys and verbatim configs, but suck for flags/toggles/options.
* "[String|any old content]" * "[Boolean|true]" * "[Number|1]"
I feel you on the typing, but the format could accomodate such a tool. Code away! :)
Is this attitude/phobia a consequence of folks not starting their programmer journey with simple command line programs, anymore?
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.
Try cuelang.org
I pretentiously claim that 90% of people using Kubernetes should write efficient algorithms, db requests and maaaaybe look at a CDN and/or buy a bigger server. Like, are your servers really that busy? Stack overflow is 9 servers.... I can testify of some very large, compute intensive service being run on just a couple of servers with the right tech and less abstraction :)
* most people are pre-building their packages. That means you can’t really modify anything at run time. It also means it can be extremely tedious to test changes and wait for the build process.
* there’s a string of places you have to properly “expose” ENV variables to a layer deeper down. This includes some build condos to make sure you’re not exposing sensitive BE configs. It can be challenging debugging the intermediate steps.
Backend and frontend can be separate runtimes... The backend could be an API-only-server and an OS process with all permissions that come with it (reading env vars during execution as it pleases). The frontend, eg. a React SPA, could just be a bunch of built HTML/CSS/JS-files (type-checked, bundled, minified, etc.), served through a static file server (separate backend, nginx?) and interpreted through a browser engine on the client.
Are you asking for nginx to parse the served HTML/CSS/JS-files and replace unknown placeholders with env var values? If so you are prob asking for a SSR framework[0], meaning a backend that treats a React project similar to template files, and is able to inject env vars?
[0]: Next.js - https://nextjs.org/docs/app/building-your-application/config...
[1] https://create-react-app.dev/docs/adding-custom-environment-...
We needed to set env vars on React applications (the frontend) inside Docker containers. The containers were going to be built in CI/CD, and we didn't want different builds for each environment.
We solved this by adding a small JS script that is included in the base HTML. It pushes env vars into the window object, so they're accessible from window.env.
The JS file is generated by a Bash script that's ran whenever the container starts (by modifying the entry-point.sh file used by Nginx's Docker image).
The Bash script loads enumerates any environment variable with a given prefix.
Docker already allows you to set runtime environment variables from the run command, or via Docker Compose.
Im the end, the developer experience comes down to "docker run image:tag -e PREFIX_FOO=BAR".
Shdotenv helps to convert/reuse environment files across tools
IMO it would be much better if that was the default and dependencies had to ask to run scripts (perhaps with a whitelist in package.json), but unfortunately node did a bunch of "helpful" mistakes and now it's hard to roll back without breaking.
Maybe if they had denos permission model they could isolate so that the dependency could only read/write to it's own directory within node_modules.