英文:
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<EnvironmentVariables>) {...}
. 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 '@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?
@Module({
imports: [ /* the Module containing ConfigService */ ]
})
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.
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 '@nestjs/config';
import { ConfigService } from './config.service';
import { Module } from '@nestjs/common';
@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
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 = () => ({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 {}
答案3
得分: 0
nestjs配置模块使用自己的ConfigService作为其提供者。
它们的提供者看起来像下面这样(并非完全相同,这是一个简化的例子)
```ts
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],
}
]
我认为nestjst-typed-config中的代码可以作为你的示例。
自定义的配置模块具有forRoot静态方法,其forRoot方法需要配置服务作为参数。它支持使用输入的配置服务进行依赖注入。
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;
}
}
此外,如果你想使用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) => {
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<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;
}
}
Additionally, if you want to use ConfigService, you can use nestjs-typed-config.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论