env variables with node, express and typescript
If you need to manage environment variables better for express, node and typescript web services, you have a few choices.
- Define a default set of vars using an “environments” file and fallback to those while using an optional user provided file.
- Add fallback values in .ts code.
- Don’t add fallback values and error out of require variables are missing.
The standard thinking is that if you have environment variables that hold secrets or a file of environment variables these should not be checked in at all because you should use the production method of setting environment variables e.g. a container can set environment variables using container methods. You can always setup a separate, restricted VCS repo to hold secrete information that only production admins can access. Hence, .env files should only be used for non-production, say dev and test, and not checked into VSC.
The default package to use is “dotenv”. dotenv reads a .env file at runtime and adds the contents to process.env so the values are available at runtime. I find that many variables, such as database config variables, change even in dev based on which database I might want to hit, say something on my dev box or another dev database.
To make these combinations happen, I do something like this in a src/config/env.ts file:
// load this module as early as possible
import * as fs from "fs"
import { config } from "mssql"
if (process.env.NODE_ENV !== "production") {
const result = require("dotenv").config()
if (result.error)
console.error("Failed to parse .env file", result.error)
}
else {
// if .env but in producton, provide wraning
if (fs.existsSync(".env")
console.log("A .env was found but is ignored in production.")
}
// required
const REQUIRED = ["DB", "AUXDB", "IDENTITYMETADATA", "CLIENTID"]
const NOTSET = REQUIRED.filter(e => !(typeof process.env[e] !== "undefined"))
if (NOTSET.length > 0)
throw new Error(`Required env variables: ${NOTSET.join(", ")}`)
export const DB: config = JSON.parse(process.env.DB!)
export const AUXDB: config = JSON.parse(process.env.AUXDB!)
export const ROOT: string = process.env.ROOT || "/api/v1.0"
...
In some cases I have a default, say with ROOT. In other cases, there is no default so those must be checked and an error thrown if they are missing.
My .env. I might have a .env.localhost and .env.remotedb defined as well and change the one I want based specifying the .env file directly (not shown in the code) or just copying the .env.* file I do want to .env:
ENCOREAUXDB={"user":"sa","password": "sa", "server": "localhost", "database": "db"}
ENCOREDB={"user":"sa","password": "sa", "server": "localhost", "database": "auxdb"}
CLIENTID=YYYY
IDENTITYMETADATA=https://login.microsoftonline.com/XXX/v2.0/.well-known/openid-configuration
# ...
Comments
Post a Comment