A convenient wrapper around undefinedenv-schemaundefined and undefinedfluent-json-schemaundefined to automatically load your schemas from external files, while offering a number of additional configuration hooks.
.env.schema file:
fluent-json-schemaundefined exposed as globals.env.config.js:
.env.schema, an actual JavaScript module.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 from fluent-json-schema are globally available in the scope of this file, the exception is enum() which can’t be a global due to its status as a reserved keyword, so it’s aliased to values().
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 .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)
undefinedfluent-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.
undefined
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.
undefinedfluent-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:
undefinedfluent-env has the following setup sequence:
loadEnvironment() runs loading your .env files.createFlags() also runs at this point, populating env.flags.createContextGetter() and createContext() run creating the context for .env.schema.loadSchema() and createSchema()` run creating the full validation schemavalidateEnvironment() validates the environment against the schemacreateEnvironment() populates process.env by default.All of those functions can be overriden.
See the full reference on configuration options and hooks.
Licensed under MIT.
We use cookies
We use cookies to analyze traffic and improve your experience. You can accept or reject analytics cookies.