NestJS tip: how to inject multiple versions of the same provider into one module (e.g.: many Axios instances)
Tags: node, nestjs, typescript, javascript
Published: 2023-07-09 23:23:12

> for NestJS v8, v9 and v10

## What?

Let's say you want to use multiple "versions" of the same NestJS provider that is created by a 3rd-party lib that you don't control.

For example, when using the [`HttpModule` module](https://docs.nestjs.com/techniques/http-module) from  `@nestjs/axios` we can use the dynamic module `HttpModule.register()` to configure our Axios instance. That module exposes a `HttpService` that has such configuration. But what if we want to have many Axios instances, each one with its own config and import everything in one module?

We **can't do** the following because there's no way to distinguish between the two `HttpService` providers registered since they live under the same injection token, the class reference `HttpService`, although we have two instances of that `HttpService` class:

```ts
@Module({
  imports: [
    HttpModule.register({
      baseURL: 'https://api.example.com',
    }),
    HttpModule.register({
      baseURL: 'https://api.another.example.com',
    }),
  ],
})
export class AppModule {
  constructor(
    private readonly httpService: HttpService,
  ) {
    console.log(this.httpService.axiosRef.defaults.baseURL) // https://api.example.com
  }
}
```

The only idiomatic way to achieve that AFIAK is by creating a wrapper module for each configuration. That module will import `HttpModule.register()` and expose the `HttpService` provider via another injection token, so we could inject that provider along with others instances of `HttpService` as usual.

## How?

Like this:

- `app.module.ts` -- we want to inject multiple HTTP services here

```ts
import { Module } from '@nestjs/common'
import { CatApiModule, CatApiHttpService } from './cat-api'

@Module({
  imports: [
    CatApiModule,
    DogApiModule,
  ],
})
export class AppModule {
  constructor(
    private catApi: CatApiHttpService,
    private dogApi: DogApiHttpService,
  ) {}
}
```

- `cat-api.module.ts` -- our wrapper module. Responsible for creating a HTTP service client for the cats API

```ts
import { Module, OnModuleInit } from '@nestjs/common'
import { HttpModule, HttpService } from '@nestjs/axios'
import { CatApiHttpService } from './cat-api-http.service'

@Module({
  imports: [
    HttpModule.register({ // line A
      timeout: 1000,
      maxRedirects: 2,
      baseURL: 'https://http.cat',
    }),
  ],
  providers: [
    {
      provide: CatApiHttpService,
      useExisting: HttpService, // line B
    }
  ],
  exports: [CatApiHttpService], // line C
})
export class CatApiModule implements OnModuleInit {
  constructor(private readonly httpService: HttpService) {}

  // Defining others configuration for our Axios instance
  onModuleInit() {
    this.httpService.axiosRef.defaults.headers.common['Accept'] = 'application/json'
  }
}
```

- `cat-api-http.service.ts` -- the _interface_ on which consumers of `CatApiModule` will rely on. The implementation itself is up to `HttpService`
```ts
import { HttpService } from '@nestjs/axios'

export abstract class CatApiHttpService extends HttpService {}
```

The `DogApiHttpService` one would follow the same pattern as shown above.

## How it works

When importing the dynamic module `HttpModule.register()` (**line A**), we'll have the `HttpService` provider available to use in the `CatApiModule` module.

Since we don't want to expose that provider with the same injection token due to posible name collisions, we can leverage on the [`useExisting`](https://docs.nestjs.com/fundamentals/custom-providers#alias-providers-useexisting) alias provider (**line B**) to tell to NestJS that we now have 2 providers, one is just an alias to the other.

Then we are exposing the `CatApiHttpService` token instead of `HttpService` as a [proxy](https://refactoring.guru/design-patterns/proxy) to consume the `HttpService` provider (**line C**).

You can see that `CatApiHttpService` is an [abstract class](https://www.typescriptlang.org/docs/handbook/2/classes.html#abstract-classes-and-members) (a TypeScript feature). This is a way to tell to developers that that class isn't supposed to be initialized. Also, we are using concrete classes here to avoid using the `@Inject()` utility while injecting the `HttpService`.