How can NestJS customize its built-in services such as ConfigService using a TypeScript interface?

The configuration documentation for NestJS provides an example of achieving type safety with the ConfigService by using an interface named EnvironmentVariables. This interface is then annotated during injection in the constructor like this:

constructor(private configService: ConfigService<EnvironmentVariables>) {...}
. However, I want to bind this interface permanently to the ConfigService without having to remember to import and annotate it at every injection point. My attempt to do this through re-exporting a TypeScript version using extends resulted in breaking the application. I suspect this happened because extending a service means it's no longer the original service, causing my extended version of ConfigService to no longer be paired with the built-in ConfigModule. What would be the best way to resolve this issue?

config.service.ts

import { ConfigService as NestConfigService } from '@nestjs/config';

interface EnvironmentVariables {
  NODE_ENV: 'development' | 'production';
  DATABASE_URL: string;
  JWT_SECRET: string;
}

export class ConfigService extends NestConfigService<EnvironmentVariables> {}

users.module.ts

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [ConfigModule],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

users.service.ts

import { Injectable } from '@nestjs/common';
import { ConfigService } from '../config/config.service';

@Injectable()
export class UsersService {
  constructor(private configService: ConfigService) {}

  async example() {
    return this.configService.get('JWT_SECRET');
  }
}

error

[Nest] 16612  - 05/17/2023, 2:15:03 PM   ERROR [ExceptionHandler] Nest can't resolve dependencies of the UsersService (?). Please make sure that the argument ConfigService at index [0] is available in the UsersModule context.

Potential solutions:
- Is UsersModule a valid NestJS module?
- If ConfigService is a provider, is it part of the current UsersModule?
- If ConfigService is exported from a separate @Module, is that module imported within UsersModule?

Error: Nest can't resolve dependencies of the UsersService (?). Please make sure that the argument ConfigService at index [0] is available in the UsersModule context.

Answer №1

The original Nest ConfigService cannot be extended without overriding it from the Nest ConfigModule. If your custom service is not provided in any module but still being injected into your UsersService, it will inevitably cause errors.

To resolve this issue, you need to create your own ConfigModule that utilizes both NestConfigModule and includes provision for your custom ConfigService:

import { ConfigModule as NestConfigModule } from '@nestjs/config';
import { ConfigService } from './config.service';
import { Module } from '@nestjs/common';

@Module({
  exports: [ConfigService],
  imports: [NestConfigModule.forRoot()],
  providers: [ConfigService],
})
export class ConfigModule {}

Answer №2

After attempting @reginald's code without success, it seems that the issue lies in NestConfigModule.forRoot using the internal ConfigService instead of your extended class.

View the source code here

By implementing a custom provider, I managed to make it work. Although I'm new to nestjs and it might not be considered best practices, you can give it a try.

// configuration.ts
const getConfiguration = () => ({key:'value'})
// config.service.ts
import { ConfigService as NestConfigService } from '@nestjs/config';
import { getConfiguration } from './configuration';

export class ConfigService extends NestConfigService<ReturnType<typeof getConfiguration>> {}
// config.module.ts
import { Module, Global } from '@nestjs/common';
import { ConfigService } from './config.service';
import { getConfiguration } from './configuration';

@Global()
@Module({
  exports: [ConfigService],
  providers: [
    {
      provide: ConfigService,
      useFactory() {
        return new ConfigService(getConfiguration());
      },
    },
  ],
})
export class ConfigModule {}

Answer №3

When using the nestjs config module, it utilizes its own ConfigService for its providers. Here is an example of what their provider may look like (though simplified):

const providers = [
{ provide: CONFIGURATION_SERVICE_TOKEN, useClass: ConfigService },
{
  provide: ConfigService,
    useFactory: (configService: ConfigService) => {
      if (options.cache) {
        (configService as any).isCacheEnabled = true;
      }
      return configService;
    },
    inject: [CONFIGURATION_SERVICE_TOKEN],
  }
]

The implementation in nestjs-typed-config could serve as a good reference for your situation. This custom config module has a static method forRoot, where the input parameter is the config service required. It also supports dependency injection.

export class TypedConfigModule {
  static forRoot(
    configService: typeof BaseTypedConfigService<any>,
    options: ConfigModuleOptions,
  ) {
    const configModule = ConfigModule.forRoot(options);
    configModule.providers?.push({
      provide: TYPED_CONFIG_SERVICE_INJECT_TOKEN,
      useClass: configService,
    });
    configModule.providers?.push({
      provide: configService,
      useFactory: (typedConfigService: BaseTypedConfigService<any>) => {
        (typedConfigService as any).isCacheEnabled = !!options.cache;
        return typedConfigService;
      },
      inject: [TYPED_CONFIG_SERVICE_INJECT_TOKEN],
    });
    configModule.exports?.push(configService);
    return configModule;
  }
}

If you are interested in utilizing ConfigService, you can explore nestjs-typed-config.

Answer №4

In the absence of a complete accepted answer in this post, I have compiled one by combining multiple up-voted comments. Here is my solution for extending the ConfigService to include a method for creating a plain object to provide to client instances:

app.module.cs

import { ConfigModule } from './config/config.module';
@Module({
  imports: [
    ConfigModule
  ],
  controllers: [],
  providers: [ MyClass ],
})
export class AppModule {}

config/config.module

import { Global, Module } from '@nestjs/common';
import { ConfigService } from "./config.service";
import { ConfigModule as NestConfigModule } from '@nestjs/config'; // <- mind the alias!

@Global()
@Module({
  exports: [ConfigService],
  imports: [NestConfigModule.forRoot({
    envFilePath: 'config.env', // <- just an .env file
  })],
  providers: [ConfigService],

})

export class ConfigModule {}

config/config.service

import { ConfigService as NestConfigService } from '@nestjs/config';
import { Logger } from "@nestjs/common";

interface MyEnvVars {
  USER: string;
  TOKEN: string;
}
export class ConfigService extends NestConfigService<MyEnvVars> {  // <-- brings
  private readonly logger = new Logger(ConfigService.name);

  constructor() {
    super();
    this.logger.log('Custom ConfigService initialized'); // <-- just for better visibility
  }

  public createCredentialsFromConfig = (): { user: string; token: string; } => {
    const user = this.getOrThrow<string>('USER', undefined);
    const token = this.get<string>('TOKEN', undefined);
    if (!(user && token)) {
      this.logger.error('check your config!');
    }
    this.logger.log('I got your token');  // <-- just for better visibility
    return { user, token };
  }
}

my-class.ts

import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from "../config/config.service";

@Injectable()
export class MyClass {
  private readonly logger = new Logger(MyClass.name);

  constructor(
      private readonly configService: ConfigService, // <-- injected is custom ConfigService
  ) { }

  public doSomething() {
    console.log(this.configService.createCredentialsFromConfig());
  }
}

Working flawlessly on my machine! 🚀

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Accessing URL fragments in Next JS with context during the execution of getServerSideProps

I am trying to extract a URL fragment using getServerSideProps. The URL is structured like this: http://localhost:3000/some-folder#desiredParam=value Even though I pass the context as an argument to the getServerSideProps function, I am struggling to retr ...

Intellij IDEA does not offer auto-completion for TypeScript .d.ts definitions when a function with a callback parameter is used

I've been working on setting up .d.ts definitions for a JavaScript project in order to enable auto-completion in Intellij IDEA. Here is an example of the JavaScript code I'm currently defining: var testObj = { tests: function (it) { ...

What is the significance of including parameter names in Typescript function type signatures?

Just diving into typescript for the first time, so bear with me... I decided to create a simple filter function for a container I had created class Container<T> { filter(predicate: (T) => boolean): Container<T> { for(const elem ...

Is there a way to showcase the contents of the angular "tags" object seamlessly?

Is there a way to show the content of the "tags" object using angular? I attempted to achieve it using {{gallery.tags.tag}} but unfortunately, it did not work import {IPhoto} from "./iphoto"; export interface IGallery { galleryId: string; title: ...

What strategies can be employed to improve generic inference skills?

Looking at the scenario provided below, how can we enhance code reusability in a manner similar to foobarA? interface F<T, U extends string> { t: T, f: (u: U) => void } declare const foo: <T, U extends string>(type: U) => F<T, U>; ...

What method can be used to verify if a username exists within Angular data?

We want to display online users on a webpage by checking if they are currently active. The current code logs all online users in the console, but we need to show this visually on the page. public isOnline: boolean = false; ... ... ngOnInit() { ...

Exploring Angular 6: Unveiling the Secrets of Angular Specific Attributes

When working with a component, I have included the angular i18n attribute like so: <app-text i18n="meaning|description"> DeveloperText </app-text> I am trying to retrieve this property. I attempted using ElementRef to access nativeElement, bu ...

Encountering the following issue: Unhandled Promise Rejection - TypeError: Unable to access property 'value' of null after implementing ngAfterViewInit in the code

I'm attempting to display a Google Map using the place_id extracted from an HTML value. activity-details.page.html <ion-col> <div id="map"></div> <div ...

The frontend is not triggering the Patch API call

I am having trouble with my http.patch request not being called to the backend. This issue only occurs when I try calling it from the frontend. Oddly enough, when I tested it in Postman, everything worked perfectly. Testing the backend on its own shows t ...

Trouble with passing options to ES6 module imports

After coming across this Stackoverflow thread, I am attempting to pass options to ES6 imports. Initially, this code worked without any issues: export default (Param1:any, Param2:any) => { return class Foo { constructor() { cons ...

Issue: There is no pg_hba.conf entry for host "172.27.0.5". What is going on here?

I've been struggling with a persistent error for days now. Whenever I attempt to compile my authentication (rabbitmq) and my API gateway (hyper), the API seems to function, but I encounter issues with downloading packages, and my authentication module ...

Sending template reference from one Angular component to another

I have a main grid component that includes smaller grid-item components. The majority of these grid items navigate to a specific route when clicked. However, there is one particular item that should open a modal window instead of navigating. Is there a wa ...

Steer clear of receiving null values from asynchronous requests running in the background

When a user logs in, I have a request that retrieves a large dataset which takes around 15 seconds to return. My goal is to make this request upon login so that when the user navigates to the page where this data is loaded, they can either see it instantly ...

I am encountering an issue with Wedriver.IO where screenshots of executions on a Remote Selenium Grid Hub are not being included in my Allure Reports

wdio.conf.ci.js: The following code snippet has been added. afterTest: function(test, context, { error, result, duration, passed, retries }) { if (passed){ browser.takeScreenshot(); } }, I expect to see a screenshot attachment in the bottom right corn ...

Troubleshooting problem with @Input number in Angular 2

Here is the component in question: <component value="3"></component> This is the code for the component: private _value:number; get value(): number { return this._value; } @Input() set value(value: number) { console.log(v ...

Does combineLatest detach this from an angular service function?

Check out this test service on Stackblitz! It utilizes the combineLatest method inside the constructor to invoke a service method: constructor() { console.log("TEST SERVICE CONSTRUCTED") this.setParameters.bind(this) this.assignFixedParamete ...

Can you explain the purpose of the "=" symbol in the function definition of "export const useAppDispatch: () => AppDispatch = useDispatch" in TypeScript?

Recently, while working on a React app that utilizes react-redux and react-toolkit, I encountered some TypeScript syntax that left me puzzled. export type RootState = ReturnType<typeof store.getState> export type AppDispatch = typeof store.dispatch e ...

What is the best way to implement bypassSecurityTrustResourceUrl for all elements within an array?

My challenge is dealing with an array of Google Map Embed API URLs. As I iterate over each item, I need to bind them to the source of an iFrame. I have a solution in mind: constructor(private sanitizer: DomSanitizationService) { this.url = sanitizer. ...

Angular 7 TypeScript code not updating value

UPDATE: I'm having trouble with my code not updating the selected value with the new one entered in the input field "newVb". The error message says 'this.newVarde' is undefined when it reaches the line 'this.selectedVarde = this.newVard ...

Encountering a Issue with Http module in Angular

When attempting to call my API using HttpModule, an error is being thrown upon starting the server (please refer to the screenshot). Error Image The error arises when I try to make a call to the API from the service using Http.post method. Here is my app ...