Configuration
Applications often run in different environments. Depending on the environment, different configuration settings should be used. For example, usually the local environment relies on specific database credentials, valid only for the local DB instance. The production environment would use a separate set of DB credentials. Since configuration variables change, best practice is to store configuration variables in the environment.
Externally defined environment variables are visible inside Node.js through the process.env global. We could try to solve the problem of multiple environments by setting the environment variables separately in each environment. This can quickly get unwieldy, especially in the development and testing environments where these values need to be easily mocked and/or changed.
In Node.js applications, it's common to use .env files, holding key-value pairs where each key represents a particular value, to represent each environment. Running an app in different environments is then just a matter of swapping in the correct .env file.
A good approach for using this technique in Nest is to create a ConfigModule that exposes a ConfigService which loads the appropriate .env file. While you may choose to write such a module yourself, for convenience Nest provides the @nestjs/config package out-of-the box. We'll cover this package in the current chapter.
Installation
To begin using it, we first install the required dependency.
info Hint The
@nestjs/configpackage internally uses dotenv.
warning Note
@nestjs/configrequires TypeScript 4.1 or later.
Getting started
Once the installation process is complete, we can import the ConfigModule. Typically, we'll import it into the root AppModule and control its behavior using the .forRoot() static method. During this step, environment variable key/value pairs are parsed and resolved. Later, we'll see several options for accessing the ConfigService class of the ConfigModule in our other feature modules.
The above code will load and parse a .env file from the default location (the project root directory), merge key/value pairs from the .env file with environment variables assigned to process.env, and store the result in a private structure that you can access through the ConfigService. The forRoot() method registers the ConfigService provider, which provides a get() method for reading these parsed/merged configuration variables. Since @nestjs/config relies on dotenv, it uses that package's rules for resolving conflicts in environment variable names. When a key exists both in the runtime environment as an environment variable (e.g., via OS shell exports like export DATABASE_USER=test) and in a .env file, the runtime environment variable takes precedence.
A sample .env file looks something like this:
If you need some env variables to be available even before the ConfigModule is loaded and Nest application is bootstrapped (for example, to pass the microservice configuration to the NestFactory#createMicroservice method), you can use the --env-file option of the Nest CLI. This option allows you to specify the path to the .env file that should be loaded before the application starts. --env-file flag support was introduced in Node v20, see the documentation for more details.
Custom env file path
By default, the package looks for a .env file in the root directory of the application. To specify another path for the .env file, set the envFilePath property of an (optional) options object you pass to forRoot(), as follows:
You can also specify multiple paths for .env files like this:
If a variable is found in multiple files, the first one takes precedence.
Disable env variables loading
If you don't want to load the .env file, but instead would like to simply access environment variables from the runtime environment (as with OS shell exports like export DATABASE_USER=test), set the options object's ignoreEnvFile property to true, as follows:
Use module globally
When you want to use ConfigModule in other modules, you'll need to import it (as is standard with any Nest module). Alternatively, declare it as a global module by setting the options object's isGlobal property to true, as shown below. In that case, you will not need to import ConfigModule in other modules once it's been loaded in the root module (e.g., AppModule).
Custom configuration files
For more complex projects, you may utilize custom configuration files to return nested configuration objects. This allows you to group related configuration settings by function (e.g., database-related settings), and to store related settings in individual files to help manage them independently.
A custom configuration file exports a factory function that returns a configuration object. The configuration object can be any arbitrarily nested plain JavaScript object. The process.env object will contain the fully resolved environment variable key/value pairs (with .env file and externally defined variables resolved and merged as described above). Since you control the returned configuration object, you can add any required logic to cast values to an appropriate type, set default values, etc. For example:
We load this file using the load property of the options object we pass to the ConfigModule.forRoot() method:
info Notice The value assigned to the
loadproperty is an array, allowing you to load multiple configuration files (e.g.load: [databaseConfig, authConfig])
With custom configuration files, we can also manage custom files such as YAML files. Here is an example of a configuration using YAML format:
To read and parse YAML files, we can leverage the js-yaml package.
Once the package is installed, we use the yaml#load function to load the YAML file we just created above.
warning Note Nest CLI does not automatically move your "assets" (non-TS files) to the
distfolder during the build process. To make sure that your YAML files are copied, you have to specify this in thecompilerOptions#资源object in thenest-cli.jsonfile. As an example, if theconfigfolder is at the same level as thesrcfolder, addcompilerOptions#资源with the value"assets": [{"include": "../config/*.yaml", "outDir": "./dist/config"}]. Read more here.
Just a quick note - configuration files aren't automatically validated, even if you're using the validationSchema option in NestJS's ConfigModule. If you need validation or want to apply any transformations, you'll have to handle that within the factory function where you have complete control over the configuration object. This allows you to implement any custom validation logic as needed.
For example, if you want to ensure that port is within a certain range, you can add a validation step to the factory function:
Now, if the port is outside the specified range, the application will throw an error during startup.
Using the ConfigService
To access configuration values from our ConfigService, we first need to inject ConfigService. As with any provider, we need to import its containing module - the ConfigModule - into the module that will use it (unless you set the isGlobal property in the options object passed to the ConfigModule.forRoot() method to true). Import it into a feature module as shown below.
Then we can inject it using standard constructor injection:
info Hint The
ConfigServiceis imported from the@nestjs/configpackage.
And use it in our class:
As shown above, use the configService.get() method to get a simple environment variable by passing the variable name. You can do TypeScript type hinting by passing the type, as shown above (e.g., get<string>(...)). The get() method can also traverse a nested custom configuration object (created via a Custom configuration file), as shown in the second example above.
You can also get the whole nested custom configuration object using an interface as the type hint:
The get() method also takes an optional second argument defining a default value, which will be returned when the key doesn't exist, as shown below:
ConfigService has two optional generics (type arguments). The first one is to help prevent accessing a config property that does not exist. Use it as shown below:
With the infer property set to true, the ConfigService#get method will automatically infer the property type based on the interface, so for example, typeof port === "number" (if you're not using strictNullChecks flag from TypeScript) since PORT has a number type in the EnvironmentVariables interface.
Also, with the infer feature, you can infer the type of a nested custom configuration object's property, even when using dot notation, as follows:
The second generic relies on the first one, acting as a type assertion to get rid of all undefined types that ConfigService's methods can return when strictNullChecks is on. For instance:
info Hint To make sure the
ConfigService#getmethod retrieves values exclusively from custom configuration files and ignoresprocess.envvariables, set theskipProcessEnvoption totruein the options object of theConfigModule'sforRoot()method.
Configuration namespaces
The ConfigModule allows you to define and load multiple custom configuration files, as shown in Custom configuration files above. You can manage complex configuration object hierarchies with nested configuration objects as shown in that section. Alternatively, you can return a "namespaced" configuration object with the registerAs() function as follows:
As with custom configuration files, inside your registerAs() factory function, the process.env object will contain the fully resolved environment variable key/value pairs (with .env file and externally defined variables resolved and merged as described above).
info Hint The
registerAsfunction is exported from the@nestjs/configpackage.
Load a namespaced configuration with the load property of the forRoot() method's options object, in the same way you load a custom configuration file:
Now, to get the host value from the database namespace, use dot notation. Use 'database' as the prefix to the property name, corresponding to the name of the namespace (passed as the first argument to the registerAs() function):
A reasonable alternative is to inject the database namespace directly. This allows us to benefit from strong typing:
info Hint The
ConfigTypeis exported from the@nestjs/configpackage.
Namespaced configurations in modules
To use a namespaced configuration as a configuration object for another module in your application, you can utilize the .asProvider() method of the configuration object. This method converts your namespaced configuration into a provider, which can then be passed to the forRootAsync() (or any equivalent method) of the module you want to use.
Here's an example:
To understand how the .asProvider() method functions, let's examine the return value:
This structure allows you to seamlessly integrate namespaced configurations into your modules, ensuring that your application remains organized and modular, without writing boilerplate, repetitive code.
Cache environment variables
As accessing process.env can be slow, you can set the cache property of the options object passed to ConfigModule.forRoot() to increase the performance of ConfigService#get method when it comes to variables stored in process.env.
Partial registration
Thus far, we've processed configuration files in our root module (e.g., AppModule), with the forRoot() method. Perhaps you have a more complex project structure, with feature-specific configuration files located in multiple different directories. Rather than load all these files in the root module, the @nestjs/config package provides a feature called partial registration, which references only the configuration files associated with each feature module. Use the forFeature() static method within a feature module to perform this partial registration, as follows:
info Warning In some circumstances, you may need to access properties loaded via partial registration using the
onModuleInit()hook, rather than in a constructor. This is because theforFeature()method is run during module initialization, and the order of module initialization is indeterminate. If you access values loaded this way by another module, in a constructor, the module that the configuration depends upon may not yet have initialized. TheonModuleInit()method runs only after all modules it depends upon have been initialized, so this technique is safe.
Schema validation
It is standard practice to throw an exception during application startup if required environment variables haven't been provided or if they don't meet certain validation rules. The @nestjs/config package enables two different ways to do this:
- Joi built-in validator. With Joi, you define an object schema and validate JavaScript objects against it.
- A custom
validate()function which takes environment variables as an input.
To use Joi, we must install Joi package:
Now we can define a Joi validation schema and pass it via the validationSchema property of the forRoot() method's options object, as shown below:
By default, all schema keys are considered optional. Here, we set default values for NODE_ENV and PORT which will be used if we don't provide these variables in the environment (.env file or process environment). Alternatively, we can use the required() validation method to require that a value must be defined in the environment (.env file or process environment). In this case, the validation step will throw an exception if we don't provide the variable in the environment. See Joi validation methods for more on how to construct validation schemas.
By default, unknown environment variables (environment variables whose keys are not present in the schema) are allowed and do not trigger a validation exception. By default, all validation errors are reported. You can alter these behaviors by passing an options object via the validationOptions key of the forRoot() options object. This options object can contain any of the standard validation options properties provided by Joi validation options. For example, to reverse the two settings above, pass options like this:
The @nestjs/config package uses default settings of:
allowUnknown: controls whether or not to allow unknown keys in the environment variables. Default istrueabortEarly: if true, stops validation on the first error; if false, returns all errors. Defaults tofalse.
Note that once you decide to pass a validationOptions object, any settings you do not explicitly pass will default to Joi standard defaults (not the @nestjs/config defaults). For example, if you leave allowUnknowns unspecified in your custom validationOptions object, it will have the Joi default value of false. Hence, it is probably safest to specify both of these settings in your custom object.
info Hint To disable validation of predefined environment variables, set the
validatePredefinedattribute tofalsein theforRoot()method's options object. Predefined environment variables are process variables (process.envvariables) that were set before the module was imported. For example, if you start your application withPORT=3000 node main.js, thenPORTis a predefined environment variable.
Custom validate function
Alternatively, you can specify a synchronous validate function that takes an object containing the environment variables (from env file and process) and returns an object containing validated environment variables so that you can convert/mutate them if needed. If the function throws an error, it will prevent the application from bootstrapping.
In this example, we'll proceed with the class-transformer and class-validator packages. First, we have to define:
- a class with validation constraints,
- a validate function that makes use of the
plainToInstanceandvalidateSyncfunctions.
With this in place, use the validate function as a configuration option of the ConfigModule, as follows:
Custom getter functions
ConfigService defines a generic get() method to retrieve a configuration value by key. We may also add getter functions to enable a little more natural coding style:
Now we can use the getter function as follows:
Environment variables loaded hook
If a module configuration depends on the environment variables, and these variables are loaded from the .env file, you can use the ConfigModule.envVariablesLoaded hook to ensure that the file was loaded before interacting with the process.env object, see the following example:
This construction guarantees that after the ConfigModule.envVariablesLoaded Promise resolves, all configuration variables are loaded up.
Conditional module configuration
There may be times where you want to conditionally load in a module and specify the condition in an env variable. Fortunately, @nestjs/config provides a ConditionalModule that allows you to do just that.
The above module would only load in the FooModule if in the .env file there is not a false value for the env variable USE_FOO. You can also pass a custom condition yourself, a function receiving the process.env reference that should return a boolean for the ConditionalModule to handle:
It is important to be sure that when using the ConditionalModule you also have the ConfigModule loaded in the application, so that the ConfigModule.envVariablesLoaded hook can be properly referenced and utilized. If the hook is not flipped to true within 5 seconds, or a timeout in milliseconds, set by the user in the third options parameter of the registerWhen method, then the ConditionalModule will throw an error and Nest will abort starting the application.
Expandable variables
The @nestjs/config package supports environment variable expansion. With this technique, you can create nested environment variables, where one variable is referred to within the definition of another. For example:
With this construction, the variable SUPPORT_EMAIL resolves to 'support@mywebsite.com'. Note the use of the ${...} syntax to trigger resolving the value of the variable APP_URL inside the definition of SUPPORT_EMAIL.
info Hint For this feature,
@nestjs/configpackage internally uses dotenv-expand.
Enable environment variable expansion using the expandVariables property in the options object passed to the forRoot() method of the ConfigModule, as shown below:
Using in the main.ts
While our config is stored in a service, it can still be used in the main.ts file. This way, you can use it to store variables such as the application port or the CORS host.
To access it, you must use the app.get() method, followed by the service reference:
You can then use it as usual, by calling the get method with the configuration key:

