El manejo de credenciales y configuraciones en nuestras aplicaciones es generalmente algo indispensable para los distintos escenarios de desarrollo y despliegue. Algo tedioso a veces, pero nuestro gatuno framework Nestjs Nos provee de ConfigModule
este maravilloso módulo cubre la mayoría de los casos de uso al momento de disponer valores de configuración en nuestra aplicación en esta ocasión les tengo un ejemplo práctico de ConfigModule su inicialización y utilización. ConfigModule es perfecto para mostrar las ventajas de los módulos dinámicos, esta estrategia puede ser utilizada de una manera flexible, no importando donde lo llamemos siempre se comportara igual, pero al momento de registrarlo ahí es donde nosotros definimos que debe hacer, como debe hacerlo y que queremos hacer.
Instalación y definición de nuestra Config
Asumiendo que ya estas trabjando con un proyecto Nestjs instalamos lo siguiente:
1
2
npm i --save @Nestjs/config
Ahora lo primero que haremos es crear un archivo de configuración .env
con las siguientes variables:
1
2
3
4
5
6
7
8
# http server
SERVER_PORT=3000
# northwind database
DATABASE_HOST="localhost"
DATABASE_PORT=5432
DATABASE_NAME="northwind"
DATABASE_USER="northwind"
DATABASE_PASSWORD="northwind"
Como tip les recomiento que los nombres sean lo mas descriptivo posible. Por ejemplo
SERVER_PORT
tiene más contexto quePORT
.
Ahora crearemos nuestras interfaces que definirán la estructura de nuestras configuraciones y su vez crearemos sus respectivos métodos factory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export interface DatabaseConfig {
host: string;
port: number;
name: string;
user: string;
password: string;
}
export default () => ({
database: {
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10),
name: process.env.DATABASE_NAME,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
}
});
Lo mismo para la configuracion de nuestro server http
1
2
3
4
5
6
7
8
9
10
11
export interface ServerConfig {
port: 3000
}
export default () => ({
server: {
port: parseInt(process.env.SERVER_PORT, 10),
}
});
Ahora registramos nuestro ConfigModule
en SharedModule
1
2
3
4
5
6
7
8
9
10
11
12
13
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
expandVariables: true,
load: [
databaseConfig,
serverConfig
],
})
]
})
export class SharedModule { }
Validación y configuración por defecto
Nuestro módulo ya está listo y puede obtener las variables de entorno sin ningún problema, pero ahora agregaremos una librería para poder realizar validaciones de las variables de entorno.
Instalamos joi
para generar un esquema de validación.
1
npm install --save joi
Y agregamos lo siguiente a nuestro SharedModule
. Como podemos observar la librería joi
nos proporciona una manera simple y declarativa de la estructura de los valores de configuración de nuestra aplicación inclusive nos da la opción de poner valores por defecto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
expandVariables: true,
load: [
databaseConfig,
serverConfig
],
validationSchema: Joi.object({
SERVER_PORT: Joi.number().default(3000),
DATABASE_HOST: Joi.string().default('localhost'),
DATABASE_PORT: Joi.number().default(5432),
DATABASE_NAME: Joi.string().required(),
DATABASE_USER: Joi.string().required(),
DATABASE_PASSWORD: Joi.string().required(),
}),
validationOptions: {
allowUnknown: true,
abortEarly: false,
},
})
]
})
export class SharedModule { }
Usando ConfigService para obtener nuestras configuraciones
Para obtener las variables de configuración lo hacemos por medio del servicio ConfigService
y su método get()
podemos obtener los valores inyectando ConfigService en algún servicio definido en nuestra aplicación:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Injectable()
export class CustomService{
constructor(private config: ConfigService) {}
getHost() {
return this.configService.get<string>('database.host')
}
getDatabaseConfig(){
return this.configService.get<DatabaseConfig>('database')
}
}
También podemos inyectar ConfigService de forma dinámica mediante CustomProviders el cual es nuestro caso.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Module({
imports: [
ConfigModule,
TypeOrmModule.forRootAsync({
useFactory: (config: ConfigService) => {
// get specific configuration
const database = config.get<DatabaseConfig>('database')
return {
type: 'postgres',
host: database.host,
port: database.port,
username: database.user,
password: database.password,
database: database.name,
entities: [
ProductEntity,
CategoryEntity,
SupplierEntity
],
synchronize: false,
logging: ['query']
}
},
inject: [ConfigService], // Injecting ConfigService (nestjs responsability)
})
]
})
export class NorthwindDatabaseModule { }
Finalmente conseguimos una forma limpia y configurable de manejar las configuraciones de nuestra aplicación en este caso hacemos uso de variables de entorno pero dependiendo del caso podemos obtener configuraciones desde archivos o alguna API. La documentación de ConfigModule verás más a detalle como usarlo, pero sin duda esto nos simplifica el desarrollo.
Github repository
Conclusión
Que más decir esto fue corto, pero útil a sí que toma un meme de regalo: