We use cookies to enhance your experience on the site
CodeWorlds

Configuration Management - managing resources

A database address written into the code works perfectly - on your laptop. On the staging server the database sits elsewhere, on production elsewhere again, and its password should never reach the repository at all. Three environments, one codebase, three different values - and none of them can be hard-coded.

Rome had a custom for this. Orders were written once, but a province's resources were recorded in the local register: how much grain, which road, who governs. The same legion marched into Gaul and into Egypt, reading the local register each time. In an application that register is the environment variables.

Four steps

Configuration enters a project in a fixed order - worth knowing as a whole, because skipping a step produces misleading errors:

  1. Create the
    .env
    file
    with keys and values.
  2. Register
    ConfigModule.forRoot()
    in the root module.
  3. Inject
    ConfigService
    through the constructor.
  4. Read the values with
    configService.get('KEY')
    .

Step one: the .env file

Environment variables live in a

.env
file in the project directory:

1DB_HOST=localhost
2DB_PORT=5432
3DB_PASSWORD=tribute123
4JWT_SECRET=super-secret-key

The format is minimal: name, equals sign, value. No quotes, no spaces around the equals sign, no semicolons at the end.

The file's name is not a convention you choose - it is

.env
, not
config.json
,
settings.yaml
or
environment.xml
. You will meet those formats in other ecosystems, but not here.

The file itself never goes into the repository - you add it to

.gitignore
. Instead you commit a
.env.example
with the same keys and empty or sample values, so a colleague knows what to fill in.

Step two: registering the module

Configuration support comes from the

@nestjs/config
package. Mind the name - there are no
@nestjs/env
,
@nestjs/settings
or
@nestjs/environment
packages.

We register it in the root module:

1@Module({
2  imports: [
3    ConfigModule.forRoot({
4      envFilePath: '.env',
5      isGlobal: true,
6    }),
7  ],
8})
9export class AppModule {}

The order of the elements is fixed:

ConfigModule.forRoot({
, then the options -
envFilePath: '.env',
and
isGlobal: true
- and finally
})
.

envFilePath
points at the file to load; with the default name it can be omitted.

isGlobal: true
makes
ConfigService
available in every module with no additional imports.
Without that option you would have to import
ConfigModule
in each module separately - and usually half the application uses it. Note what this option does not do: it encrypts nothing, does not limit visibility to the root module, and does not refresh configuration on restart.

Steps three and four: reading

You inject

ConfigService
like any other dependency:

1@Injectable()
2export class DatabaseService {
3  constructor(private configService: ConfigService) {}
4
5  getConnection() {
6    const host = this.configService.get('DB_HOST');
7    const port = this.configService.get<number>('DB_PORT', 5432);
8
9    return { host, port };
10  }
11}

A read is three parts in a fixed order:

this.configService
,
.get
,
('DB_HOST')
.

Two details are worth knowing. The

<number>
notation is a type parameter - it tells TypeScript what you expect, because values from a
.env
file always arrive as text. The second argument,
5432
here, is a default value used when the key is missing. That is convenient, but be careful: a default password or a default JWT secret is a ready-made hole - for such values it is better that the application fails to start than that it starts with anything at hand, @name.

Checking at startup

Since a missing key yields

undefined
, a failure surfaces only at first use - sometimes hours later. Better to check the full set at once:

1ConfigModule.forRoot({
2  isGlobal: true,
3  validationSchema: Joi.object({
4    DB_HOST: Joi.string().required(),
5    DB_PORT: Joi.number().default(5432),
6    JWT_SECRET: Joi.string().required(),
7  }),
8});

validationSchema
describes which keys you expect and of what type. At startup
ConfigModule
compares it with the contents of
.env
and aborts the launch if anything is missing - with a message naming the key outright.

That trades a three-in-the-morning outage for an error at deployment time.

Joi
is a schema description library; it is not part of NestJS, but it is what
@nestjs/config
uses.

Summary

The province's register is read on the spot, the code is one for all of them:

  • configuration lives outside the code, because the same application runs in several environments,
  • four steps in order: the
    .env
    file →
    ConfigModule.forRoot()
    in the root module → injecting
    ConfigService
    configService.get('KEY')
    ,
  • variables live in a
    .env
    file
    - not
    config.json
    , not
    settings.yaml
    , not
    environment.xml
    ,
  • format: name, equals sign, value; the file stays out of the repository, you commit
    .env.example
    ,
  • the package is
    @nestjs/config
    ;
    @nestjs/env
    ,
    @nestjs/settings
    and
    @nestjs/environment
    do not exist,
  • registration in order:
    ConfigModule.forRoot({
    ,
    envFilePath: '.env',
    ,
    isGlobal: true
    ,
    })
    ,
  • isGlobal: true
    makes
    ConfigService
    available in all modules with no extra imports
    - it encrypts nothing and restricts nothing,
  • reading:
    this.configService
    +
    .get
    +
    ('DB_HOST')
    ; values from
    .env
    always arrive as text,
  • get
    's second argument is a default value - do not give one to passwords or secrets,
  • validationSchema
    with
    Joi
    checks the full set of keys at startup and aborts the launch when one is missing.

This is the module's last lesson. You can now build a module, a controller and a service, expose a REST API, describe data with DTOs and move configuration out of the code - everything an application needs to go out into the world. For now remember: the code is one for every province; only the register they read on arrival differs.

Go to CodeWorlds