The more I work with Node.js, the more I appreciate its built-in features. Supply chain attacks are a real thing and the more I can do without pulling in megabytes (or even kilobytes) of dependencies, the better.
For example, one of the very common patterns in Node applications are configs in .env files. The traditional way to digest them is to use dotenv, which admittedly has zero dependencies – but nowadays even that is not necessary. You can just run Node with --env-file .env, and the variables from .env will be loaded into process.env. (There is also --env-file-if-exists, which silently fails instead of throwing if the given file does not exist.) This works much like the dotenv package – if something is already in process.env, the value from the .env file is discarded, you can use comments (starting with #) in the .env file, etc. Even better, you can use --env-file more than one time, and a few .env files will be read in, with values in later files overriding values from the earlier ones. That way, you can have a general .env file and an .env.dev file with local overrides (although whether it’s a good practice is another question). You can also use this feature in a dotenv-like style where instead of a command-line option you call process.loadEnvFile at the beginning of your script. There is even util.parseEnv, which accepts a string and returns an object containing the key-value pairs resulting from its parsing.
One feature that sets this apart from dotenv is that you can use this to set variables that configure Node itself. For example, you can say
NODE_OPTIONS=--title=some-node-app
in .env, and when you run node --env-file .env your-app.js, ps
aux will show you some-node-app instead of node --env-file .env
your-app.js. As far as I know, this is something dotenv (which operates on a different level) cannot do.
This behavior has some quirks, though. Apparently parsing CLI arguments is not entirely consistent. I created this file:
console.log('process.argv:', process.argv)
console.log('NODE_OPTIONS:', process.env.NODE_OPTIONS)
console.log('SOME_OPTION:', process.env.SOME_OPTION)
setTimeout(() => {}, 10 * 1000)
and this .env fle:
SOME_OPTION=some option NODE_OPTIONS=--title=some-node-app
and said node --env-file .env test.js. Unsurprisingly, this is what I’ve got:
process.argv: [ '/usr/bin/node', '/.../test.js' ] NODE_OPTIONS: --title=some-node-app SOME_OPTION: some option
and $ pgrep -a node showed
1225363 some-node-app
So far, so good. But watch this:
$ node test.js --env-file .env process.argv: [ '/usr/bin/node', '/.../test.js', '--env-file', '.env' ] NODE_OPTIONS: undefined SOME_OPTION: undefined
So, Node treated –env-file .env as parameters to my script and not to Node itself (correctly) – but somehow still used it:
$ pgrep -a node 1226282 some-node-app
Why this happens I have no idea. I suspect it’s a weird (and hopefully harmless) bug in Node itself I plan to report.
Anyway, as you can see, you might not need dotenv anymore. In fact, thanks to this, my most recent project (which I will certainly blog about soon!) is a Node CLI tool with zero dependencies.
While at that, let me also mention another cool feature which landed in Node v25.0.0 (so it will be available in the next LTS version in a month or two). Prior to Node v25, you could console.log a regex and see this:
Since v25, the output has the regex colored:
How cool is that!?
When coding in JavaScript, I use Eslint like everybody else. (Let’s set the discussion about Eslint vs. Oxlint for another time.) One problem I have is that sometimes (rarely, but not never) I need to tell Eslint that I broke one of the rules intentionally and I don’t want it to nag me about it. The easiest way is to put a comment saying
// eslint-disable-next-line <name of the rule I broke>
directly above the offending line. Trouble is, I never remember the exact syntax of that line (is it eslint-disable-next-line? or disable-eslint-next-line, or maybe eslint-disable-line…?), not to mention the name of the rule. (Yes, it appears in the echo area. No, you can’t easily copy it from there. Well, you can say C-u C-x =, press RET on the [Show] button next to the flycheck-error property name. Or you can just head to the *Messages* buffer after seeing the rule name in the echo area and copy it from there. Either way, it’s not exactly convenient.)
I figured that having a function to add such a comment automatically would be pretty cool. Of course, I would have to dive in Flycheck internals. As usual in such cases, I decided to start with checking if such a function exists already. Also as usual, I was not disappointed! I have Tide installed, and it turns out that it defines a command tide-add-eslint-disable-next-line which does exactly what I need! One caveat is that the Eslint checker must be enabled in Flycheck for this to work.
That’s it for today, come next week for another post!
CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryJavaScript
Today I have a nice SQL tip about sampling tables in SQL. (I’m writing about PostgreSQL as usual but this applies to other databases, too, since this feature is in the SQL standard.) Imagine you have a large table and you want to get a general feeling about what its contents look like. Saying select * from my_table limit 10 may be a nice idea, but it doesn’t always suffice. For example, if you have a table where you never delete or update rows, only insert them, that query will often just show the earliest 10 rows, and you might want to see how the contents of the table changed over time.
You can say, for example,
select * from my_table tablesample bernoulli (5)
and see about 5 percent of rows from table my_table. (It’s approximate because each row is included in the result with a probability of 5%, so depending on the RNG there may be more or fewer rows in the result.) The mysterious bernoulli keyword tells Postgres that the decision whether to include a row or not must be made for each row individually; if you use system instead, these decisions are made for pages (by default, a page contains 8kB of data, so if your row contains, say, half a kilobyte of data on average, then a page may have about 16 rows – or probably 15, since there is some overhead for the page header), which is “less random” but much faster.
One more thing worth knowing is the repeatable keyword, which allows to provide a random seed. If you say
select * from my_table tablesample bernoulli (5) repeatable (1337)
you will get the exact same 5% of rows of the underlying table every time (provided that you don’t make any changes to its contents, of course).
That’s it for today, see you next time!