โมดูลโหลดแบบขี้เกียจและสวิตช์ฟีเจอร์
โหลดโมดูลฟีเจอร์เสริมเมื่อจำเป็นด้วย LazyModuleLoader เพื่อลดต้นทุนการเริ่มต้นระบบ
โมดูลโหลดแบบขี้เกียจและสวิตช์ฟีเจอร์ เป็นบทเรียน NestJS Enterprise Backend APIs ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน NestJS Enterprise Backend APIs และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส NestJS Enterprise Backend APIs มีบทเรียนทั้งหมด 4 บทเรียน
บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ
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.
เรียนรู้ TypeScript ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 20
- บทเรียน
- 76
คำถามที่พบบ่อย
บทเรียน “โมดูลโหลดแบบขี้เกียจและสวิตช์ฟีเจอร์” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “โมดูลโหลดแบบขี้เกียจและสวิตช์ฟีเจอร์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส NestJS Enterprise Backend APIs ให้อัปเกรดเป็น CoddyKit PRO คอร์ส NestJS Enterprise Backend APIs มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “โมดูลโหลดแบบขี้เกียจและสวิตช์ฟีเจอร์”
โหลดโมดูลฟีเจอร์เสริมเมื่อจำเป็นด้วย LazyModuleLoader เพื่อลดต้นทุนการเริ่มต้นระบบ คุณปฏิบัติ NestJS Enterprise Backend APIs ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน NestJS Enterprise Backend APIs หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน NestJS Enterprise Backend APIs บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “โมดูลโหลดแบบขี้เกียจและสวิตช์ฟีเจอร์” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน NestJS Enterprise Backend APIs นี้ได้ไหม
ได้ บทเรียน NestJS Enterprise Backend APIs ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- พอร์ตและอะแดปเตอร์เพื่อแยกโดเมน
- การลงทะเบียนตัวให้บริการแบบไดนามิกด้วย DiscoveryService
- โมดูลโหลดแบบขี้เกียจและสวิตช์ฟีเจอร์
- จุดขยายด้วย API อ้างอิงโมดูล