Environment variables
Environment variables serve the purpose of storing secrets like the database password, the app secret, or an API key outside of your application codebase.
Also, environment variables can be used to have different configurations for different environments. For example, you may use a memory mailer during tests, an SMTP mailer during development, and a third-party service in production.
Since environment variables are supported by all operating systems, deployment platforms, and CI/CD pipelines, they have become a de-facto standard for storing secrets and environment-specific config.
In this guide, we will learn how to leverage environment variables inside an AdonisJS application.
Reading environment variables
Node.js natively exposes all the environment variables as an object through the process.env
global property, and you may access them as follows.
process.env.NODE_ENV
process.env.HOST
process.env.PORT
Using the AdonisJS env module
Reading environment variables via the process.env
object requires no setup on the AdonisJS side, as the Node.js runtime supports it. However, in the rest of this document, we will use the AdonisJS env module for the following reasons.
- Ability to store and parse environment variables from multiple
.env
files. - Validate environment variables as soon as the application starts.
- Have static-type safety for validated environment variables.
The env module is instantiated inside the start/env.ts
file, and you may access it elsewhere inside your application as follows.
import env from '#start/env'
env.get('NODE_ENV')
env.get('HOST')
env.get('PORT')
// Returns 3333 when PORT is undefined
env.get('PORT', 3333)
Sharing env module with Edge templates
If you want to access environment variables within edge templates, then you must share the env
module as a global variable with edge templates.
You can create view.ts
as a preload file inside the start
directory and write the following lines of code inside it.
import env from '#start/env'
import edge from 'edge.js'
edge.global('env', env)
Validating environment variables
The validation rules for environment variables are defined inside the start/env.ts
file using the Env.create
method.
The validation is performed automatically when you first import this file. Typically, the start/env.ts
file is imported by one of the config files in your project. If not, then AdonisJS will import this file implicitly before booting the application.
The Env.create
method accepts the validation schema as a key-value pair.
- The key is the name of the environment variable.
- The value is the function that performs the validation. It can be a custom inline function or a reference to pre-defined schema methods like
schema.string
orschema.number
.
import Env from '@adonisjs/core/env'
/**
* App root is used to locate .env files inside
* the project root.
*/
const APP_ROOT = new URL('../', import.meta.url)
export default await Env.create(APP_ROOT, {
HOST: Env.schema.string({ format: 'host' }),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
APP_NAME: Env.schema.string(),
CACHE_VIEWS: Env.schema.boolean(),
SESSION_DRIVER: Env.schema.string(),
NODE_ENV: Env.schema.enum([
'development',
'production',
'test'
] as const),
})
Static-type information
The same validation rules are used to infer the static-type information. The type information is available when using the env module.
Validator schema API
schema.string
The schema.string
method ensures the value is a valid string. Empty strings fail the validation, and you must use the optional variant to allow empty strings.
{
APP_KEY: Env.schema.string()
}
// Mark APP_KEY to be optional
{
APP_KEY: Env.schema.string.optional()
}
The string value can be validated for its formatting. Following is the list of available formats.
host
Validate the value to be a valid URL or an IP address.
{
HOST: Env.schema.string({ format: 'host' })
}
url
Validate the value to be a valid URL. Optionally, you can make the validation less strict by allowing URLs not to have protocol
or tld
.
{
S3_ENDPOINT: Env.schema.string({ format: 'url' })
// Allow URLs without protocol
S3_ENDPOINT: Env.schema.string({ format: 'url', protocol: false })
// Allow URLs without tld
S3_ENDPOINT: Env.schema.string({ format: 'url', tld: false })
}
Validate the value to be a valid email address.
{
SENDER_EMAIL: Env.schema.string({ format: 'email' })
}
schema.boolean
The schema.boolean
method ensures the value is a valid boolean. Empty values fail the validation, and you must use the optional variant to allow empty values.
The string representations of 'true'
, '1'
, 'false'
, and '0'
are cast to the boolean data type.
{
CACHE_VIEWS: schema.boolean()
}
// Mark it as optional
{
CACHE_VIEWS: schema.boolean.optional()
}
schema.number
The schema.number
method ensures the value is a valid number. The string representation of a number value is cast to the number data type.
{
PORT: schema.number()
}
// Mark it as optional
{
PORT: schema.number.optional()
}
schema.enum
The schema.enum
method validates the environment variable against one of the pre-defined values. The enum options can be specified as an array of values or a TypeScript native enum type.
{
NODE_ENV: Env
.schema
.enum(['development', 'production'] as const)
}
// Mark it as optional
{
NODE_ENV: Env
.schema
.enum
.optional(['development', 'production'] as const)
}
// Using native enums
enum NODE_ENV = {
development = 'development',
production = 'production'
}
{
NODE_ENV: Env.schema.enum(NODE_ENV)
}
Custom functions
Custom functions can perform validations not covered by the schema API.
The function receives the name of the environment variable as the first argument and the value as the second argument. It must return the final value post-validation.
{
PORT: (name, value) => {
if (!value) {
throw new Error('Value for PORT is required')
}
if (isNaN(Number(value))) {
throw new Error('Value for PORT must be a valid number')
}
return Number(value)
}
}
Defining environment variables
In development
The environment variables are defined inside the .env
file during development. The env module looks for this file within the project's root and automatically parses it (if it exists).
PORT=3333
HOST=0.0.0.0
NODE_ENV=development
APP_KEY=sH2k88gojcp3PdAJiGDxof54kjtTXa3g
SESSION_DRIVER=cookie
CACHE_VIEWS=false
In production
Using your deployment platform to define the environment variables is recommended in production. Most modern-day deployment platforms have first-class support for defining environment variables from their web UI.
Suppose your deployment platform provides no means for defining environment variables. You can create a .env
file in the project root or at some different location on your production server.
AdonisJS will automatically read the .env
file from the project root. However, you must set the ENV_PATH
variable when the .env
file is stored at some different location.
# Attempts to read .env file from project root
node server.js
# Reads the .env file from the "/etc/secrets" directory
ENV_PATH=/etc/secrets node server.js
During tests
The environment variables specific to the test environment must be defined within the .env.test
file. The values from this file override the values from the .env
file.
NODE_ENV=development
SESSION_DRIVER=cookie
ASSETS_DRIVER=vite
NODE_ENV=test
SESSION_DRIVER=memory
ASSETS_DRIVER=fake
// During tests
import env from '#start/env'
env.get('SESSION_DRIVER') // memory
All other dot-env files
Alongside the .env
file, AdonisJS processes the environment variables from the following dot-env files. Therefore, you can optionally create these files (if needed).
The file with the top-most rank overrides the values from the bottom rank files.
Rank | Filename | Notes |
---|---|---|
1st | .env.[NODE_ENV].local |
Loaded for the current NODE_ENV . For example, if the NODE_ENV is set to development , then the .env.development.local file will be loaded.
|
2nd | .env.local |
Loaded in all the environments except the test and testing environments |
3rd | .env.[NODE_ENV] |
Loaded for the current NODE_ENV . For example, if the NODE_ENV is set to development , then the .env.development file will be loaded.
|
4th | .env |
Loaded in all the environments. You should add this file to .gitignore when storing secrets inside it. |
Using variables inside the dot-env files
Within dot-env files, you can reference other environment variables using the variable substitution syntax.
We compute the APP_URL
from the HOST
and the PORT
properties in the following example.
HOST=localhost
PORT=3333
URL=$HOST:$PORT
All letter, numbers, and the underscore (_) after the $
sign are used to form a variable name. You must wrap the variable name inside curly braces {}
if the name has special characters other than an underscore.
REDIS-USER=admin
REDIS-URL=localhost@${REDIS-USER}
Escaping the $
sign
To use the $
sign as a value, you must escape it to prevent variable substitution.
PASSWORD=pa\$\$word