How to sanely configure your application
Tags: webdev, architecture
Published: 2022-04-17 11:28:17

There is an awkward friction in frameworks that offer to start your application, like `rails serve` and `next start`, because they swallow up your application so they're in charge of it, rather than you owning it. Today I'll focus on one specific area where this friction can get very visible: Configuring your application.

I've seen my share of applications that have not put enough thought into their configuration, and the result was a mishmash of using the framework's conventions and reading environment variables in random places. In several cases the values weren't validated at all, causing obscure runtime errors if the environment was not configured exactly right. In this context new-joiners have **no way** to understand what configuration options exists or what they really do other than searching for them in the code. It's brittle madness.

This article is a highly opinionated **example** of how I like to take ownership of the application startup process, but I'd very much like to hear how you prefer doing it because I'm sure there are great alternative designs out there that are well worth considering.

_(as long as it doesn't involve reading environment variables all over the place!)_

Just to be clear, when I say "application" I mean anything from long-running services that handles customer requests, to tools and utilities used in pipelines and batch-processing contexts.

And what is it I'm selling? What I suggest is this: Our applications should work like any other application, and the simplest, most tried-and-true approach for almost all applications is: The **Command Line Interface** (CLI). The decades old, evergreen practice of passing arguments to a program using the terminal. Our applications are no different than `ls` or `dir` or any of the commands and programs we use every day, so why should the applications we write behave any differently? CLIs are the easiest and most intuitive interfaces because we all know how to use them and they offer great discovery by letting new developers explore each possible argument.

What exactly do I mean with **CLI**? At the very least it means being able to pass `--help` to understand how to use the application. If we're making the **Cool Awesome Hats** web-store service then what is simpler than running `coolawesomehats --help` and have it explain how to get started?

```
$ cah —help
Usage: cah [-dph]

cah starts the Cool Awesome Hats web store, letting customers buy cool & awesome hats.

-d —db-connection-string		Connection string for a Postgres database
-p —port						Port to serve on, default: 8080
-h —help						Show this screen
```

With this approach developers can intuitively learn how to start the app, and there are no mysterious, unknowable environment variables to stumble across. Configuration is specified in one **and only one** place: The CLI.

This implies a CLI parsing module whose only responsibility is to read and validate argument inputs and instantiate some sort of application configuration object, which can then be passed into the application itself. In Typescript it could work like this:

```
interface Configuration {
    dbConnectionString: string;
    port: number;
}

function parseCli(argv: string[]): Promise<Configuration> {
  // Parse & validate argv
}

function startService(conf: Configuration): Promise<void> {
  // Start the server and connect to DB
}

parseCli(process.argv)
  .then(startService)
  .catch((err) => {
    console.error(err)
    process.exit(1)
})

// Or if you're allowed top-level awaits:
// await startService(await parseCli(process.argv))
```

This way there is a simple, explicit boundary between configuration and the service, and the CLI parsing module is easily unit-tested which gets increasingly important when dealing with more and more options.

Of course this was just a simple example and real-world applications may have many configurable parts. Thankfully CLIs scale well, and are very flexible in terms of the inputs they can be made to accept. I recommend the CLI approach as a great way to centralize and clarify configuration, and to make it easy to explore the options that are available.


_Photo by <a href="https://unsplash.com/@jontyson?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Jon Tyson</a> on <a href="https://unsplash.com/s/photos/start?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>_