Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/nestjsx/nestjs-config

Config module for nestjs using dotenv :key:
https://github.com/nestjsx/nestjs-config

config dotenv nestjs typescript

Last synced: 6 days ago
JSON representation

Config module for nestjs using dotenv :key:

Awesome Lists containing this project

README

        



travis
npm
license
coveralls
package quality
dependabot
PRs welcome
Awesome Nest
Nest Powered

npm downloads


Nestjs Config

Configuration component for NestJs.

## Features

- Load your configuration files using globs
- Support for different environment configurations, thanks to [dotenv](https://github.com/motdotla/dotenv)
- Change and Load configuration at runtime

### Installation

**Yarn**
```bash
yarn add nestjs-config
```

**NPM**
```bash
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.

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

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

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

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

-----

#### Complex Project Structure

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

We want to be able to set a different 'root path' to load our configurations from. Be it `src` or `dist`.

Imagine a more complex project structure like this:

```
/
├── 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 in the `/src/config` 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`.

```ts
// app.module.ts
import { Module } from '@nestjs/common';
import { BootstrapModule } from './bootstrap';
import { ConfigService } from 'nestjs-config';

ConfigService.rootPath = path.resolve(__dirname, '..');

@Module({
imports: [BootstrapModule],
})
export class AppModule {}
```

```typescript
// bootstrap.module.ts
import * as path from 'path';
import { Module } from '@nestjs/common';
import { ConfigModule } from 'nestjs-config';

@Module({
imports: [
ConfigModule.load(path.resolve('config', '**/!(*.d).{ts,js}')),
],
})
export class BootstrapModule {}
```
Setting the `ConfigService.rootPath` before calling `ConfigModule.load(...)` will change the default root dir of where your configs are loaded from.

Another method is to invoke `ConfigModule.resolveRootPath(__dirname)` from any module before loading the config and use glob with a relative path.
```ts
// bootstrap.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from 'nestjs-config';

@Module({
imports: [
ConfigModule.resolveRootPath(__dirname).load('config/**/!(*.d).{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 (or eventually `dist/`).

## Multi-modular config usage

In some cases your structure might take on this shape

```
/
├── src/
│ ├── cats/
│ │ ├── cats.module.ts
│ │ └── cats.config.ts
│ ├── dogs/
│ │ ├── dogs.module.ts
│ │ └── dogs.config.ts
│ ├── app.module.ts
│ └── main.ts
├── tsconfig.json
└── package.json
```

With the examples above you'd have to call your config like so `ConfigService.get('dogs.config.bark')`. You can use the `modifyConfigName` method option to change the name of your configs

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

@Module({
imports: [
ConfigModule.load(path.resolve(__dirname, '**/!(*.d).config.{ts,js}'), {
modifyConfigName: name => name.replace('.config', ''),
}),
],
})
export class AppModule {}
```
Now you can call your config like so `ConfigService.get('dogs.bark')`.

## Production environments

You might have notice the use of `config/**/!(*.d).{ts,js}` in the glob. When running in production (running in JavaScript after TypeScript compilation) we want to disinclude the TypeScript definition files. The use of `config/**/*.ts` is fine in dev environments but we recommend using this example `config/**/!(*.d).{ts,js}` to avoid issues later on when running in a production environment.

## Environment Configuration

This package ships with the amazing [dotenv](https://github.com/motdotla/dotenv) package that allows you to create
a `.env` file in your preferred location.

Let's create one, for demo purposes!

```bash
# .env
EXPRESS_PORT=3000
```

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

```ts
// 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` anywhere we'd like.

```ts
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:

```ts
import {InjectConfig} from 'nestjs-config';

@Injectable()
class SomeService {

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

## Custom 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:

```ts
// 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:

```ts
// 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:

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

And then use it like this:

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

## 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](https://github.com/nestjs/nest/issues/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:**
```ts
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:**
```ts
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:**
```ts
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](https://github.com/typeorm/typeorm) (e.g. in order to configure TypeORM) requires using the `forRootAsync()` function supplied by the typeorm package for nestjs (`@nestjs/typeorm`)

```ts
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', '**', '!(*.d).{ts,js}')),
TypeOrmModule.forRootAsync({
useFactory: (config: ConfigService) => config.get('database'),
inject: [ConfigService],
}),
],
})
export default class AppModule {}
```

Your config file may look something like this:

```ts
//config/database.ts
export default {
type: 'mysql',
host: process.env.TYPEORM_HOST,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE,
port: parseInt(process.env.TYPEORM_PORT),
logging: process.env.TYPEORM_LOGGING === 'true',
entities: process.env.TYPEORM_ENTITIES.split(','),
migrationsRun: process.env.TYPEORM_MIGRATIONS_RUN === 'true',
synchronize: process.env.TYPEORM_SYNCHRONIZE === 'true',
};
```

> We recommend using a `TYPEORM_` prefix so when running in production environments you're also able to use the same envs for running the typeorm cli. [More options here](http://typeorm.io/#/using-ormconfig/using-environment-variables)

## Custom env file path

You can specify dotenv options with the second parameter of the load method

```ts
ConfigModule.load(path.resolve(__dirname, '*/**!(*.d).config.{ts,js}'), {
path: path.resolve(__dirname, '..', '.env.staging')),
});
```

## Using different env files

Create a `.env` file at the root directory of your application had always been the best practice.
You could also create custom .env file per different environments.

```bash
# .env
EXPRESS_PORT=3000

# .env.dev
EXPRESS_PORT=3001

# .env.testing
EXPRESS_PORT=3002

# .env.staging
EXPRESS_PORT=3003
```

```ts
const ENV = process.env.NODE_ENV;

ConfigModule.load(path.resolve(__dirname, '*/**!(*.d).config.{ts,js}'), {
path: path.resolve(process.cwd(), !ENV ? '.env' : `.env.${ENV}`),
});
```

Perhaps you need a custom directory to manage env files.

In this example, the name of the custom directory is `env`.

```bash
/
├── dist/
├── env/
│ ├── .env.dev
│ ├── .env.testing
│ └── .env.staging
│ │
├── src/
│ ├── config/
│ │ └── typeorm.config.ts
│ └── main.ts
├── tsconfig.json
└── package.json
```

```ts
const ENV = process.env.NODE_ENV;

ConfigModule.load(path.resolve(__dirname, '*/**!(*.d).config.{ts,js}'), {
path: path.resolve(process.cwd(), 'env', !ENV ? '.env' : `.env.${ENV}`),
});
```

> **Note:** If you place env files inside an `src` directory, you won't be able to see env files included as a final output in `outDir` since TS compiler will never transpile files that do not match with \*.ts extension.

## Support

Any support is welcome. At least you can give us a star :star:

## Contributors

### Code Contributors

This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].


## 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.

```ts
this.config.get('server.port'); // 3000
this.config.get('an.undefined.value', 'foobar'); // 'foobar' is returned 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.

```ts
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.

```ts
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.

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

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

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

#### registerHelper(name: string, fn: (...args:any[]) => any): ConfigService
Register a custom global helper function

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

#### resolveRootPath(path: string): typeof ConfigService
change the root path from where configs files are loaded

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

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

#### root(path: string = ''): string

Returns the current working dir or defined rootPath.

```ts
ConfigService.root(); // /var/www/src
ConfigService.root('some/path/file.html'); // /var/www/src/some/path/file.html

ConfigService.resolveRootPath(__dirname).root(); // /var/www/src/app (or wherever resolveRootPath has been called with)
```

-----