
Qué es un provider asíncrono
Los providers asíncronos en NestJS son servicios que requieren operaciones asíncronas durante su inicialización antes de estar disponibles para la inyección de dependencias. A diferencia de los providers síncronos tradicionales, necesitan completar tareas como conexiones a bases de datos, llamadas a servicios de secretos (AWS Secrets Manager, Vault) o lectura de archivos de configuración antes de que la aplicación pueda procesar la primera petición.
El runtime de NestJS espera a que todas las factorías asíncronas se resuelvan antes de arrancar el servidor HTTP. Esto garantiza que el primer request que llegue encuentre todos los recursos listos, evitando errores 500 durante el calentamiento.
Si una factoría asíncrona falla, el contenedor registra el error y aborta el arranque. Este comportamiento es deseable: mejor fallar rápido en despliegue que servir peticiones con un servicio parcialmente inicializado.
Los providers asíncronos son habituales en varios escenarios de producción:
- Conexiones a bases de datos con verificación de disponibilidad (ping inicial).
- Carga de secretos desde AWS Secrets Manager, Azure Key Vault o HashiCorp Vault.
- Clientes de APIs de terceros que requieren tokens OAuth renovados en el arranque.
- Feature flags descargados desde LaunchDarkly o Unleash para la primera evaluación.
Implementación con useFactory
La forma canónica de crear un provider asíncrono es con la propiedad useFactory devolviendo una promesa. El token puede ser un string, un Symbol o una clase.
import { Module } from '@nestjs/common';
import { MongoClient } from 'mongodb';
@Module({
providers: [
{
provide: 'MONGO_CLIENT',
useFactory: async () => {
const client = new MongoClient(process.env.MONGO_URL);
await client.connect();
return client;
},
},
],
exports: ['MONGO_CLIENT'],
})
export class DatabaseModule {}
Cuando la factoría necesita otras dependencias, se declaran en inject. NestJS las resuelve antes de invocar la función.
@Module({
imports: [ConfigModule],
providers: [
{
provide: 'STRIPE_CLIENT',
useFactory: async (config: ConfigService) => {
const secret = await config.getSecret('stripe.api_key');
return new Stripe(secret, { apiVersion: '2025-07-30' });
},
inject: [ConfigService],
},
],
exports: ['STRIPE_CLIENT'],
})
export class PaymentsModule {}
En este ejemplo típico de fintech, ConfigService recupera la API key desde un gestor de secretos y la factoría devuelve un cliente de Stripe ya autenticado.
Manejo de errores en arranque
Un provider asíncrono debe manejar los fallos de inicialización de forma explícita. Envolver la factoría en try/catch permite añadir trazas útiles para el diagnóstico en producción.
{
provide: 'REDIS_CLIENT',
useFactory: async (config: ConfigService) => {
try {
const client = new Redis({
host: config.get('redis.host'),
port: config.get('redis.port'),
password: config.get('redis.password'),
connectTimeout: 5000,
});
await client.ping();
return client;
} catch (error) {
throw new Error(
`No se pudo conectar a Redis en ${config.get('redis.host')}: ${error.message}`,
);
}
},
inject: [ConfigService],
}
El mensaje de error llega a stdout y a los exporters de OpenTelemetry, lo que permite detectar rápidamente problemas de red en entornos cloud. En despliegues Kubernetes, el pod queda en estado CrashLoopBackOff y el alerta salta a Grafana.
Caso real en una plataforma de logística
Una plataforma de logística con múltiples transportistas necesita resolver credenciales OAuth al arrancar. Cada transportista tiene su propio endpoint, su propio flujo de autenticación y un token con caducidad. El provider asíncrono precalienta los clientes HTTP antes de aceptar peticiones.
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TransportistaClient } from './transportista.client';
@Module({
providers: [
{
provide: 'TRANSPORTISTAS',
useFactory: async (config: ConfigService) => {
const proveedores = ['seur', 'mrw', 'correos-express'];
const clientes = new Map<string, TransportistaClient>();
for (const nombre of proveedores) {
const cliente = new TransportistaClient({
baseUrl: config.get(`transportistas.${nombre}.url`),
clientId: config.get(`transportistas.${nombre}.clientId`),
clientSecret: config.get(`transportistas.${nombre}.secret`),
});
await cliente.autenticar();
clientes.set(nombre, cliente);
}
return clientes;
},
inject: [ConfigService],
},
],
exports: ['TRANSPORTISTAS'],
})
export class LogisticaModule {}
Consumo del provider en servicios
El servicio recibe el provider con @Inject() usando el mismo token que se declaró. El tipado fuerte garantiza que los métodos disponibles se resuelven en tiempo de compilación.
import { Injectable, Inject } from '@nestjs/common';
import { TransportistaClient } from './transportista.client';
@Injectable()
export class EnvioService {
constructor(
@Inject('TRANSPORTISTAS')
private readonly transportistas: Map<string, TransportistaClient>,
) {}
async crearEnvio(proveedor: string, pedido: Pedido) {
const cliente = this.transportistas.get(proveedor);
if (!cliente) {
throw new Error(`Transportista no soportado: ${proveedor}`);
}
return cliente.crearGuia(pedido);
}
}
flowchart LR
A[bootstrap] --> B[Resolver ConfigService]
B --> C[useFactory async]
C --> D{Todas las conexiones OK}
D -- si --> E[Servidor HTTP arriba]
D -- no --> F[Abortar arranque]
Con este patrón, la primera petición que reciba la API encontrará todos los transportistas autenticados y el tiempo de respuesta será predecible, evitando latencias adicionales por autenticación en caliente.
Hooks del ciclo de vida (OnModuleInit, OnApplicationBootstrap)
NestJS expone interfaces de ciclo de vida que se ejecutan en momentos clave del arranque. Para tareas que dependen de varios providers ya inicializados, OnModuleInit y OnApplicationBootstrap son alternativas más limpias que un useFactory complejo:
import { Injectable, OnModuleInit, OnApplicationBootstrap, Logger } from '@nestjs/common';
@Injectable()
export class WarmupService implements OnModuleInit, OnApplicationBootstrap {
private readonly logger = new Logger('Warmup');
constructor(
private readonly cache: CacheService,
private readonly featureFlags: FeatureFlagsService,
) {}
async onModuleInit() {
this.logger.log('Cargando feature flags iniciales...');
await this.featureFlags.refresh();
}
async onApplicationBootstrap() {
this.logger.log('Precalentando caché de catálogo...');
await this.cache.warmup(['products:catalog', 'taxonomy:tree']);
}
}
onModuleInit se ejecuta cuando el módulo del provider está construido; onApplicationBootstrap cuando todos los módulos terminan. Si el segundo lanza una excepción, el arranque falla y Kubernetes reinicia el pod.
Coordinación con OnApplicationShutdown y enableShutdownHooks
Para cierres ordenados (drenar conexiones HTTP, cerrar pools de BD, hacer flush de logs) es esencial habilitar los shutdown hooks en main.ts:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks(); // SIGTERM, SIGINT
await app.listen(3000);
}
@Injectable()
export class DatabaseModule implements OnApplicationShutdown {
constructor(@Inject('PG_POOL') private readonly pool: Pool) {}
async onApplicationShutdown(signal?: string) {
this.pool.end(); // cierra conexiones tras drenar in-flight
}
}
En despliegues Kubernetes, este patrón evita errores connection terminated unexpectedly durante rolling updates: el pod recibe SIGTERM, NestJS dispara OnApplicationShutdown, los providers cierran recursos y el proceso termina limpio antes del kill duro a los 30 segundos.
Buenas prácticas en producción
- Health probes correlacionadas: el endpoint
/healthde Kubernetes solo devuelve 200 cuando todos los providers asíncronos hayan completado sus comprobaciones (@nestjs/terminus). - Retries con backoff: para conexiones a Redis o RabbitMQ, envolver
useFactoryconretry({ retries: 5, factor: 2 })evita CrashLoopBackOff por arranques cuando el servicio externo aún no está listo. - Timeouts duros: limitar el tiempo de inicialización (
Promise.racecon unsetTimeoutde 30s) para detectar deadlocks de configuración temprano. - Logs con contexto: cada provider asíncrono debe loguear
inicioycompletado en X ms. Es la forma más rápida de diagnosticar arranques lentos. - Sin lógica de negocio: las factorías inicializan recursos. Si necesitas lógica condicional con varios pasos, hazlo en un servicio que se invoca desde
OnApplicationBootstrap.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Nest
Documentación oficial de Nest
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Nest es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Nest
Explora más contenido relacionado con Nest y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender qué son los providers asíncronos y su diferencia con los síncronos.
- Identificar casos de uso comunes en aplicaciones reales.
- Implementar providers asíncronos con useFactory e inject.
- Manejar errores durante la inicialización para evitar fallos de arranque.
- Inyectar y utilizar providers asíncronos en servicios NestJS.