A convenient wrapper around env-schema
and fluent-json-schema
to automatically load your schemas from external files, while offering a number of additional configuration hooks.
- Allows you to set your environment schema in a
.env.schema
file:- Parsed as JavaScript and ran in an isolated V8 context.
- All methods from
fluent-json-schema
exposed as globals.
- Alternatively, allows you set your environment schema via
env.config.js
:- Unlike
.env.schema
, an actual JavaScript module. - Spports importing subset of variables from another file.
- Allows use your environment schema as a package
- Unlike
Use your favorite package manager:
pnpm add fluent-env
bun install fluent-env
npm i fluent-env
yarn add fluent-env
-
Create a file named
.env.schema
with your environment schema. All methods fromfluent-json-schema
are globally available in the scope of this file, the exception isenum()
which can't be a global due to its status as a reserved keyword, so it's aliased tovalues()
.NODE_ENV=values(['production', 'development', 'test']) APPLICATION_ENV=values(['production', 'development', 'staging']) POSTGRES_HOST=string().required() POSTGRES_PORT=number().default(5432) POSTGRES_DB=string().required() POSTGRES_USER=string().required() POSTGRES_PASS=string().required() REDIS_HOST=string().default('localhost') REDIS_PORT=number().default(6379) REDIS_PASS=string()
If you want to use a different validation library, the global scope of
.env.schema
can be configured by providing your owncreateContext()
hook. In that case you'll also need to override the defaultvalidateEnvironment()
definition. -
Create a file named
.env
with your environment variable values:NODE_ENV=production APPLICATION_ENV=staging POSTGRES_HOST=localhost POSTGRES_PORT=5432 POSTGRES_DB=database POSTGRES_USER=user POSTGRES_PASS=password REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASS=redispassword
-
In your Node.js application, import
fluent-env/auto
from a file at the same level as your.env
and.env.schema
files. The order of loading for.env
and its variants follows the Vite standard.import 'fluent-env/auto' console.log(process.env.NODE_ENV) console.log(process.env.APPLICATION_ENV) console.log(process.env.POSTGRES_HOST) console.log(process.env.POSTGRES_PORT) console.log(process.env.POSTGRES_DB) console.log(process.env.POSTGRES_USER) console.log(process.env.POSTGRES_PASS) console.log(process.env.REDIS_HOST) console.log(process.env.REDIS_PORT) console.log(process.env.REDIS_PASS)
fluent-env
can be automatically initialized if you import fluent-env/auto
, as demonstratedd in the tutorial above. In that case, the root
will be resolved to the path that contains a package.json
file, the root of the current package. If you import fluent-env/auto
from a subdirectory, it will traverse the file tree upwards looking for the directory that contains env.config.js
or at the very least package.json
to determine what the root path is. When looking for .env.schema
, .env
and other variants, fluent-env
will traverse the file tree upwards until it can find these files, starting from the root
path.
fluent-env
will also detect when it being imported by another CLI, such asvitest
, and will consider the parent directory of the firstnode_modules
directory found in the path as the root.
If you want to use a different root for those files, you can import the setup()
function from fluent-env
(rather than fluent-env/auto
) and call it with a custom root
option:
import { fileURLToPath } from 'node:url'
import { join, dirname } from 'node:path'
import { setup as setupEnvironment } from 'fluent-env'
setupEnvironment({
root: join(dirname(fileURLToPath(import.meta.url)), 'custom/.env/location')
})
Note that all .env
file variants, .env.schema
and env.config.js
are all loaded from this same root path.
fluent-env
can be completely customized via the env.config.js
configuration file. It will either detect its presence when you import fluent-env/auto
, or load it from the path defined in the root
property passed to the setup()
method's parameters object as demonstrated in the previous example.
If you are using a .env.schema
file and don't need any customizations, you don't need env.config.js
. However, if you need to have multiple packages consume the same environment schema and a shared .env
file, they can be helpful. They also allow to customize how the schema is created and validated, in case you want to use anything other than fluent-json-schema
.
Every named export from env.config.js
is considered to be an environment variable property definition:
import { S } from 'fluent-json-schema'
export const NODE_ENV = S.values(['production', 'development', 'test'])
export const APPLICATION_ENV = S.values(['production', 'development', 'staging'])
export const POSTGRES_HOST = S.string().required()
export const POSTGRES_PORT = S.number().default(5432)
export const POSTGRES_DB = S.string().required()
export const POSTGRES_USER = S.string().required()
export const POSTGRES_PASS = S.string().required()
export const REDIS_HOST = S.string().default('localhost')
export const REDIS_PORT = S.number().default(6379)
export const REDIS_PASS = S.string()
Unlike
.env.schema
, when exporting your schema fromenv.config.js
you are working with a full blown JavaScript module, so a small amount of boilerplate code like importingfluent-json-schema
manually and exporting consts is needed in this case.
Which you can then import and export from another env.config.js
file:
export {
POSTGRES_HOST,
POSTGRES_PORT,
POSTGRES_DB,
POSTGRES_USER,
POSTGRES_PASS,
} from 'your-main-app/env.config.js'
You can use a different validation library in your .env.schema
file by customizing how the schema is created and used for validation in env.config.js
.
By providing your own createSchema()
and validateEnvironment()
hooks, you can use any other validation library, and by providing your own createContext()
hook, you can also inject different globals into .env.schema
(which by default receives a set of global aliases to fluent-json-schema
's typing functions).
Below is an example of a configuration file to use zod
instead:
fluent-env
has the following setup sequence:
loadEnvironment()
runs loading your.env
files.createFlags
() also runs at this point, populatingenv.flags
.createContextGetter()
andcreateContext()
run creating the context for.env.schema
.loadSchema()
and createSchema()` run creating the full validation schemavalidateEnvironment()
validates the environment against the schemacreateEnvironment()
populatesprocess.env
by default.
All of those functions can be overriden.
See the full reference on configuration options and hooks.
Licensed under MIT.