[Nest.js] 15 - Configuration
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.