DEV Community

Micael Levi L. C.
Micael Levi L. C.

Posted on • Updated on

NestJS tip: how to inject multiple versions of the same provider into one module (e.g.: many Axios instances)

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 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:

@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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
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,
  ) {}
}
Enter fullscreen mode Exit fullscreen mode
  • cat-api.module.ts -- our wrapper module. Responsible for creating a HTTP service client for the cats API
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'
  }
}
Enter fullscreen mode Exit fullscreen mode
  • cat-api-http.service.ts -- the interface on which consumers of CatApiModule will rely on. The implementation itself is up to HttpService
import { HttpService } from '@nestjs/axios'

export abstract class CatApiHttpService extends HttpService {}
Enter fullscreen mode Exit fullscreen mode

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 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 to consume the HttpService provider (line C).

You can see that CatApiHttpService is an abstract class (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.

Top comments (3)

Collapse
 
kostyatretyak profile image
Костя Третяк

Yes, a provider with the useExisting property is used for aliases. In my opinion, the name of the property useExisting is not very clear describing the behavior of such a provider. At one time, I proposed to rename it to useToken, but the Angular team did not want to do it (it was done in tsyringe instead).

Collapse
 
jmcdo29 profile image
Jay McDoniel

Interesting use of the abstract class to guarantee an easier way to work with the automated dependency injection. More straightforward than needing to use @Inject('CatApi') private readonly catApi: HttpService. Very nice job

Collapse
 
kuro091 profile image
Kuro091

This saved me today! Thank you