如何使用TypeScript接口扩展NestJS内置服务,如ConfigService?

huangapple go评论70阅读模式
英文:

NestJS how to extend built-in services like ConfigService with a TypeScript interface?

问题

The NestJS config docs provide an example of how to achieve type safety with ConfigService by using an interface like EnvironmentVariables and annotating it during injection, like so: constructor(private configService: ConfigService<EnvironmentVariables>) {...}. However, you want to permanently bind this interface to ConfigService without needing to import and annotate it at every injection point. You attempted to do this by re-exporting a TypeScript version using extends, as shown below, but it broke the app because extending a service makes it a separate service, disconnecting it from the built-in ConfigModule. What's the best way to fix this?

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';
import { ConfigService } from './config/config.service'; // Import the extended ConfigService

@Module({
  imports: [ConfigModule],
  providers: [UsersService, ConfigService], // Add ConfigService as a provider
  exports: [UsersService, ConfigService], // Export ConfigService as well
})
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');
  }
}

This should resolve the dependency issue and allow you to use the extended ConfigService throughout your application.

英文:

The NestJS config docs give an example of how to get type safety on ConfigService by using an interface like EnvironmentVariables and annotating it during injection like constructor(private configService: ConfigService&lt;EnvironmentVariables&gt;) {...}. However I would like to bind this interface permanently to ConfigService without needing to remember to import and annotate it at every injection point. I attempted this by re-exporting a TypeScript version using extends as shown below but this breaks the app, I think because extending a service means it's no longer the same service and so my extended version of ConfigService is no longer paired with the built in ConfigModule. What is the best way to fix this?

config.service.ts

import { ConfigService as NestConfigService } from &#39;@nestjs/config&#39;;

interface EnvironmentVariables {
  NODE_ENV: &#39;development&#39; | &#39;production&#39;;
  DATABASE_URL: string;
  JWT_SECRET: string;
}

export class ConfigService extends NestConfigService&lt;EnvironmentVariables&gt; {}

users.module.ts

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

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

users.service.ts

import { Injectable } from &#39;@nestjs/common&#39;;
import { ConfigService } from &#39;../config/config.service&#39;;

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

  async example() {
    return this.configService.get(&#39;JWT_SECRET&#39;);
  }
}

error

[Nest] 16612  - 05/17/2023, 2:15:03 PM   ERROR [ExceptionHandler] Nest can&#39;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?
  @Module({
    imports: [ /* the Module containing ConfigService */ ]
  })

Error: Nest can&#39;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?
  @Module({
    imports: [ /* the Module containing ConfigService */ ]
  })

答案1

得分: 1

Sure, here is the translated code:

确实,扩展原始的Nest ConfigService 不会覆盖它从Nest ConfigModule 中继承的内容。此外,尽管您的自定义服务未在任何模块中提供,但仍然被注入到您的 UsersService 中,这不可避免地会导致错误。

您需要创建自己的 ConfigModule,该模块利用了 NestConfigModule,并且既提供又导出您的自定义 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 {}
英文:

Indeed extending the original Nest ConfigService doesn't override it from the Nest ConfigModule. Also, your custom service not being provided in any module but still being injected in your UsersService inevitably leads to an error.

You have to create your own ConfigModule that makes use of NestConfigModule and that both provides and exports your custom ConfigService :

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

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

答案2

得分: 1

I have tried the code by @reginald but not working, I guess the reason is that NestConfigModule.forRoot used internal ConfigService insted of your extends class.

我尝试了@reginald的代码,但不起作用,我猜想原因是NestConfigModule.forRoot使用了内部的ConfigService而不是你的扩展类。

I tried to use custom provider and it works, I'm new to nestjs and it maybe is not best practices, you can try it.

我尝试使用自定义提供程序,它有效,我是nestjs的新手,可能不是最佳实践,你可以尝试一下。

// 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 {}
英文:

I have tried the code by @reginald but not working, I guess the reason is that NestConfigModule.forRoot used internal ConfigService insted of your extends class

https://github.com/nestjs/config/blob/fbac8a0617d27ab8284cab986eb8e2c9792ad257/lib/config.module.ts#L134

I tried to use custom provider and it works, I'm new to nestjs and it maybe is not best practices, you can try it.

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

export class ConfigService extends NestConfigService&lt;ReturnType&lt;typeof getConfiguration&gt;&gt; {}
// config.module.ts
import { Module, Global } from &#39;@nestjs/common&#39;;
import { ConfigService } from &#39;./config.service&#39;;
import { getConfiguration } from &#39;./configuration&#39;;

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

答案3

得分: 0

nestjs配置模块使用自己的ConfigService作为其提供者
它们的提供者看起来像下面这样(并非完全相同,这是一个简化的例子)

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

我认为nestjst-typed-config中的代码可以作为你的示例。
自定义的配置模块具有forRoot静态方法,其forRoot方法需要配置服务作为参数。它支持使用输入的配置服务进行依赖注入。

export class TypedConfigModule {
  static forRoot(
    configService: typeof BaseTypedConfigService&lt;any&gt;,
    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&lt;any&gt;) =&gt; {
        (typedConfigService as any).isCacheEnabled = !!options.cache;
        return typedConfigService;
      },
      inject: [TYPED_CONFIG_SERVICE_INJECT_TOKEN],
    });
    configModule.exports?.push(configService);
    return configModule;
  }
}

此外,如果你想使用ConfigService,你可以使用nestjs-typed-config


<details>
<summary>英文:</summary>

nestjs config module use its own ConfigService for its providers.
Their provider looks liek below. (not exactly same. simplified example)

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

I think codes from nestjst-typed-config can be a example in your case.
Custom config module which has forRoot static method. And its forRoot method requires config service as parameter. It supports dependency injection with input config service.

export class TypedConfigModule {
  static forRoot(
    configService: typeof BaseTypedConfigService&lt;any&gt;,
    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&lt;any&gt;) =&gt; {
        (typedConfigService as any).isCacheEnabled = !!options.cache;
        return typedConfigService;
      },
      inject: [TYPED_CONFIG_SERVICE_INJECT_TOKEN],
    });
    configModule.exports?.push(configService);
    return configModule;
  }
}

Additionally, if you want to use ConfigService, you can use nestjs-typed-config.

huangapple
  • 本文由 发表于 2023年5月18日 03:25:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76275576.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定