Nest.js

[Nest.js] 15 - Configuration

ESTJames 2021. 9. 22. 02:25

Contents
1. Configuration?
2. Codebase vs Environment Variables
3. Module Installation
4. Configuration implementation


1. Configuration?

- In our source code, some of codes that we have written may be affected by Application Environment, Development Environment, or be protected from others.

- Therefore, we will deal with those code to save in much securer way, which is called Configuration File.

- Configuration files are not usually changed during runtime, these are loaded and defined when an application starts.

- It could be a XML, JSON, YAML, or Environment Variables type.

- let's start to manage and protect our configuration information.


2. Codebase vs Environment Variables

 

Codebase

- Usually, Codebase configuration files have information, which is OK to be exposed, such as Port number.

- XML, JASON, or YAML are Codebase configuration file.

 

Environment Variables

- It is much focused on secure informtaion of application, such as API key, or password.


3. Module Installation

Window only

$ npm install -g win-node-env

 

and then, Window & MAC OS

$ npm install config --save

4. Configuration Implementation

 

1. create a config directory to manage all configuration files.

 

2. create 3 YAML(YML) files

 

3. cofig/default.yaml

server:
  port: 3000

db:
  type: 'mysql'
  port: 3306
  database: 'test'

jwt:
  expiresIn: 3600

 

4. config/development.yml => default.yml + information in Development environment

db:
  host: 'localhost'
  username: 'root'
  password: '1234'
  synchronize: true

jwt:
  secret: 'Secret1234'

 

5. config/production.yml => => default.yml + information in Production environment

db:
  synchronize: false

 

6. src/main.ts

...
import * as config from 'config'; // 1

async function bootstrap() {
  const logger = new Logger();
  const app = await NestFactory.create(AppModule);

  const serverConfig = config.get('server'); // 2
  const port = serverConfig.port; // 3

  // Global Pipe
  ...

  // const port = 3000; // 4
  await app.listen(port);

  Logger.log(`Application running on port ${port}`);
  logger.log(`Application running on port ${port}`);
}
bootstrap();

// 1. import all files in config directory.

// 2. config.get('server') - search a scope, which is named as 'server'.

// 3. serverConfig.port - read the Key, which is named as 'port', and then return its value.

// 4. now we manage port number in configuration file, default.yaml.

 

7.1 src/configs/typeorm.config.ts : before using configuration files

import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const TypeORMConfig: TypeOrmModuleOptions = {
  type: 'mysql',
  host: 'localhost',
  port: 3306,
  username: 'root',
  password: '1234',
  database: 'test',
  entities: [__dirname + '/../**/*.entity.{js,ts}'],
  synchronize: true,
};

// this file exposes all secure information directly, even though I put this path in .gitignore

// let's change this with configuration file.

 

7.2 src/configs/typeorm.config.ts : after using configuration files

import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import * as config from 'config';

const dbConfig = config.get('db'); // 1

export const TypeORMConfig: TypeOrmModuleOptions = {
  type: dbConfig.type, // 2
  host: process.env.RDS_HOSTNAME || dbConfig.host, // 3
  port: process.env.RDS_PORT || dbConfig.port, // 2
  username: process.env.RDS_USERNAME || dbConfig.username, // 3
  password: process.env.RDS_PASSWORD || dbConfig.password, // 3
  database: process.env.RDS_DB_NAME || dbConfig.database, // 2
  entities: [__dirname + '/../**/*.entity.{js,ts}'],
  synchronize: dbConfig.synchronize, // 4
};

### process.env.XXX are not defined yet, it will apply if process.env.XXX exists in a real server environment

// 1. load a scope, named as 'db', thus default.yaml, development.yml, and production.yml are all loaded to the variable.

// 2. those key and value are defined in default.yaml

// 3. those key ans value are defined in development.yml

// 4. dbConfig.synchronize - are defined in both development.yml and production.yml,
        so we can easily change based on running environment.

 

8. src/auth/auth.module.ts

...
import * as config from 'config';

const jwtConfig = config.get('jwt'); // 1

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.register({
      secret: process.env.JWT_SECRET || jwtConfig.secret, // 2
      signOptions: {
        expiresIn: jwtConfig.expiresIn, // 3
      },
    }),
    TypeOrmModule.forFeature([UserRepository]),
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtStrategy],
  exports: [JwtStrategy, PassportModule],
})
export class AuthModule {}

// 1. load a scope, named as 'jwt', thus default.yaml, and development.yml are all loaded to the variable.

// 2. process.env.JWT_SECRET - look for a key first in a server, if not found, use development.yml's value

// 3. this key and value is defined in defualt.yaml

 

// 9. src/auth/jwt.strategy.ts

...
import * as config from 'config';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    @InjectRepository(UserRepository)
    private userRepository: UserRepository
  ) {
      super({
        secretOrKey: process.env.JWT_SECRET || config.get('jwt.secret'), // 1
        jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      })
  }
  
  
  ...
}

// 1. config.get('jwt.secret') - use directly scope('jwt'), key('secret') and value('Secret1234') from config directory.