env variables with node, express and typescript

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

Popular posts from this blog

quick note on scala.js, react hooks, monix, auth

zio environment and modules pattern: zio, scala.js, react, query management

user experience, scala.js, cats-effect, IO