dependency-injectionnestjslazy-loading

How to lazy resolve a module in NestJS?


I'm reading this NestJS documentation on lazy loading

I'm trying to use nestjs modules outside of nestjs in a serverless environment. The documentation references that people do this.

The code below works to get the AppService with all of it's dependencies injected

"use server";

import { NestFactory } from "@nestjs/core";
import { AppModule } from "@api/app.module";
import { AppService } from "@api/app.service";


export async function getHelloAction() {
    const app = await NestFactory.create(AppModule);
    const appService = app.get(AppService);
    const result = appService.getHello();
    await app.close();
    return result;
}

My understanding is that when I call NestFactory.create(), all modules are by default instantiated.

So I'd like to lazily resolve the modules only when they are needed even if my Module class has a lot of extra modules.

Based on the documentation, it seems I need to write something like the code below:

"use server";

import { AppModule } from "@api/app.module";
import { NestFactory } from "@nestjs/core";
import { LazyModuleLoader } from "@nestjs/core";

export async function getLazyHelloAction() {
  const app = await NestFactory.create(AppModule);

  // At this point, AppModule and all its dependencies are instantiated

  const lazyModuleLoader = app.get(LazyModuleLoader);

  // Later, when you need to load an additional module

    const { AppService } = await import('@api/app.service');
    const moduleRef = await lazyModuleLoader.load(() => AppService);
    const appService = moduleRef.get(AppService);
    // Now LazyModule is loaded and its providers are available


    const result = appService.getHello();
    return result;
}

The above works, but I get this error

 ⨯ Error: Classes annotated with @Injectable(), @Catch(), and @Controller() decorators must not appear in the "imports" array of a module.
Please remove "AppService" (including forwarded occurrences, if any) from all of the "imports" arrays.

Scope []

    at async getLazyHelloAction (./src/app/lazy-action.ts:23:23)

More importantly, I don't understand how this lazy loads the module. If we further clarify the terms and call it "registering" a module and "resolving" a module, what does lazy load mean here?

In the lazily loaded code above, I'm still calling NestFactory.create() so I assume all modules are instantiated. So the startup time will be very long if the module is large.

Does lazilyLoading in this context mean, the AppService IS NOT inside the AppModule and it's lazily/manually registered when needed? That seems a bit counter intuitive in terms of the term lazyLoad and doesn't really make sense in terms of usefulness to me.

How can I have the modules imported and only instantiated when I need them like in a typical di container?


Solution

  • So I realize lazy loading means both registering and resolving. The code below works to lazily load a module and use a service inside the module. Previously I was lazily loading a service which caused the error.

    Lazy Loading

    "use server";
    
    import { AppModule } from "@api/app.module";
    import { NestFactory } from "@nestjs/core";
    import { LazyModuleLoader } from "@nestjs/core";
    
    export async function getLazyHelloAction() {
        // AppModule should have minimal stuff in it
        const app = await NestFactory.create(AppModule);
    
        // This is a separate module as written below.
        // It isn't registered in AppModule
        const lazyModuleLoader = app.get(LazyModuleLoader);
    
        // We register and resolve this LazyModule when needed
        const { LazyModule } = await import("@api/lazy/lazy.module");
        const moduleRef = await lazyModuleLoader.load(() => LazyModule);
    
        // Then we get the service from this module
        const { LazyService } = await import("@api/lazy/lazy.service");
        const lazyService = moduleRef.get(LazyService);
    
    
        const result = lazyService.getLazyHello();
        return result;
    }
    
    // lazy.module
    import { Logger, Module } from '@nestjs/common';
    import { LazyService } from './lazy.service';
    
    @Module({
      providers: [LazyService, Logger],
      exports: [LazyService],
    })
    export class LazyModule {}
    
    // lazy.service
    import { Injectable, Logger } from '@nestjs/common';
    
    @Injectable()
    export class LazyService {
      constructor(private readonly logger: Logger) {}
    
      getLazyHello(): string {
        const message = 'Lazy Hello World!';
        this.logger.log(message);
        return message;
      }
    }