Tipos de scopes en inyección

Intermedio
Nest
Nest
Actualizado: 05/05/2026

Diagrama: tutorial-nest-injection-scopes

Scopes de inyección de dependencias

Los scopes en NestJS determinan el ciclo de vida de las instancias de servicios y controlan cuándo y cómo se crean, mantienen y destruyen. Por defecto, NestJS utiliza el patrón singleton para todos los servicios, pero ofrece diferentes opciones de scope para casos específicos donde necesites un comportamiento diferente.

Tipos de scopes disponibles

NestJS proporciona tres tipos principales de scopes de inyección:

  • DEFAULT (Singleton): Una única instancia del servicio se crea y comparte en toda la aplicación
  • REQUEST: Se crea una nueva instancia para cada petición HTTP entrante
  • TRANSIENT: Se crea una nueva instancia cada vez que se inyecta el servicio

Scope DEFAULT (Singleton)

El scope por defecto es el más eficiente en términos de memoria y rendimiento. Una sola instancia del servicio se mantiene durante toda la vida de la aplicación:

import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  private users: User[] = [];

  findAll(): User[] {
    return this.users;
  }

  create(user: User): User {
    this.users.push(user);
    return user;
  }
}

Este servicio mantiene el estado compartido entre todas las peticiones, lo que es ideal para servicios que gestionan datos globales o configuraciones.

Scope REQUEST

El scope REQUEST crea una nueva instancia del servicio para cada petición HTTP. Esto es útil cuando necesitas mantener datos específicos de cada petición:

import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestLoggerService {
  private logs: string[] = [];

  addLog(message: string): void {
    this.logs.push(`${new Date().toISOString()}: ${message}`);
  }

  getLogs(): string[] {
    return this.logs;
  }
}

En este ejemplo, cada petición tendrá su propia instancia del servicio con su propio array de logs, evitando que se mezclen los datos entre diferentes peticiones.

Scope TRANSIENT

El scope TRANSIENT crea una nueva instancia cada vez que el servicio se inyecta, incluso dentro de la misma petición:

import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.TRANSIENT })
export class UniqueIdService {
  private readonly id: string;

  constructor() {
    this.id = Math.random().toString(36).substring(7);
  }

  getId(): string {
    return this.id;
  }
}

Cada vez que este servicio se inyecte, se creará con un ID único, útil para casos donde necesitas garantizar instancias completamente independientes.

Configuración de scopes en controladores

Los controladores también pueden tener scopes, lo que afecta a todos los servicios que dependan de ellos:

import { Controller, Get, Scope } from '@nestjs/common';

@Controller({
  path: 'users',
  scope: Scope.REQUEST
})
export class UsersController {
  constructor(
    private readonly userService: UserService,
    private readonly loggerService: RequestLoggerService
  ) {}

  @Get()
  findAll() {
    this.loggerService.addLog('Fetching all users');
    return this.userService.findAll();
  }
}

Inyección de REQUEST object

Cuando utilizas scope REQUEST, puedes inyectar el objeto de petición directamente:

import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';

@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
  constructor(@Inject(REQUEST) private request: Request) {}

  getUserAgent(): string {
    return this.request.headers['user-agent'] || 'Unknown';
  }

  getRequestId(): string {
    return this.request.headers['x-request-id'] as string || 'no-id';
  }
}

Consideraciones de rendimiento

El uso de scopes diferentes al DEFAULT tiene implicaciones importantes:

  • REQUEST scope: Aumenta el uso de memoria y tiempo de procesamiento al crear nuevas instancias para cada petición
  • TRANSIENT scope: Genera la mayor sobrecarga al crear instancias constantemente
  • DEFAULT scope: Ofrece el mejor rendimiento pero comparte estado entre peticiones

Propagación de scopes

Cuando un servicio tiene un scope específico, todos los servicios que dependan de él heredarán automáticamente ese scope:

@Injectable({ scope: Scope.REQUEST })
export class DatabaseConnectionService {
  // Servicio con scope REQUEST
}

@Injectable() // Automáticamente se convierte en REQUEST scope
export class UserRepository {
  constructor(
    private dbConnection: DatabaseConnectionService
  ) {}
}

Esta propagación automática garantiza la consistencia en el ciclo de vida de las dependencias relacionadas.

Implicaciones del scope REQUEST en producción

Marcar un servicio como REQUEST activa una cascada con consecuencias importantes:

  1. No se puede inyectar en providers DEFAULT: cualquier servicio que dependa de uno REQUEST queda automáticamente promovido a REQUEST. Esto se puede propagar hasta AppModule si no se aísla bien.
  2. Cada request crea un sub-injector: NestJS instancia un mini contenedor de DI para esa request, lo que multiplica las allocations de memoria.
  3. No funciona con Cron ni BullMQ: los workers no tienen Request disponible, así que un servicio REQUEST lanza error fuera del ciclo HTTP.
  4. El Logger por defecto se reinicia: el contexto del logger se asocia al request actual, perdiendo el patrón singleton de los logs estructurados.

Para evitar la cascada, una técnica habitual es delegar el contexto request a una clase plana (no provider) que se construya manualmente con ContextIdFactory:

@Injectable()
export class TenantContext {
  private readonly storage = new AsyncLocalStorage<TenantData>();

  run<T>(data: TenantData, fn: () => T): T {
    return this.storage.run(data, fn);
  }

  get current(): TenantData | undefined {
    return this.storage.getStore();
  }
}

AsyncLocalStorage (Node.js nativo) permite mantener contexto por request sin necesidad de Scope.REQUEST, evitando la propagación. Es el patrón recomendado en aplicaciones multi-tenant grandes.

Cuándo usar cada scope

| Caso | Scope recomendado | Razón | |------|-------------------|-------| | Servicio de negocio (UserService, OrderService) | DEFAULT | Sin estado por request, máximo rendimiento | | Repositorio TypeORM | DEFAULT | Singleton; el EntityManager ya gestiona transacciones | | Servicio que necesita req.user o headers | REQUEST (o AsyncLocalStorage) | Aísla datos de cada petición | | Generador de IDs únicos por inyección | TRANSIENT | Cada cliente recibe instancia propia | | Cliente HTTP con cabeceras de tracing | REQUEST o middleware que añada headers globalmente | Propaga correlationId | | Cron job o consumidor de cola | DEFAULT | No hay request asociada |

Mantener DEFAULT por defecto y subir a REQUEST solo donde se requiera de forma demostrable es la regla de oro en proyectos NestJS de larga vida.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Nest

Documentación oficial de Nest
Alan Sastre - Autor del tutorial

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 scopes en la inyección de dependencias en NestJS. Identificar y diferenciar los tres tipos principales de scopes: DEFAULT (singleton), REQUEST y TRANSIENT. Saber cómo configurar scopes en servicios y controladores. Entender las implicaciones de rendimiento de cada tipo de scope. Conocer la propagación automática de scopes entre servicios dependientes.