@vinsidious/nestjs-config v1.0.4
Note
This is a fork of the original nestjs-config which has been modified to use dotenv-flow instead of dotenv.
Features
- Load your configurations with globs
- Support for different environment configuration, thanks to dotenv
- Change and Load configuration at runtime
Installation
Yarn
yarn add nestjs-configNPM
npm install nestjs-config --saveGetting Started
Let's imagine that we have a folder called config in our project under src
/src
├── app.module.ts
├── config
│ ├── express.ts
│ ├── graphql.ts
│ └── grpc.tsLet'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 in a folder called src, it's 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
For example project has the next 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.jsonOn this example, config files are located near the 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 ConfigModule is imported in 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 {}We still provide as first argument the glob of our configuration, but an example above looks a little bit ugly.
Also we will always have to remember about this glob path when we want to move the BootstrapModule
to different place.
There is two ways to avoid such situations:
Explicitly set absolute path to the project sources from
AppModuleand 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"; ConfigService.srcPath = path.resolve(__dirname, '..'); @Module({ imports: [BootstrapModule], }) export class AppModule {}// bootstrap.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from "nestjs-config"; @Module({ imports: [ ConfigModule.load('config/**/*.{ts,js}') ], }) export class BootstrapModule {}Invoke
ConfigModule.resolveSrcPath(__dirname)from any your module before config loading and use glob with 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 {}
On these examples we provide as first argument the glob of our configuration, but it is relative to the src/ folder.
Environment configuration
This package ship with the amazing dotenv so that you can create
a .env file in your preferred location.
let's create one!
# .env
EXPRESS_PORT=3000now in our src/config/express.ts file we can refer to that environment variable
// src/config/express.ts
export default {
port: process.env.EXPRESS_PORT
}Note: By default the package look for a .env file in the path that you started your server from.
If you want to specify a 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 can also use the @InjectConfig decorator instead, 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 configurations.
example isProduction helper:
// src/config/express.ts
export default {
environment: process.env.EXPRESS_PORT
port: process.env.EXPRESS_PORT,
// helpers
isProduction() {
return this.get('express.environment') === 'production';
}
}usage:
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';
});Then use it:
this.config.isProduction();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.
this.config.get('server.port'); // 3000set(param: string | string[], value: any = null): Config
Set a value at runtime, it creates one if doesn't 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 set
this.config.has('server.port'); // true or falsemerge(glob: string, options?: DotenvOptions): Promise
You can load other configuration at runtime. 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 custom global helper
this.config.registerHelper('isProduction', () => {
return this.get('express.environment') === 'production';
});Decorators
It's possible to use decorators instead of injecting the ConfigService.
But note that @Configurable() decorator replaces descriptor.value for the
method with own function. Regarding to the current nestjs implementation
(Issue-1180) this behavior will
break all decorators that follow after Configurable() decorator.
For the right behavior @Configurable() decorator MUST be placed at
the last place when you use several decorators 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', 'deafult 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', 'deafult 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 after @Configurable()
@Configurable()
@UseInterceptors(TransformInterceptor) // <-- nestjs decorator won't work because it placed after @Configurable()
index(@ConfigParam('my.parameter', 'deafult value') parameter?: string) {
return { data: parameter };
}
}Typeorm
Usage with typeorm requires the use of the forRootAsync function supplied by the typeorm package for nestjs
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 {}And your config file:
//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 from Fenos, Shekohex and Bashleigh