En esta página
Providers y servicios — inyección de dependencias
¿Qué es un provider en NestJS?
Un provider es cualquier clase que puede ser inyectada como dependencia en otras clases por el sistema de IoC (Inversion of Control) de NestJS. Los servicios son el tipo más común de provider, pero también lo son los repositorios, las factories, los helpers y prácticamente cualquier clase que necesite ser compartida.
La inyección de dependencias (DI, Dependency Injection) es un patrón de diseño donde las dependencias de una clase se le proporcionan desde el exterior en lugar de que la clase las cree por sí misma. NestJS tiene un contenedor de DI integrado que gestiona la creación y el ciclo de vida de todos los providers automáticamente.
El decorador @Injectable
Cualquier clase que quieras que el contenedor de NestJS pueda inyectar debe estar decorada con @Injectable:
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsuariosService {
private usuarios: Usuario[] = [];
findAll(): Usuario[] {
return this.usuarios;
}
}Este decorador hace dos cosas: marca la clase como "inyectable" y emite metadatos de TypeScript que NestJS usa para resolver las dependencias del constructor automáticamente mediante reflect-metadata.
Inyectando un servicio en un controlador
Para usar un servicio en un controlador, simplemente decláralo en el constructor:
@Controller('usuarios')
export class UsuariosController {
constructor(private readonly usuariosService: UsuariosService) {}
@Get()
findAll() {
return this.usuariosService.findAll();
}
}NestJS resolverá automáticamente la dependencia gracias a los metadatos de TypeScript. El modificador readonly y private es una convención que garantiza que el servicio no sea reasignado accidentalmente.
Excepciones HTTP integradas
NestJS proporciona un conjunto de excepciones HTTP listas para usar que automáticamente generan respuestas de error apropiadas:
import {
NotFoundException, // 404
BadRequestException, // 400
UnauthorizedException, // 401
ForbiddenException, // 403
ConflictException, // 409
InternalServerErrorException, // 500
UnprocessableEntityException, // 422
} from '@nestjs/common';
@Injectable()
export class ProductosService {
findOne(id: number): Producto {
const producto = this.db.find(id);
if (!producto) {
throw new NotFoundException(`No se encontró el producto con ID ${id}`);
}
return producto;
}
}La respuesta automática tendrá este formato JSON:
{
"statusCode": 404,
"message": "No se encontró el producto con ID 42",
"error": "Not Found"
}Proveedores personalizados
La forma más simple de registrar un provider es simplemente añadir la clase a la lista providers de un módulo. Pero NestJS también ofrece una sintaxis extendida para casos más complejos:
useClass — Implementaciones intercambiables
import { Module } from '@nestjs/common';
import { AnalyticsService } from './analytics.service';
import { MockAnalyticsService } from './mock-analytics.service';
@Module({
providers: [
{
provide: AnalyticsService,
useClass: process.env['NODE_ENV'] === 'test'
? MockAnalyticsService
: AnalyticsService,
},
],
})
export class AnalyticsModule {}useValue — Constantes y valores estáticos
Ideal para configuración, clientes de librerías externas o datos mock para testing:
const mockProductosService = {
findAll: () => [{ id: 1, nombre: 'Producto mock' }],
findOne: (id: number) => ({ id, nombre: 'Producto mock' }),
};
@Module({
providers: [
{
provide: ProductosService,
useValue: mockProductosService,
},
],
})
export class TestModule {}useFactory — Creación asíncrona con lógica
El patrón más flexible, permite crear providers con lógica de inicialización asíncrona:
@Module({
providers: [
{
provide: 'REDIS_CLIENT',
useFactory: async (): Promise<Redis> => {
const client = new Redis({
host: process.env['REDIS_HOST'],
port: parseInt(process.env['REDIS_PORT'] ?? '6379', 10),
});
await client.ping(); // verifica la conexión antes de continuar
return client;
},
},
],
})
export class CacheModule {}useExisting — Alias de providers
Crea un alias para un provider existente, útil cuando quieres que una interfaz resuelva a una implementación concreta:
@Module({
providers: [
LoggerService,
{
provide: 'ILogger',
useExisting: LoggerService, // 'ILogger' y LoggerService apuntan a la misma instancia
},
],
})
export class AppModule {}Scopes de vida de los providers
Por defecto, todos los providers en NestJS son singletons: se crean una vez cuando la aplicación arranca y se reutiliza la misma instancia en toda la vida de la aplicación. Pero puedes cambiar este comportamiento:
import { Injectable, Scope } from '@nestjs/common';
// DEFAULT — singleton (una instancia para toda la app)
@Injectable({ scope: Scope.DEFAULT })
export class ProductosService {}
// REQUEST — nueva instancia por cada petición HTTP
@Injectable({ scope: Scope.REQUEST })
export class AuditoriaService {}
// TRANSIENT — nueva instancia en cada punto de inyección
@Injectable({ scope: Scope.TRANSIENT })
export class ContadorService {}Cuándo usar cada scope:
DEFAULT(singleton): el 95% de los casos. Servicios stateless que acceden a bases de datos, APIs externas, etc.REQUEST: cuando necesitas datos específicos de la petición dentro del service (usuario actual, tenant ID, trace ID).TRANSIENT: cuando necesitas garantizar que cada consumidor tenga su propia instancia (e.g. un buffer de escritura que no debe compartirse).
El scope se propaga
Una cosa importante: el scope se propaga hacia arriba. Si un servicio con scope REQUEST es inyectado en un servicio con scope DEFAULT, el servicio DEFAULT también se convierte en REQUEST. Esto puede tener implicaciones de rendimiento no deseadas.
// ⚠️ AuditoriaService (REQUEST) inyectado en ProductosService (DEFAULT)
// → ProductosService también pasa a ser REQUEST implícitamente
@Injectable()
export class ProductosService {
constructor(private readonly auditoriaService: AuditoriaService) {}
}Servicios asíncronos con OnModuleInit
Cuando un servicio necesita inicializarse de forma asíncrona al arrancar la aplicación (conexión a BD, carga de caché, etc.), implementa la interfaz OnModuleInit:
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
@Injectable()
export class DatabaseService implements OnModuleInit, OnModuleDestroy {
private connection: DatabaseConnection | null = null;
async onModuleInit(): Promise<void> {
this.connection = await createDatabaseConnection();
console.log('Base de datos conectada correctamente');
}
async onModuleDestroy(): Promise<void> {
await this.connection?.close();
console.log('Conexión a base de datos cerrada');
}
}Exportar servicios entre módulos
Un servicio solo puede ser inyectado en providers del mismo módulo, a menos que sea exportado. Para compartir un servicio entre módulos:
@Module({
providers: [EmailService],
exports: [EmailService], // ← ahora EmailService puede ser inyectado en otros módulos
})
export class EmailModule {}
// En otro módulo
@Module({
imports: [EmailModule], // ← importar el módulo da acceso a sus exports
providers: [UsuariosService],
})
export class UsuariosModule {}
// UsuariosService puede ahora inyectar EmailService
@Injectable()
export class UsuariosService {
constructor(private readonly emailService: EmailService) {}
}El sistema de DI de NestJS es uno de sus diferenciadores más importantes respecto a Express puro. Permite escribir código altamente desacoplado, testeable y mantenible. En la próxima lección, veremos cómo los Pipes complementan a los servicios validando los datos antes de que lleguen a la lógica de negocio.
Inicia sesión para guardar tu progreso