Starting the week with incredible news and one of the most awaited new features of Node.js in years, the native support for the use of environment files, the so-called dotenvs or env-config.
dotenv
One of the most common and widely used packages in the JavaScript community is the dotenv package. It does something super simple, but at the same time essential: it loads environment variables into the runtime memory.
Environment variables are special variables that carry protected data that cannot be exposed in the code and are only available in the environment in which they are inserted, for example, a VM or a serverless function.
I've written a lot about them in this article - including using the old way of loading variables, with dotenv
, so it's nice to have a look at how Node has evolved since then - but, essentially, environment variables are the ideal place to store database users and passwords, or even token secrets, since, in order to access and fetch this data, the application needs to have access to the same machine and the same host, which is much more difficult than having access only to the code.
These variables are stored in files we call dotenv
, or .env
, which are files defined in INI
or bash
format depending on the runtime you are using (for dotenv it would be INI), so an environment file could look like this:
PASSWORD=123
USER=123
However, we had always needed a module to be able to load these variables, until Deno arrived.
Deno
Deno has greatly changed the way runtimes look at these files because it allows you to load them directly from your standard library. In other words, instead of having to install a separate module, we can use what is native to the runtime:
// app.ts
import { load } from "https://deno.land/std@0.203.0/dotenv/mod.ts";
console.log(await load({export: true})); // { GREETING: "hello world" }
console.log(Deno.env.get("GREETING")); // hello world
Node 20
Node 20.6 brings a change that goes beyond what Deno has already done, now it's possible to read .env files directly from the runtime.
Instead of having to do:
require('dotenv').config()
const {DB_HOST, DB_PORT, DB_USER, DB_PASS} = process.env
const db = require('some-database-lib')
db.connect({
host: DB_HOST,
port: DB_PORT,
user: DB_USER,
pass: DB_PASS
})
We can simply use the command node --env-file=<path> index.js
and all the variables present in our file defined in the path will be loaded into our process.env
. For example, imagine a .env
file as follows:
DB_PASS=pass
DB_USER=root
DB_PORT=5432
DB_HOST=localhost
When we run our Node with the command node --env-file=.env index.js
, we can use the variable process.env.DB_PASS
, and it will have the value of pass
, just like the other variables that will be perfectly set in our application for use.
But that's not the only new feature.
Node options
Node.js allows you to create an environment variable called NODE_OPTIONS
, it allows you to set all the options that would be passed to node in the main command directly in the variable, that is, if we want, for example, to start Node with an inspector (using the --inspect
flag) we can use: NODE_OPTIONS='--inspect'
and now all executions of node index.js
will be as if they were written node --inspect index.js
.
There used to be a way to leave these settings in package.json
, but envs support changes everything, so now you can leave the Node options (except, of course, the --env-file
itself) in an environment variable:
NODE_OPTIONS='--inspect --loader=tsx'
As I showed in my article on TSX, we can load TypeScript files natively using a loader, and on top of that we can load our environment file directly, with the command above we can run node --env-file=.env index.ts
, for example.
Conclusion
This feature is huge! While it removes a package from our list of dependencies, it still allows us to make our code simpler and more straightforward but remember that it is only available from version 20.6 on.