Skip to content

HttpHealthIndicator accidently resolves HttpService with configured baseUrl #2667

@DavidAttar

Description

@DavidAttar

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

I created custom ApiService using the following article:
https://dev.to/micalevisk/nestjs-tip-how-to-inject-multiple-versions-of-the-same-provider-into-one-module-eg-many-axios-instances-5agc

// custom http client is initialized like that:

const CUSTOM_API_SERVICE = Symbol('CUSTOM_API_SERVICE');

@Module({
  imports: [
    HttpModule.register({
      baseURL: 'https://cats.com',
    }),
  ],
  providers: [{ provide: CUSTOM_API_SERVICE, useExisting: HttpService }],
  exports: [CUSTOM_API_SERVICE],
})
export class CustomApiModule {}

The problem

If CustomApiModule is initialized before TerminusModule, HealthIndicator will unintendedly retrieve the baseURL of CustomApiModule.

This happens due to getHttpService() method in HttpHealthIndicator implementation:
getHttpService() - resolves the HttpService using moduleRef.get(HttpService, { strict: false }); which crawls Nest dependency tree until it finds an instance of HttpService, the problem is that it firstly encounters the CUSTOM_API_SERVICE instance (yes even though the token itself is called CUSTOM_API_SERVICE)

I have fixed the problem in my code by providing to pingCheck a custom httpService via method options, but it does feel like an bug.

Thanks <3

Minimum reproduction code

import { Injectable, Module, OnModuleInit } from '@nestjs/common';
import { HttpModule, HttpService } from '@nestjs/axios';
import {
  HealthCheckService,
  HttpHealthIndicator,
  TerminusModule,
} from '@nestjs/terminus';

const CUSTOM_HTTP_CLIENT_TOKEN = Symbol('A_TOKEN');

@Module({
  imports: [
    HttpModule.registerAsync({
      useFactory: () => ({
        baseURL: 'https://cats.com',
      }),
    }),
  ],
  providers: [{ provide: CUSTOM_HTTP_CLIENT_TOKEN, useExisting: HttpService }],
  exports: [CUSTOM_HTTP_CLIENT_TOKEN],
})
class CustomApiModule {}

@Injectable()
class HealthCheckedService implements OnModuleInit {
  constructor(
    private health: HealthCheckService,
    private http: HttpHealthIndicator
  ) {}

  async onModuleInit() {
    await this.pingHealthCheck();
  }

  private async pingHealthCheck() {
    await this.health.check([
      () => this.http.pingCheck('some-ping', 'https://dogs.com'), // debugging HealthIndicator pingCheck will show that axiosRef.defaults has baseURL of 'https://cats.com' 
    ]);
  }
}

@Module({
  imports: [TerminusModule],
  providers: [HealthCheckedService],
})
class HealthCheckedModule {}

@Module({
  imports: [CustomApiModule],
})
class SomeModule {}

@Module({})
@Module({
  imports: [SomeModule, HealthCheckedModule],
})
export class AppTest {}

// my solution
@Injectable()
class HealthCheckedService implements OnModuleInit {
  constructor(
    private health: HealthCheckService,
    private http: HttpHealthIndicator,
    private httpService: HttpService
  ) {}

  async onModuleInit() {
    await this.pingHealthCheck();
  }

  private async pingHealthCheck() {
    await this.health.check([
      () =>
        this.http.pingCheck('some-ping', 'https://dogs.com', {
          httpClient: this.httpService, // fixes the problem
        }),
    ]);
  }
}

Steps to reproduce

  1. run the code above (can import AppTest into AppModule)
  2. debug health-indicator/http.health pingCheck method and look for axiosRef.defaults which will show improper baseUrl

Expected behavior

inject proper HttpService instead of trying to retrieve it using moduleRef.get(nestjs.axios.HttpService)

Package version

8.0.6

NestJS version

9.4.0

Node.js version

16.17.0

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions