F# and DotEnv

Read Time: 3 minutes

Often the question arises, what is the best way to manage environment variables for a project. Many languages have libaries precisely for this issue. A common approach is the use of a .env file, .NET and F# are no different. In today’s post I’ll take a brief look at leveraging DotEnv in an F# application.

For those not familiar with the concept, an application’s configuration is managed through environment variables. Although great for deployment, manually setting them locally and/or passing them to other developers can be a pain. For development, management of environment variables can be easily done using a .env file. This file resides in the project root. The DotEnv library loads those environment variables into the executing process. This allows for the ability to easily share application configuration. It depends on the library implementation, but often a bonus is that comments are permitted in this file as well.

There are several packages available that meet this need, I will specifically be looking at dotenv.net.

Step one, setup a .env file. This is simply a list of <key>=<value> pairs.

1
2
3
# My custom environment variables
FOO=myfoo
BAR=mybar

Using Paket, here is the paket.dependencies file.

1
2
3
source https://nuget.org/api/v2

nuget dotenv.net

Below is sample code, along with a sample execution. Honestly, as an example there isn’t much to look at. But that is kind of the point.

1
2
3
4
5
6
7
8
9
#r "./packages/dotenv.net/lib/netstandard2.0/dotenv.net.dll"

open System
open dotenv.net

DotEnv.Config()

printfn "foo: %A" (Environment.GetEnvironmentVariable("FOO"))
printfn "bar: %A" (Environment.GetEnvironmentVariable("BAR"))

Execution Results

Including packages makes life easy, especially for quick proof-of-concepting. With that said, the basics of .env are pretty simple to implement. Below is an F# version. I’m going to pick on C# a bit too. I looked at a couple of the implemenations available through nuget. They share a common theme, they are all drastically longer than the code below. This is one of the reasons I enjoy F#, it allows me to quickly execute on ideas and not get bogged down in syntax. As an aside, one notable difference is that my version doesn’t overwrite existing environment variables. This may be personal preference, but I like the ability to override .env values by explicitly setting environment values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
open System
open System.Text.RegularExpressions

/// Load .env file
let loadDotEnv() =
let envFile = ".env"
if IO.File.Exists(envFile)
then
IO.File.ReadAllLines(envFile)
|> Array.iter (fun line ->
// Remove comments
let line' = Regex.Replace(line, "#.*", "")

// Split into <key>=<value>
let parts = line'.Split([|'='|]) |> Array.map (fun x -> x.Trim())
if Array.length parts = 2
then
let (key, value) = (parts.[0], parts.[1])

// Only add key if not already there
if String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(key))
then Environment.SetEnvironmentVariable(key, value)
else ()
else ())

loadDotEnv()

printfn "foo: %A" (Environment.GetEnvironmentVariable("FOO"))
printfn "bar: %A" (Environment.GetEnvironmentVariable("BAR"))

Execution Results

As you can see, the environment variables now populated as desired. That’s all for today. Hopefully at least some found this short post useful. Until next time…