Categories
Development Tech

Loading environment variables in code: the simple way

Read on for a solution to loading environment variables from a .env file. The code is for Kotlin, but should be easy to replicate in other stacks as well. No frameworks, low complexity. 🙂


I try to not add frameworks or libraries unless they add great value. Of course I don’t want to re-invent the wheel, but there is always more complexity and limitations to the libraries than you first think. And then it is a chore to replace it.

Start small and expand. 🙂

A lot of things are really not that complex to write code for. Application configs are one of those things. Even working across Gradle and local development in your IDE.

Get a variable from a file, if it exists in the ENV; use that instead. It’s that simple. 🙂 Need something a bit fancier? Just extend the code, you can do it. 😉

So here’s the Kotlin code for a config with the code to load it. Just one key here, but you get the gist of it:

data class ApplicationConfig(
    val lookupApiKey: String
) {

    companion object {
        fun load(): ApplicationConfig {

            fun Map<String, String>.envOrLookup(key: String): String {
                return System.getenv(key) ?: this[key]!!
            }

            val envVars: Map<String, String> = envFile().let { envFile ->
                if (envFile.exists()) {
                    envFile.readLines()
                        .map { it.split("=") }
                        .filter { it.size == 2 }
                        .associate { it.first().trim() to it.last().trim() }
                } else emptyMap()
            }

            return ApplicationConfig(
                lookupApiKey = envVars.envOrLookup("LOOKUP_API_KEY")
            )
        }

    }
}
Kotlin

The config is loaded in tests and in the KTor setup by calling the load function like this:

fun Application.module() {
    configureHTTP()
    configureMonitoring()
    configureSerialization()
    configureRouting()

    // Manual dependency injection :) Usually smart to find a separate place to do this from KTor
    val config = ApplicationConfig.load()

    val mainPage = MainPage(LookupClient(config.lookupApiKey))
    val selectedPage = SelectedPage()

    // Load pages
    configurePageRoutes(mainPage, selectedPage)
}
Kotlin

As a bonus, you also see manual dependency injection. It really works. 🙂 And it really helps keeping the compiler give real feedback when something changed that needs to be updated.

You can find a complete application with this code working here: https://github.com/anderssv/kotlin-htmx/blob/ef6559473f1d246a914c98160a03bdc3644f6749/src/main/kotlin/no/mikill/kotlin_htmx/Application.kt#L16

Thoughts? Let me know.

This is just one of many things I do to avoid complexity so stay tuned and subscribe for the next thing.

Leave a Reply

Your email address will not be published. Required fields are marked *