Creating Dynamic Modules in Nest JS Part-2
Published: 2022-03-31 15:58:48

## Creating Dynamic Modules in Nest JS Part-2

Please checkout Part-1 of this blog series before moving to Part-2 to get basic idea about dynamic Modules. here is the link
https://dev.to/tkssharma/creating-dynamic-modules-in-nest-js-part-1-2n0d

Code :
https://github.com/tkssharma/blogs/tree/master/nestjs-dynamic-module

I am starting just after finishing part of Part-1 of this blog.
Okay we have a use-case of creating External HTTP client as a nestjs dynamic Module, This Module will act as a http service using which we can make api calls same as axios or httpClient 

This is just for Demo and based on this we can create other nestjs dynamic Modules which can be plugged anywhere in any project 
Our final Goal to have something like this 

We should be able to expose all different methods like forRoot and forRootAsync from dynamic Module

`forRootAsync` should return Dynamic Module
```javascript 
    HttpClientModule.forRootAsync({
      imports: [AppConfigModule],
      inject: [AppConfigService],
      useFactory: (config: AppConfigService) => ({
        apiUrl: config.platformApi.baseUrl,
        apiKey: config.platformApi.apiKey,
      }),
    }),
```

Lets get started 

- Using this Module we want to expose service methods which can deal with http calls to external world
- we need a plain ES6 service which we can use with Providers 
- we need HttpClient Module to have all these methods as static `forRootAsync` and `forRoot`
- Injectable Providers and Tokens we need 

we will write service which wil get HttpClientModule options and will use its methods 
For a HttpClient module, options can be a url and api key or any custom header we want to pass in api calls 

```javascript
export class HttpClientService {
  private readonly apiUrl: string = "";
  private readonly apiKey: string = "";

  constructor(
    @Inject(HTTP_CLIENT_MODULE_OPTIONS)
    private readonly options: HttpClientModuleOptions
  ) {
    this.apiUrl = this.options.apiUrl;
    this.apiKey = this.options.apiKey;
  }

  public async fetchData(method: string, payload?: any) {
    return axios({
      {
        method,
        url: `${this.apiUrl}/health`,
        data,
        headers: {
        "Content-Type": "application/json",
          Authorization: `Bearer ${this.apiKey}`,
        }
      }
    );
  }
}
```

```javascript
export const HTTP_CLIENT_MODULE_OPTIONS = "HttpClientModuleOptions";
export const HTTP_CLIENT_TOKEN = "HttpClientToken";
export const HTTP_CLIENT_MODULE = "HttpClientModule";
```

Create a provider which can take `HttpClientModuleOptions` and return use a provider, Provider is using Injectable Token HTTP_CLIENT_TOKEN and value for that Injectable token is instance of HttpClientService service

```javascript
export function createHttpClientProvider(
  options: HttpClientModuleOptions
): Provider {
  return {
    provide: HTTP_CLIENT_TOKEN,
    useValue: getHttpClientModuleOptions(options),
  };
}

export const getHttpClientModuleOptions = (
  options: HttpClientModuleOptions
): HttpClientService => new HttpClientService(options);
```

Now we can use this `createHttpClientProvider` function in HttpClientModule for adding Providers
Here is the important Part we are creating static methods forRoot and forRootAsync both methods 
should return module like structure 

```javascript
   {
      module: HttpClientModule,
      imports: options.imports,
      providers: [...this.createAsyncProviders(options), provider],
      exports: [provider],
    }
```    

```javascript
@Global()
@Module({})
export class HttpClientModule {
  public static forRoot(options: HttpClientModuleOptions): DynamicModule {
    const provider: Provider = createHttpClientProvider(options);
    return {
      module: HttpClientModule,
      providers: [provider],
      exports: [provider],
    };
  }

  public static forRootAsync(
    options: HttpClientModuleAsyncOptions
  ): DynamicModule {
    const provider: Provider = {
      inject: [HTTP_CLIENT_MODULE_OPTIONS],
      provide: HTTP_CLIENT_TOKEN,
      useFactory: async (options: HttpClientModuleOptions) =>
        getHttpClientModuleOptions(options),
    };

    return {
      module: HttpClientModule,
      imports: options.imports,
      providers: [...this.createAsyncProviders(options), provider],
      exports: [provider],
    };
  }

  private static createAsyncProviders(
    options: HttpClientModuleAsyncOptions
  ): Provider[] {
    if (options.useExisting || options.useFactory) {
      return [this.createAsyncOptionsProvider(options)];
    }

    const useClass = options.useClass as Type<HttpClientModuleFactory>;

    return [
      this.createAsyncOptionsProvider(options),
      {
        provide: useClass,
        useClass,
      },
    ];
  }

  private static createAsyncOptionsProvider(
    options: HttpClientModuleAsyncOptions
  ): Provider {
    if (options.useFactory) {
      return {
        provide: HTTP_CLIENT_MODULE_OPTIONS,
        useFactory: options.useFactory,
        inject: options.inject || [],
      };
    }

    const inject = [
      (options.useClass ||
        options.useExisting) as Type<HttpClientModuleFactory>,
    ];

    return {
      provide: HTTP_CLIENT_MODULE_OPTIONS,
      useFactory: async (optionsFactory: HttpClientModuleFactory) =>
        await optionsFactory.createHttpModuleOptions(),
      inject,
    };
  }
}
```

Lets de-code the forRoot Implementation Here we are returning DynamicModule and its using provider returned from createHttpClientProvider and exporting same, createHttpClientProvider is nothing but instance of httpClientService 

```javascript
 public static forRoot(options: HttpClientModuleOptions): DynamicModule {
    const provider: Provider = createHttpClientProvider(options);
    return {
      module: HttpClientModule,
      providers: [provider],
      exports: [provider],
    };
  }
  // createHttpClientProvider will return this 
  {
    provide: HTTP_CLIENT_TOKEN,
    useValue: new HttpClientService(options)
  }
``` 

Variant Forms of Asynchronous Options Providers

### Asynchronous providers
At times, the application start should be delayed until one or more asynchronous tasks are completed. For example, you may not want to start accepting requests until the connection with the database has been established. You can achieve this using asynchronous providers.
https://docs.nestjs.com/fundamentals/custom-providers

### useClass
```javascript
@Module({
  imports: [
    HttpClientModule.forRootAsync({ useClass: ConfigService})
  ]
})
```
### useFactory
```javascript
@Module({
  imports: [HttpClientModule.forRootAsync({
    useFactory: () => {
      return {
        host: "localhost",
        port: 5432,
        database: "nest",
        user: "john",
        password: "password"
      }
    }
  })]
})
```

### useExisting
```javascript
@Module({
  imports: [HttpClientModule.registerAsync({
    useExisting: ConfigService
  })]
})
```

### Supporting Multiple Async Options Providers Techniques

We're in the home stretch. We're going to focus now on generalizing and optimizing our forRootAsync() method to support the additional techniques described above. When we're done, our module will support all three techniques:

- useClass - to get a private instance of the options provider.
- useFactory - to use a function as the options provider.
- useExisting - to re-use an existing (shared, SINGLETON) service as the options provider.
Lets check the code for all these cases 

```javascript
  public static forRootAsync(
    options: HttpClientModuleAsyncOptions
  ): DynamicModule {
    const provider: Provider = {
      inject: [HTTP_CLIENT_MODULE_OPTIONS],
      provide: HTTP_CLIENT_TOKEN,
      useFactory: async (options: HttpClientModuleOptions) =>
        getHttpClientModuleOptions(options),
    };

    return {
      module: HttpClientModule,
      imports: options.imports,
      providers: [...this.createAsyncProviders(options), provider],
      exports: [provider],
    };
  }
```

Now as we know options object can be of these different type so we have to handle that 

```javascript
  private static createAsyncProviders(
    options: HttpClientModuleAsyncOptions
  ): Provider[] {
    if (options.useExisting || options.useFactory) {
      return [this.createAsyncOptionsProvider(options)];
    }

    const useClass = options.useClass as Type<HttpClientModuleFactory>;

    return [
      this.createAsyncOptionsProvider(options),
      {
        provide: useClass,
        useClass,
      },
    ];
  }
  ```

Lets also have a look on HttpClientModuleAsyncOptions with all there name options 

```javascript

export interface HttpClientModuleOptions {
  apiUrl: string;
  apiKey: string;
}

export interface HttpClientModuleFactory {
  createHttpModuleOptions: () =>
    | Promise<HttpClientModuleOptions>
    | HttpClientModuleOptions;
}

export interface HttpClientModuleAsyncOptions
  extends Pick<ModuleMetadata, "imports"> {
  inject?: any[];
  useClass?: Type<HttpClientModuleFactory>;
  useExisting?: Type<HttpClientModuleFactory>;
  useFactory?: (
    ...args: any[]
  ) => Promise<HttpClientModuleOptions> | HttpClientModuleOptions;
}
```

After we have all these ready we can use this module in all different ways like 

```javascript
HttpClientModule.forRootAsync({
      imports: [AppConfigModule],
      inject: [AppConfigService],
      useFactory: (config: AppConfigService) => ({
        apiUrl: config.platformApi.baseUrl,
        apiKey: config.platformApi.apiKey,
      }),
    })
```

## Another option 

```javascript
@Module({
  imports: [HttpClientModule.forRootAsync({
    useExisting: AppConfigService
  })]
})
We could expect a dynamic module to be constructed with the following properties:
{
  module: HttpClientModule,
  imports: [],
  providers: [
    {
      provide: HTTP_CLIENT_MODULE_OPTIONS,
      useFactory: async (optionsFactory: HttpClientModuleFactory) =>
        await optionsFactory.createHttpModuleOptions(),
      inject,
    },
  ],
}
```

## Conclusion 
The patterns is used in all popular modules like @nestjs/jwt, @nestjs/passport and @nestjs/typeorm. Hopefully you now see not only how powerful these patterns are, but how you can make use of them in your own project.


## References 

- https://dev.to/nestjs/advanced-nestjs-how-to-build-completely-dynamic-nestjs-modules-1370 
- https://docs.nestjs.com/fundamentals/dynamic-modules
- https://docs.nestjs.com/
- Blog Originally published at tkssharma.com