tyfigo

Placeholders and .env

Fill config values from environment variables and .env files.

Syntax

A string value can refer to an environment variable:

PlaceholderValue
${VAR}The value of VAR. A problem when VAR is not set
${VAR:-default}The value of VAR, or default when VAR is unset or empty
${VAR:?message}The value of VAR. A problem with message when VAR is unset or empty
$${A literal ${

Placeholders are resolved in values only, never in keys.

config/app.config.yaml
http:
  port: ${PORT:-3000}
database:
  url: ${DATABASE_URL}
  password: ${DATABASE_PASSWORD:?Set DATABASE_PASSWORD in .env.local}

In TOML, JSONC and JSON5 a placeholder has to be a string, so quote it:

config/app.config.toml
[server]
port = "${PORT:-3000}"

Whole values are typed

A value that is exactly one placeholder has the same type for tsc and at run time:

  • In the base file, it has the type of its default. With PORT=8080, port: ${PORT:-3000} is the number 8080. Without a default (${PORT} or ${PORT:?message}), it is a string, so port: ${PORT} is '8080'.
  • In an environment or local file, it has the type of the base key it overrides. If app.config.yaml has port: 3000, port: ${PORT} in app.production.config.yaml is the number 8080.

The variable is read with the file's own rules for that type (true is a boolean in YAML and TOML), and a value that doesn't read as that type is a problem. A placeholder inside a longer string stays a string: url: http://localhost:${PORT} is 'http://localhost:8080'.

In the base fileTypeWith PORT=8080, DEBUG=true, NAME=api
port: ${PORT:-3000}number8080
debug: ${DEBUG:-false}booleantrue
port: ${PORT}string'8080'
name: ${NAME:?required}string'api'
url: http://localhost:${PORT}string'http://localhost:8080'

To get a number from a placeholder with no default, give it a default or validate it with a schema that coerces (z.coerce.number(), see Schemas).

.env files

Placeholders read from these files, each later one winning over the ones before it:

  1. .env
  2. .env.local
  3. .env.<environment>
  4. .env.<environment>.local

The real environment variables win over all of them. Each file is looked for next to the base config file first, then in the project root (the nearest folder with a package.json). .env files are parsed with Node's util.parseEnv.

tyfigo never writes process.env. .env files only supply values to placeholders and to loadEnv; they are never imported as modules.

Bundled servers

A server bundle built with a bundler plugin reads .env files from the working directory when it starts, not from the machine that built it. It picks its layers and .env.<env> files by NODE_ENV when it starts, unless the plugin's environment option fixed them. See Server bundles, and Browser builds for what client bundles do.

Problems

A missing variable stops the import with every problem listed:

Invalid config /srv/app/config/app.config.yaml
/srv/app/config/app.config.yaml:5:8 - Environment variable 'DATABASE_URL' is not set (missing-variable)

A value that doesn't read as the placeholder's type is a placeholder-type problem, at the placeholder:

Invalid config /srv/app/config/app.config.yaml
/srv/app/config/app.production.config.yaml:2:9 - Expected number at 'http.port', but environment variable 'PORT' is "abc" (placeholder-type)

A malformed placeholder such as ${1PORT} or an unclosed ${PORT is an invalid-placeholder problem, reported by tsc as well as at run time.

On this page