Moduły ładowane leniwie i przełączniki funkcji
Ładuj opcjonalne moduły funkcji na żądanie za pomocą LazyModuleLoader, aby skrócić czas uruchamiania.
Moduły ładowane leniwie i przełączniki funkcji to bezpłatna lekcja NestJS Enterprise Backend APIs na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej NestJS Enterprise Backend APIs, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs NestJS Enterprise Backend APIs zawiera 4 lekcji w sumie.
Części tej lekcji nie zostały jeszcze przetłumaczone i są wyświetlane po angielsku.
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.
Ucz się TypeScript dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 20
- Lekcje
- 76
Często zadawane pytania
Czy lekcja „Moduły ładowane leniwie i przełączniki funkcji” jest bezpłatna?
Tak — pełny tekst „Moduły ładowane leniwie i przełączniki funkcji” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu NestJS Enterprise Backend APIs, przejdź na CoddyKit PRO. Kurs NestJS Enterprise Backend APIs zawiera 4 lekcji w sumie.
Co nauczysz się w „Moduły ładowane leniwie i przełączniki funkcji”?
Ładuj opcjonalne moduły funkcji na żądanie za pomocą LazyModuleLoader, aby skrócić czas uruchamiania. Ćwiczysz NestJS Enterprise Backend APIs z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć NestJS Enterprise Backend APIs?
Nie wymagamy żadnego doświadczenia. NestJS Enterprise Backend APIs w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Moduły ładowane leniwie i przełączniki funkcji”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji NestJS Enterprise Backend APIs?
Tak. Każda lekcja NestJS Enterprise Backend APIs zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Porty i adaptery dla izolacji domeny
- Dynamiczna rejestracja dostawców za pomocą DiscoveryService
- Moduły ładowane leniwie i przełączniki funkcji
- Punkty rozszerzeń za pomocą Module Reference API