typed-dotenv v10.0.2
typed-dotenv
⨠Inspired by dotenv and dotenv-extended.
The Twelve-factor App suggests to store the config in the environment. This is an excellent idea, BUT there is ONE big shortcoming.
š¤·āāļø Environment variables must be string. Not every config is string however.
To read a typed environment variable (e.g. number or boolean), you need to convert the value to the corresponding type in your code manually. This greatly impacts the code readability and is error-prone.
š« UNDESIRED
const { parsed: env } = require('dotenv').config();
const timeout = parseInt(env.TIMEOUT) || 10;
if (timeout > 0) {
// do something...
}ā DESIRED: The variables are in the correct data types, and possibly assigned with the default value at the first place
const { env } = require('typed-dotenv').config();
if (env.timeout > 0) {
// do something...
}Table of Contents
What does typed-dotenv do?
typed-dotenv reads a template file, then manipulate the environment variables according to the template.
In the template, you can...
specify the data type(s) for each variable. typed-dotenv will convert the environment variables into the target type(s) accordingly.
specify whether a variable is required or optional. typed-dotenv will throw an error if a required variable is missing.
- specify the default values for the optional variables. typed-dotenv will apply the default values if the optional variables are missing.
Check out the .env.template file for more details.
šš¼ Moreover, you can configure typed-dotenv to rename and nest the environment variables.
// Default output
{
"MYSQL__DATABASE": "my_db",
"MYSQL__POOL_SIZE": 5,
// ...
}
// Renamed and nested output
{
"mysql": {
"database": "my_db",
"poolSize": 5,
// ...
}
}Installation
npm i --save typed-dotenv
# OR
yarn add typed-dotenvUsage
Prepare the
.env.templateat the project root.# EXAMPLE ## # @required {string} SECRET= ## # @optional {number} = 30 TIMEOUT= ## # @optional {boolean} = false DEBUG= ## # @optional {json} = {"status":404} NOT_FOUND= ## # @optional {string[]} = foo,bar LIST=Copy the
.env.templateas.env, and fill in at least the required variables.SECRET=ThisIsTopSecretImport and configure
typed-dotenvas early as possible in your application.const { error, env } = require('typed-dotenv').config(); if (error) { // Handle the error } // Now you can refer to the `env` object for the environment variables.ā ļø If you want to use the
importsyntax (i.e. ES Module), see the FAQ below.
IMPORTANT NOTES
process.envwill be modified to include the loaded variables, BUT the assigned values are the RAW STRING values, becauseprocess.envaccepts string only.If a variable has already been set in
process.env, it WILL be overwritten UNLESSincludeProcessEnvis set tofalse.
The .env.template File
The .env.template file shares the same syntax as the .env. The only difference is that each variable has a special
comment block - the annotation block.
An annotation block starts with the ## line, and follows by the lines that start with #.
##
# This is an annotation block.
# You can have some description for your variable.
# @optional {boolean} isDebug = false
DEBUG=Every annotation starts with the @ symbol, follows by a keyword. The remaining part is specific for each
annotation.
Currently, only 2 annotations are supported by this library - @required and @optional.
@required
The parser will throw an error if the required variable is missing in .env.
Format: @required {TYPES} CUSTOM_NAME
# EXAMPLES
# @required {string}
# @required {number|string}
# @required {number|string} customNameTYPES
The list of types separated by |. If multiple types are specified, the parser will follow the same sequence to
convert the variable value. If the value fails to convert into any of the defined types, an error will be thrown.
The following types are supported:
string, string[], number, number[], boolean, boolean[], json
CUSTOM_NAME (optional)
If renaming is enabled, the parser will respect the custom name to rename the variable.
For example, MYSQL__POOL_SIZE will rename to mysql.poolSize by default. You can force it to rename as dbPoolSize
by providing the custom name.
@optional
The parser will assign the default value (if given) or null to the variable if the variable is missing in .env.
Format: @optional {TYPES} CUSTOM_NAME = DEFAULT_VALUE
# EXAMPLES
# @optional {string}
# @optional {number|string}
# @optional {number|string} = 10
# @optional {number|string} customName
# @optional {number|string} customName = 10TYPES
Please refer to the TYPES under the @required annotation.
CUSTOM_NAME (optional)
Please refer to the CUSTOM_NAME under the @required annotation.
= DEFAULT_VALUE (optional)
The default value to be assigned if the variable is missing in .env. The value must match one of specified types.
API Reference
š” config(options)
config will read your .env and .env.template to compose the environment variables.
const { error, env } = require('typed-dotenv').config({
debug: false,
path: '.env',
encoding: 'utf8',
errorOnFileNotFound: false,
unknownVariables: 'keep',
assignToProcessEnv: true,
includeProcessEnv: true,
template: {
debug: false,
path: '.env.template',
encoding: 'utf8',
errorOnFileNotFound: false,
errorOnMissingAnnotation: false,
},
rename: {
enabled: false,
caseStyle: 'camelCase',
nestingDelimiter: '__',
},
});options.debug (default: false)
Set to true to print the debug logs.
options.path (default: path.resolve(process.cwd(), '.env'))
The path to your .env file.
options.encoding (default: 'utf8')
The encoding of your .env file.
options.errorOnFileNotFound (default: false)
Set to true to throw an error when the .env file does not exist.
options.unknownVariables (default: 'keep')
The behaviour to handle a variable if it does not exist in .env.template but is found in .env.
'keep': Simply keep the variable in the loaded variables.'remove': Remove the variable from the loaded variables.'error': Return an error with the list of unknown variables.
options.assignToProcessEnv (default: true)
Set to true to assign the loaded variables to process.env.
The RAW STRING values, instead of the converted values, are assigned to
process.env, becauseprocess.envaccepts string only.If a variable has already been set in
process.env, it WILL be overwritten UNLESSincludeProcessEnvis set tofalse.
options.includeProcessEnv (default: true)
Set to true to include process.env to load the variables. The variables in process.env overrides the variables in
.env.
If assignToProcessEnv is also set to true, process.env will be overwritten by the resulted variables.
options.template.path (default: path.resolve(process.cwd(), '.env.template'))
The path to your .env.template file.
options.template.encoding (default: 'utf8')
The encoding of your .env.template file.
options.template.errorOnFileNotFound (default: false)
Set to true to throw an error when the .env.template file does not exist.
options.template.errorOnMissingAnnotation (default: false)
Set to true to throw an error when any of the variables is not annotated properly.
options.rename.enabled (default: (see description))
When rename option is defined, rename.enabled defaults to be true, unless specified explicitly.
// Will NOT rename the variables by default.
const { env } = config();
// Will rename the variables using the default rename options.
const { env } = config({ rename: {} });
// Will NOT rename the varibles because the option is disabled explicitly.
const { env } = config({
rename: {
enabled: false,
// ...
},
});options.rename.caseStyle (default: 'camelCase')
The case style for renaming the variables.
'camelCase': Use lodash'scamelCasefunction to rename the variables.'snake_case': Use lodash'ssnakeCasefunction to rename the variables.null: Do not change the case style.
options.rename.nestingDelimiter (default: '__')
The delimiter for splitting the variable name into the nested structure.
For example, the default __ delimiter will convert MYSQL__USER__NAME=my_user into
{ "mysql": { "user": { "name": "my_user" } } }š” compose(dotenvObj, templateObj, options)
You can construct your own dotenv object and template object to compose the variables.
const dotenvObj = {
DEBUG: 'true',
};
const templateObj = {
DEBUG: {
required: false,
types: ['boolean'],
name: 'isDebug',
rawDefaultValue: 'false',
defaultValue: false,
},
};
const options = {
unknownVariables: 'keep',
rename: {
enabled: false,
caseStyle: 'camelCase',
nestingDelimiter: '__',
},
};
const { rawEnv, convertedEnv, env } = require('typed-dotenv').compose(dotenvObj, templateObj, options);
// rawEnv = { DEBUG: 'true' }
// convertedEnv = { DEBUG: true }
// env = (options.rename.enabled) ? { isDebug: true } : { DEBUG: true }options
Please refer to the corresponding options under the config function.
š” template.config(options)
Load the dotenv template.
const { error, parsed } = require('typed-dotenv').template.config({
debug: false,
path: '.env.template',
encoding: 'utf8',
errorOnFileNotFound: false,
errorOnMissingAnnotation: false,
});options
Please refer to options.template of the config function.
š” template.parse(src, options)
Parse the dotenv template.
const src = `
##
# @optional {boolean} = false
DEBUG=
`;
const options = {
debug: false,
errorOnMissingAnnotation: false,
};
const templateObj = require('typed-dotenv').template.parse(src, options);src: string | Buffer
The template source in string or buffer.
options
Please refer to the corresponding options under options.template of the config function.
FAQ
Should I commit my .env and .env.template files?
.env
No, you should NOT commit .env to the version control system. The file is meant to be environment-dependent, and
usually contains sensitive information like the passwords or API keys.
.env.template
Yes, the .env.template file is meant to be shared, so other people can follow the template to setup their own
environments easily.
How do I use typed-dotenv with import?
When you run a module containing an import declaration, the modules it imports are loaded first, then each module body is executed in a depth-first traversal of the dependency graph, avoiding cycles by skipping anything already executed.
The following code WON'T load the environment variables for the foo module because config() runs AFTER importing
the foo module.
import * as typedDotenv from 'typed-dotenv';
import foo from './foo';
typedDotenv.config();You could solve the problem by either preloading typed-dotenv (ts-node --require dotenv/dist/config index.ts) or
importing typed-dotenv/dist/config instead of typed-dotenv.
ā ļø There are 2 disadvantages with the above solutions however.
process.envcontains only the raw string values, instead of the converted values, of the variables. This goes against the purpose of using this library - type conversion.You cannot customise the configuration.
ā Therefore, the following solution is the most encouraged (for CommonJS as well).
/* * * * * * * * * *
* 1. Setup `config.ts`
* * * * * * * * * */
import * as typedDotenv from 'typed-dotenv';
const { error, env } = typedDotenv.config({
// ...options
});
if (error) {
// ...
}
export const config = env;
/* * * * * * * * * *
* 2. In your module (e.g. `foo.ts`), import `config.ts`
* * * * * * * * * */
import { config } from './config';
// Logic of your module.
/* * * * * * * * * *
* 3. In your app's entry point (e.g. `index.ts`), import `config.ts` as soon as possible.
* * * * * * * * * */
import './config';
import foo from './foo';Other questions?
Feel free to raise an issue :)