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.schemafile:- Parsed as JavaScript and ran in an isolated V8 context.
- All methods from
fluent-json-schemaexposed 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-envbun install fluent-envnpm i fluent-env yarn add fluent-env-
Create a file named
.env.schemawith your environment schema. All methods fromfluent-json-schemaare 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.schemacan be configured by providing your owncreateContext()hook. In that case you'll also need to override the defaultvalidateEnvironment()definition. -
Create a file named
.envwith 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/autofrom a file at the same level as your.envand.env.schemafiles. The order of loading for.envand 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-envwill also detect when it being imported by another CLI, such asvitest, and will consider the parent directory of the firstnode_modulesdirectory 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.jsyou are working with a full blown JavaScript module, so a small amount of boilerplate code like importingfluent-json-schemamanually 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.envfiles.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.envby default.
All of those functions can be overriden.
See the full reference on configuration options and hooks.
Licensed under MIT.