Módulos carregados sob demanda e alternâncias de recursos
Carregue módulos opcionais sob demanda com LazyModuleLoader para reduzir o custo de inicialização.
Módulos carregados sob demanda e alternâncias de recursos é uma aula grátis de NestJS Enterprise Backend APIs no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de NestJS Enterprise Backend APIs, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de NestJS Enterprise Backend APIs inclui 4 aulas no total.
Partes desta aula ainda não foram traduzidas e aparecem em inglês.
Why Eager Loading Hurts Startup
By default, NestJS instantiates every module in your imports graph at bootstrap. For an enterprise API with dozens of optional features — a PDF exporter, a payment gateway, an AI scoring engine — that means paying the full provider-instantiation and connection-warmup cost before the app even accepts a request.
- Heavy SDKs (Stripe, AWS, gRPC clients) run their constructors eagerly.
- Modules a given deployment never uses still load.
- Cold-start latency grows linearly with the module graph.
The fix: load select feature modules lazily, only when first invoked.
The LazyModuleLoader
Nest ships a built-in LazyModuleLoader (from @nestjs/core). You inject it like any provider, then call load() with a factory that returns the module. Nest registers the module's providers on demand and caches the resulting reference for subsequent calls.
Key traits:
- Lazy modules are not listed in any
importsarray. - They do not register controllers, resolvers, or enhancers — only providers.
- The first
load()instantiates; later calls return the cachedModuleRef.
import { Injectable } from '@nestjs/common';
import { LazyModuleLoader } from '@nestjs/core';
@Injectable()
export class ReportsService {
constructor(private readonly lazyModuleLoader: LazyModuleLoader) {}
async generate(): Promise<void> {
const { PdfModule } = await import('./pdf/pdf.module');
const moduleRef = await this.lazyModuleLoader.load(() => PdfModule);
// moduleRef now exposes PdfModule's providers
}
}Resolving a Provider From the Lazy Module
The object returned by load() is a ModuleRef. Use its get() method to pull a concrete provider out of the freshly-loaded module. Because lazy modules are isolated, request a provider only after the module is loaded.
For request-scoped or transient providers use moduleRef.resolve() instead of get().
async generate(): Promise<Buffer> {
const { PdfModule } = await import('./pdf/pdf.module');
const moduleRef = await this.lazyModuleLoader.load(() => PdfModule);
const pdfService = moduleRef.get(PdfService);
return pdfService.render({ title: 'Invoice' });
}Dynamic import() Is What Saves the Bytes
The real startup win comes from pairing LazyModuleLoader.load() with a dynamic import(). A static top-level import pulls the module — and its heavy transitive deps — into the bootstrap bundle. A dynamic import() defers that file evaluation until the call runs.
- Static
import { PdfModule }at the top of the file = loaded at startup. await import('./pdf/pdf.module')inside the method = loaded on first use.
So always import the lazy module's file dynamically, never statically.
A Lazy Feature Module Definition
The lazy module itself is an ordinary @Module — there is nothing special in its decorator. What makes it lazy is purely how it is consumed (via LazyModuleLoader rather than an imports array).
Keep its providers self-contained so loading it does not drag in the whole app.
import { Module } from '@nestjs/common';
import { PdfService } from './pdf.service';
@Module({
providers: [PdfService],
exports: [PdfService],
})
export class PdfModule {}Caching Makes Repeated Loads Cheap
Nest internally keeps a registry of already-loaded lazy modules keyed by the factory result. So calling load() repeatedly with the same module class is effectively free after the first hit — no double instantiation, no duplicate connections.
This means you can safely call load() inline in a hot handler without guarding it yourself; the framework deduplicates. The one-time cost is paid on the first request that needs the feature.
Feature Toggles: Gate the Load
Feature toggles and lazy loading are a natural pair. Instead of conditionally registering modules at compile time, you check a flag at runtime and only load() the module when the flag is on. A disabled feature then costs nothing — not even its constructor.
- Flag from env, config service, or a remote flag provider (LaunchDarkly, Unleash).
- If the toggle is off, short-circuit before importing.
@Injectable()
export class ExportService {
constructor(
private readonly lazyModuleLoader: LazyModuleLoader,
private readonly flags: FeatureFlagService,
) {}
async export(payload: ExportDto) {
if (!this.flags.isEnabled('pdf-export')) {
throw new ForbiddenException('Feature disabled');
}
const { PdfModule } = await import('./pdf/pdf.module');
const ref = await this.lazyModuleLoader.load(() => PdfModule);
return ref.get(PdfService).render(payload);
}
}A Minimal Flag Service (Standalone)
A feature-flag check is just a deterministic lookup. Here is a framework-free version you can reason about and test in isolation — the same logic a Nest FeatureFlagService would wrap. It reads a flag map and falls back to a default when the key is unknown.
class FeatureFlags {
constructor(private readonly flags: Record<string, boolean>) {}
isEnabled(key: string, fallback = false): boolean {
return this.flags[key] ?? fallback;
}
}
const flags = new FeatureFlags({ 'pdf-export': true, 'ai-scoring': false });
console.log(flags.isEnabled('pdf-export')); // true
console.log(flags.isEnabled('ai-scoring')); // false
console.log(flags.isEnabled('unknown', true)); // true (fallback)Controllers and Enhancers Are Ignored
A critical limitation: when a module is loaded lazily, Nest registers its providers only. It deliberately skips:
controllers— no new HTTP routes appear.- Global guards, interceptors, pipes, filters declared in the module.
- GraphQL resolvers.
So a lazy module cannot add endpoints. Expose the feature through a controller in an eagerly-loaded module that delegates to the lazily-loaded provider.
@Controller('reports')
export class ReportsController {
constructor(private readonly reports: ReportsService) {}
@Post('pdf')
async pdf(@Body() dto: ExportDto) {
// controller is eager; PdfModule is loaded lazily inside the service
return this.reports.generate(dto);
}
}Warming Up vs Lazy: Pick Per Feature
Lazy loading trades a one-time first-request latency spike for a faster, lighter startup. That is the right deal for rarely used, expensive features. For features on the hot path, eager loading (or an explicit warm-up on onApplicationBootstrap) avoids penalizing the first user.
Decision guide:
- Lazy: heavy SDK, used by <X% of requests, optional per deployment.
- Eager: core domain, every request, latency-sensitive.
- Lazy + warm-up: heavy but predictably needed soon after boot.
@Injectable()
export class Warmer implements OnApplicationBootstrap {
constructor(private readonly lazyModuleLoader: LazyModuleLoader) {}
async onApplicationBootstrap() {
if (process.env.PRELOAD_PDF === 'true') {
const { PdfModule } = await import('./pdf/pdf.module');
await this.lazyModuleLoader.load(() => PdfModule); // pay cost now, off the request path
}
}
}Measuring the Win
Quantify before and after. Wrap bootstrap timing and the first lazy load() to confirm the trade-off is real for your workload.
- Startup time should drop by the cumulative constructor + connection cost of the deferred modules.
- First-call latency for the lazy feature absorbs that cost once.
- Watch P99 of the first request after deploy — that is where the deferred cost surfaces.
If the lazy feature is hit on nearly every request, the numbers will tell you to switch it back to eager.
const t0 = performance.now();
const { PdfModule } = await import('./pdf/pdf.module');
const ref = await this.lazyModuleLoader.load(() => PdfModule);
this.logger.log(`Lazy PdfModule ready in ${Math.round(performance.now() - t0)}ms`);Quick Check
You lazily load PdfModule via LazyModuleLoader. PdfModule declares a controller with a @Post('pdf') route. After loading, the route returns 404. What is the correct explanation and fix?
Recap
You learned how to shrink NestJS startup cost with on-demand modules:
LazyModuleLoader.load(() => SomeModule)instantiates a module's providers on first use and caches the result.- Pair it with a dynamic
import()so the module's file (and heavy deps) is never evaluated at bootstrap. - Resolve providers via
moduleRef.get()(orresolve()for scoped providers). - Feature toggles gate the load: a disabled feature costs nothing, not even a constructor.
- Lazy modules register no controllers, resolvers, or enhancers — delegate from an eager controller.
- Choose lazy for heavy, rarely-used, optional features; eager (or lazy + warm-up) for hot-path code. Measure startup and first-call latency to confirm the trade-off.
Perguntas Frequentes
A aula “Módulos carregados sob demanda e alternâncias de recursos” é grátis?
Sim — o texto completo de “Módulos carregados sob demanda e alternâncias de recursos” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de NestJS Enterprise Backend APIs, atualize para CoddyKit PRO. O curso de NestJS Enterprise Backend APIs inclui 4 aulas no total.
O que vou aprender em “Módulos carregados sob demanda e alternâncias de recursos”?
Carregue módulos opcionais sob demanda com LazyModuleLoader para reduzir o custo de inicialização. Você pratica NestJS Enterprise Backend APIs com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar NestJS Enterprise Backend APIs?
Nenhuma experiência prévia é necessária. NestJS Enterprise Backend APIs no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Módulos carregados sob demanda e alternâncias de recursos”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de NestJS Enterprise Backend APIs?
Sim. Cada aula de NestJS Enterprise Backend APIs inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Portas e adaptadores para isolamento do domínio
- Registro dinâmico de provedores com DiscoveryService
- Módulos carregados sob demanda e alternâncias de recursos
- Pontos de extensão com a API de referência de módulos