andruu / nestjs-config

Config module for nestjs using dotenv :key:

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nestjs Config

Configuration component for NestJs.

Features

  • Load your configuration files using globs
  • Support for different environment configurations, thanks to dotenv
  • Change and Load configuration at runtime

Installation

Yarn

yarn add nestjs-config

NPM

npm install nestjs-config --save

Getting Started

Let's imagine that we have a folder called src/config in our project that contains several configuration files.

/src
├── app.module.ts
├── config
│   ├── express.ts
│   ├── graphql.ts
│   └── grpc.ts

Let's register the config module in app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from "nestjs-config";

@Module({
    imports: [
        ConfigModule.load(),
    ],
})
export class AppModule {}

That's it!


Now let's say that your application isn't located in a folder called src, but it's located in ./app.

import * as path from 'path';
import { Module } from '@nestjs/common';
import { ConfigModule } from "nestjs-config";

@Module({
    imports: [
        ConfigModule.load(
            path.resolve(__dirname, 'config/**/*.{ts,js}')
        ),
    ],
})
export class AppModule {}

We provide as first argument the glob of our interested configuration that we want to load.

Complex Project Structure

Imagine a more complex project structure:

/
├── dist/
├── src/
│   ├── app/
│   │   ├── app.module.ts
│   │   └── bootstrap/
│   │   │   ├── index.ts
│   │   │   └── bootstrap.module.ts
│   ├── migrations/
│   ├── cli/
│   ├── config/
│   │   ├── app.ts
│   │   └── database.ts
│   └── main.ts
├── tsconfig.json
└── package.json

In this example, config files are located near the /src/app folder, because they are shared between app, migrations and cli scripts.

Also during typescript compilation all files from src/ folder will be moved to the dist/ folder.

Moreover, the ConfigModule is imported in the BootstrapModule, but not directly in AppModule.

// app.module.ts
import { Module } from '@nestjs/common';
import { BootstrapModule } from "./bootstrap";

@Module({
    imports: [BootstrapModule],
})
export class AppModule {}
// bootstrap.module.ts
import * as path from 'path';
import { Module } from '@nestjs/common';
import { ConfigModule } from "nestjs-config";

@Module({
    imports: [
      ConfigModule.load(path.resolve(__dirname, '../../config/**/*.{ts,js}')),
    ],
})
export class BootstrapModule {}

In this example, we still provide the glob describing the locations of our configuration files as first argument. This approach has some drawbacks:

  • First, the example above looks a little bit complicated and messy.
  • Second, we would have to remember about this glob path in case we want to move the BootstrapModule to a different location.

Fortunately, there are two ways to avoid such situations:

  • Explicitly set an absolute path to the project sources from AppModule and use glob with relative path:

    // app.module.ts
    import { Module } from '@nestjs/common';
    import { ConfigService } from "nestjs-config";
    import * as path from "path";
    import { BootstrapModule } from "./bootstrap";
    
    // define a "global root path" for this module
    ConfigService.srcPath = path.resolve(__dirname, '..');
    
    @Module({
        imports: [BootstrapModule],
    })
    export class AppModule {}
    // bootstrap.module.ts
    import { Module } from '@nestjs/common';
    import { ConfigModule } from "nestjs-config";
    
    // the following load() statement is relative to the defined root path
    @Module({
        imports: [
          ConfigModule.load('config/**/*.{ts,js}')
        ],
    })
    export class BootstrapModule {}
  • Invoke ConfigModule.resolveSrcPath(__dirname) from any module before loading the config and use glob with a relative path.

    // bootstrap.module.ts
    import { Module } from '@nestjs/common';
    import { ConfigModule } from "nestjs-config";
    
    @Module({
        imports: [
          ConfigModule.resolveSrcPath(__dirname).load('config/**/*.{ts,js}')
        ],
    })
    export class BootstrapModule {}

In both cases we provide the glob of our configuration as first argument, but it is relative to the src/ folder.

Environment Configuration

This package ship with the amazing dotenv package that allows you to create a .env file in your preferred location.

Let's create one, for demo purposes!

# .env
EXPRESS_PORT=3000

Now, in our src/config/express.ts configuration file, we can refer to that environment variable

// src/config/express.ts
export default {

    port: process.env.EXPRESS_PORT || 3000,

}

Note: By default the package look for a .env file in the path that you have started your server from. If you want to specify another path for your .env file, use the second parameter of ConfigModule.load().

Usage

Now we are ready to inject our ConfigService everywhere we'd like.

import {ConfigService} from 'nestjs-config'

@Injectable()
class SomeService {

    constructor(private readonly config: ConfigService) {
        this.config = config;
    }
    
    isProduction() {
        const env = this.config.get('app.environment');
        
        return env === 'production';
    }
}

You may also use the @InjectConfig decorator as following:

import {InjectConfig} from 'nestjs-config';

@Injectable()
class SomeService {

    constructor(@InjectConfig() private readonly config) {
        this.config = config;
    }
}

Customer Helpers

This feature allows you to create small helper function that computes values from your configurations.

Reconsider the isProduction() method from above. But in this case, let's define it as a helper:

// src/config/express.ts

export default {

    environment: process.env.EXPRESS_ENVIRONMENT,
    port: process.env.EXPRESS_PORT,
    
    // helpers
    isProduction() {
        return this.get('express.environment') === 'production';
    }
}

You can use the helper function as follows:

// this.config is the ConfigService!
this.config.get('express').isProduction();

// or
this.config._isProduction(); // note the underscore prefix.

Global Helpers

You can also attach helpers to the global instance as follow:

this.config.registerHelper('isProduction', () => {
    return this.get('express.environment') === 'production';
});

And then use it like this:

this.config.isProduction(); // note the missing underscore prefix

ConfigService API

get(param: string | string[], value: any = undefined): any

Get a configuration value via path, you can use dot notation to traverse nested object. It returns a default value if the key does not exist.

this.config.get('server.port'); // 3000
this.config.get('an.undefined.value', 'foobar'); // 'foobar' if the key does not exist

set(param: string | string[], value: any = null): Config

Set a value at runtime, it creates the specified key / value if it doesn't already exists.

this.config.set('server.port', 2000); // {server:{ port: 2000 }}

has(param: string | string[]): boolean

Determine if the given path for a configuration exists and is set.

this.config.has('server.port'); // true or false

merge(glob: string, options?: DotenvOptions): Promise

Load other configuration files at runtime. This is great for package development.

@Module({})
export class PackageModule implements NestModule {

    constructor(@InjectConfig() private readonly config) {}

    async configure(consumer: MiddlewareConsumer) {
        await this.config.merge(path.join(__dirname, '**/*.config.{ts,js}'));
    }
}

registerHelper(name: string, fn: (...args:any[]) => any): ConfigService

Register a custom global helper function

this.config.registerHelper('isProduction', () => {
    return this.get('express.environment') === 'production';
});

Decorators

It's possible to use decorators instead of injecting the ConfigService. Note that the @Configurable() decorator replaces the descriptor.value for the method with its own function. Regarding to the current nestjs implementation (Issue-1180), this behavior will break all decorators that FOLLOW AFTER the @Configurable() decorator.

For the expected behavior, the @Configurable() decorator MUST be placed at the last position for one method.

Working Example:

import {Injectable, Get} from '@nestjs/common';
import {Configurable, ConfigParam} from 'nestjs-config';

@Injectable()
export default class UserController {
    
    @Get("/")
    @Configurable()
    index(@ConfigParam('my.parameter', 'default value') parameter?: string) {
        return { data: parameter };
    }
}

Broken Example:

import {Injectable, Get, UseInterceptors} from '@nestjs/common';
import {Configurable, ConfigParam} from 'nestjs-config';
import {TransformInterceptor} from '../interceptors';

@Injectable()
export default class UserController {
    
    @Configurable()
    @Get("/")   // <-- nestjs decorator won't work because it placed after @Configurable()
    @UseInterceptors(TransformInterceptor)// <-- nestjs decorator won't work because it placed after @Configurable()
    index(@ConfigParam('my.parameter', 'default value') parameter?: string) {
        return { data: parameter };
    }
}

Broken Example 2:

import {Injectable, Get, UseInterceptors} from '@nestjs/common';
import {Configurable, ConfigParam} from 'nestjs-config';
import {TransformInterceptor} from '../interceptors';

@Injectable()
export default class UserController {
    
    
    @Get("/") // <-- nestjs decorator will work fine because it placed before @Configurable()
    @Configurable()
    @UseInterceptors(TransformInterceptor) // <-- nestjs decorator won't work because it placed after @Configurable()
    index(@ConfigParam('my.parameter', 'default value') parameter?: string) {
        return { data: parameter };
    }
}

TypeORM

Using the ConfigModule in combination with TypeORM (e.g., in order to configure TypeORM) requires using the forRootAsync() function supplied by the typeorm package for nestjs (@nestjs/typeorm)

import {Module} from '@nestjs/common';
import {ConfigModule, ConfigService} from 'nestjs-config';
import {TypeOrmModule} from '@nestjs/typeorm';
import * as path from 'path';

@Module({
    imports: [
        ConfigModule.load(path.resolve(__dirname, 'config/**/*.{ts,js}')),
        TypeOrmModule.forRootAsync({
            useFactory: (config: ConfigService) => config.get('database'),
            inject: [ConfigService],
        }),
    ],
})
export default class AppModule {}

Your config file may look something like this:

//config/database.ts
export default {
    type: 'mysql',
    host: process.env.DB_HOST,
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    port: parseInt(process.env.DB_PORT),
};

Built by Fenos, Shekohex and Bashleigh

About

Config module for nestjs using dotenv :key:

License:MIT License


Languages

Language:TypeScript 100.0%