So, you finally wrapped up the core features of your new app / CLI tool. Great!
On with the configuration part...
Which file format to support? How to load these files? How to load env vars and CLI options as well? How to merge everything together? How to validate options and apply default values? Should Typescript be supported? Etc...
Chances are you have deadlines, so configuration might not be your top priority. As a consequence, you only have a limited time to address this. Even though you can find awesome libraries to help you implement each piece independently, you still need to figure out all the plumbing and handle every edge case. This could quickly become painful and time-consuming.
If this rings a bell, then you might be interested in Fauda!
It's an all-in-one library that:
- loads options from multiple sources: env vars, CLI options, and configuration files.
- merges them together in one unified configuration object.
- normalizes it by validating against a JSON schema and setting default values.
It offers the following advantages:
- Simple - a single dependency to load, merge, and validate your configuration.
- Flexible - multiple file formats support out of the box such as JSON, YAML, JavaScript, and even Typescript!
- Reliable - a unique source of truth defined in a JSON schema.
- Typescript friendly - generated typings for your code and configuration files (bonus: auto-completion in VSCode). Take a look at https://github.com/ngryman/fauda for more info. Any feedback would be very much appreciated!
Getting Started
Let's assume you want to configure a server application with the following options:
-
port
: The port the server listens to. -
open
: Open in a browser tab if true. -
mode
: Mode of the app. -
publicPages
: A list of public pages.
Install Fauda
npm install fauda
Set up your JSON schema
Fauda uses a JSON schema to load and normalize your configuration.
Create a schema.json file:
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "My awesome app configuration",
"type": "object",
"properties": {
"$schema": {
"description": "Path to my app's schema.",
"type": "string"
},
"port": {
"description": "The port the server listens to.",
"type": "number",
"default": 3000
},
"open": {
"description": "Open in a browser tab if true.",
"type": "boolean",
"default": false
},
"mode": {
"description": "Mode of the app.",
"type": "string",
"enum": ["development", "production"],
"default": "${NODE_ENV}"
},
"publicPages": {
"description": "A list of public pages.",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["publicPages"]
}
For more information on JSON schemas, you can take a look at their Getting Started guide.
Generate types (optional)
Generating types allows you to have a strongly typed configuration object in your code. As a bonus, it also enables autocompletion for Typescript configuration files!
Generate a src/configuration.ts
file:
$ npx fauda types
This will generate the following file:
export interface Configuration {
port?: number
open?: boolean
mode?: 'development' | 'production'
publicPages: string[]
}
For more information about generating types, please take a look at the README's CLI section.
Load & validate your configuration
Assuming your package's name is my-app
:
import { fauda } from 'fauda'
import { Configuration } from './configuration'
async function loadConfiguration() {
try {
const configuration = await fauda<Configuration>('my-app')
} catch (err) {
console.error(err.message)
}
}
That's all folks! 🎉
What's next?
You can take a look at Fauda's README, it's still in early development so any feedback will be much appreciated!
Top comments (7)
This looks like a well-written tool, but I would avoid using it over the concern that it adds too many levels of indirection in my code.
Say I am writing a command line tool. If I'm doing this in Javascript, I usually use yargs. Either I would have to duplicate the contents of my Fauda config definition JSON schema, or I would have to write code which mapped over the JSON schema object to programmatically configure my yargs parser.
Also the fact that configuration can come from so many different sources -- environment variables, command line arguments, and configuration files -- would make it really difficult to debug a server application that used Fauda to manage its configuration. When I SSH into the server or exec into the container running my application, I like to be able to do a simple
to see what configuration the application is running with.
Just some feedback based on previous times I've been bitten by similar tools (dotenv in Javascript land, Viper in Go land).
I will say that the generation of typescript types from Fauda configuration is a really nice idea.
Thanks for the feedback Neeraj!
I think this is perfectly fine to stick with
yargs
and manage configuration manually when it makes sense.One of Fauda's objectives is to help tools targeting a broad audience with diverse needs and technical backgrounds.
From my experience writing internal tooling at my company, as the audience grows, so do the use cases. Folks come up with various ways of integrating a tool in their workflow. Depending on their tech stack, personal preferences, and technical constraints, they tend to need more than one configuration source simultaneously.
Fauda helps to support all of these use cases out of the box, without spending too much time figuring out the specifics.
I would be interested to understand your use case better:
Fauda already maps CLI options based on your JSON schema. For instance, a
mode
option would be mapped to the--mode
CLI option. So, in theory, you shouldn't needyargs
to parse these options. Are there other reasons you would needyargs
?That's a good point. I think this is inherent to having multiple sources of configuration. Possibly Fauda could come up with a solution. I would need to think about it a bit more, but I think a debug mode that displays the currently loaded configuration information could help.
For instance, it could look like something like this:
The
*
would indicate the currently used source.Let me know your thoughts about this and if you think it could help debugging.
I haven't used
yargs
in a long time simply because our backend code is written in Python and Go has become my language of choice for one-off CLI tools. From that point of view, take whatever I say with a grain of salt because my language choices mean I am not a Fauda user by default.That said, I have spent a lot of time in my life giving devops support to development teams that developed entirely in javascript. These are the experiences that made me wary of libraries like
dotenv
.Even in javascript, i really dislike
yargs
- it just so happens that that's the library that I'm (unfortunately) most familiar with. Would be happy to switch to something like Fauda.Your idea of a debug mode is fantastic. Some suggestions of different flavors:
It is valuable to know how a given invocation of
my-app
is configured, complete with values from the command line. Maybe a command likefauda inspect <my-app invocation taken from output of ps command>
?If a config value came from a file, would be nice to know the file path.
If a config value came from an environment variable, would be nice to know the env var name.
If a config value came from the CLI, maybe a snippet of the invocation with the argument highlighted?
To build sympathy for this use case, imagine you know almost nothing about the application. You are trying to debug some error message from your production logs by shelling into the server/container running that application. Once you are in, you want to know as quickly as possible:
how the application was invoked
what parameters it is using
If the application is using Fauda and I can use Fauda to get this information easily, I would forever recommend it to all JS developers I know.
Thanks for the feedback!
I like the idea of having a
fauda inspect
. It could be a non-intrusive way to debug an already running Fauda-configured app.I believe this could end up in unpredictable results though. The configuration file or the environment variables might have changed since the app has started, and
fauda inspect
could return values that don't reflect the startup app's configuration.I think this could work if we enabled some communication between a Fauda-configured app and
fauda inspect
(e.g. socket). However, this would quickly add complexity to the current implementation. 😅Do you see any other approaches that could allow
fauda inspect
to inspect an already running app?You know, I need to retract my previous statement about Viper (github.com/spf13/viper). Used it this weekend to build a command line tool in Go, and I found it to be quite useful.
This is the tool: github.com/bugout-dev/bugout-go
When someone installs that tool, they get access to a
bugout
binary. They can initialize a config file usingbugout state init
. The approach I've taken to inspecting state isbugout state current
. This shows the config file path as well as the current config key-value pairs.It's nowhere near as comprehensive as what Fauda allows you to do, but it suggests a different approach than a live Fauda socket (which, as you rightly pointed out, is way too much complexity). Instead, you can just write (atomically) Fauda state into a tempfile and read the most recent state from that file on
fauda inspect
.Interesting!
Why did you choose
Fauda
as the name?I really like the word's sound, and I knew that Fauda means "chaos" in Hebrew and Arabic. I found it interesting to use for a project that tries to "sort out chaos" by loading your configuration from various sources.