The pii.js convention
Quick and easy config file for your small JS project.
Whenever I need to create a small JavaScript app (like a CRUD web app or a Discord bot), the most annoying thing to deal with is specifying the program’s configuration.
Usually, one would specify the configuration one of these ways:
- Command-line flags: These are pretty barebones, and from what I understand, runtime-dependent. Node (which is what I use) just hands you a
progress.argvarray and lets you deal with it. You need to deal with parsing the arguments, dealing with edge cases, raising errors when needed. Ugh. - Environment variables: This is actually nicer than I realized—not only are environment variables conveniently keyed into
process.env, but Node 20.6.0 added support for reading.envfiles from the command line, and 20.12.0 added support for doing it in-process so you don’t even need to set the process’s actual “environment variables”, it just populates theprocess.envobject based on a file. But this can only contain bare strings, not structured data, and you still have to check for the existence of every variable you want and raise an error if it’s not there. - JSON/TOML/YAML/INI/other config file: This lets you have structured data! But you’re now responsible for reading the file, parsing it, making sure all the fields are there and the right type, and so on.
So command-line flags just suck, and the other two have compromises. But what if you could have it all? First-class support with structured and typed data, no dependencies, no parsing, no error-handling?
Enter the pii.js.
Start by adding the following line to your gitignore:
*.pii.js
Now you can define your configuration as a file, say, config.pii.js, that might look like this if you’re using CommonJS modules:
module.exports = {
discord: {
token: '<your Discord bot token>',
client_id: '<your Discord bot account client ID>',
},
port: 3001,
};
or like this if you’re using ES modules:
export default {
discord: {
token: '<your Discord bot token>',
client_id: '<your Discord bot account client ID>',
},
port: 3001,
};
and then in your code, you can refer to the configuration as:
// CommonJS
const { discord, port } = require('./config.pii');
// ES
import { discord, port } from './config.pii.js';
// both
await client.login(discord.token);
await app.listen({ port, host: '::' });
And yeah, that’s it! You can import structured and typed configuration data, you don’t need to do any file reading and parsing and error handling, and there’s zero dependencies for you to get a minor heart attack about every other Sunday.
Also, while doing the barest amount of research for this article to make sure I didn’t make any unsubstantiated claims, like “you can’t just import JSON files despite it literally being the JavaScript Object Notation,” I figured out that you can in fact just import JSON files in the same way!
// config.pii.json
{
"discord": {
"token": "<your Discord bot token>",
"client_id": "<your Discord bot account client ID>"
},
"port": 3001
}
// main.js
import { discord, port } from './config.pii.json';
await client.login(discord.token);
await app.listen({ port, host: '::' });
I find JSON slightly more annoying to write because you have to quote the keys and you can’t have trailing commas or comments, but if you prefer JSON, go wild with that as well!
I didn’t think much of this little trick until I showed it to blackle and it said that the trick is worth a small blog post. So, a generous thank you to our sponsors at Suricrasia Online for making this post possible!