最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - NestJS and typescript config strong types? - Stack Overflow

programmeradmin0浏览0评论

I have a main config in my app, which is expressed trough environment variables (process.env). How can i expose it with next JS as one object? In the example below, i can get value by the key. But i passing a string, no typescript comes into play here.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { envVarsValidator } from "../interfaces/Config";

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: envVarsValidator,
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

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

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

  getHello(): string {
    return this.configService.get<string>('hello'); // not what i need;
  }

}

Pseudocode for what i need:


export class SomeService {
  constructor(private configService: ConfigService) {}

  someLogic(): any {
    const port = this.configService.config.port;
// what i need is one main config object with highlighting properties avaliable on this object via typescript
  }

}

I have a main config in my app, which is expressed trough environment variables (process.env). How can i expose it with next JS as one object? In the example below, i can get value by the key. But i passing a string, no typescript comes into play here.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { envVarsValidator } from "../interfaces/Config";

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: envVarsValidator,
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

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

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

  getHello(): string {
    return this.configService.get<string>('hello'); // not what i need;
  }

}

Pseudocode for what i need:


export class SomeService {
  constructor(private configService: ConfigService) {}

  someLogic(): any {
    const port = this.configService.config.port;
// what i need is one main config object with highlighting properties avaliable on this object via typescript
  }

}

Share Improve this question asked Apr 20, 2020 at 10:11 user12694034user12694034
Add a comment  | 

6 Answers 6

Reset to default 5

In your class's constructor, as you inject ConfigService, parametrize it with the interface representing the shape of your config.

interface MyConfig {
  port: number;
  dbConnectionString: string;
}

class AppService {
  constructor(private configService: ConfigService<MyConfig>) {}
}

That will change the type of configService.get to ConfigService<MyConfig, false>.get<any>(propertyPath: keyof MyConfig): any. You'll still be passing in strings, but thanks to propertyPath: keyof MyConfig, TypeScript will now check whether the string represents a key of MyConfig.

So if you try to do something like configService.get('nonexistentConfigProp'), you'll get TS2345: Argument of type '"nonexistentConfigProp"' is not assignable to parameter of type 'keyof MyConfig' which is exactly what you want to happen.

Please note that you can also parametrize get further, e.g. get<T> for it to return T | undefined instead of any. If you'd like it to return just T because you have previously validated the config object and you know its properties are defined, you can parametrize ConfigService further with a second boolean parameter called WasValidated, i.e. ConfigService<MyConfig, true>. Then get<T> will return just T.

see https://docs.nestjs.com/techniques/configuration#configuration-namespaces

e.g.

config/database.config.ts JS

export default registerAs('database', () => ({
  host: process.env.DATABASE_HOST,
  port: process.env.DATABASE_PORT || 5432
}));

and inject typed object

constructor(
  @Inject(databaseConfig.KEY)
  private dbConfig: ConfigType<typeof databaseConfig>,
) {}

You can pass a typescript type object on the class level.

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

// add this type with your allowed values
type MyType = {
  hello: string;
  someOtherValue: string;
};

@Injectable()
export class AppService {
  constructor(private configService: ConfigService<MyType>) {} // <-- add the type here

  getHello(): string {
    return this.configService.get<string>('hello1'); // this should now show an TS error
  }
}

I went with a slightly more boilerplaty approach of wrapping nest's ConfigService with my own and exporting it as a part of a module (along with other common services I use often in my app), like so:

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

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

  get MY_CONFIG_STRING() {
    // this reads the value from your .env
    return this.configService.get<string>('MY_CONFIG_STRING')!
  }

  get MY_CONFIG_Number() {
    // this reads the value from your .env
    return this.configService.get<number>('MY_CONFIG_NUMBER')!
  }
}

The user side is sweet and simple:

export class DoSomethingService {
  constructor(private configurationService: ConfigurationService) {}

  doSomething() {
    console.log(this.configurationService.MY_CONFIG_VALUE)
  }

}

Just make sure to bootstrap Nest's config service in your ConfigurationService module like so:

import { Module } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { ConfigurationService } from './configuration.service'

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

We've developed the nest-typed-config module to solve the issue.

First you need to create your config model:

// config.ts
export class Config {
    @IsString()
    public readonly host!: string;

    @IsNumber()
    public readonly port!: number;
}

Register TypedConfigModule in AppModule:

// app.module.ts
import { Module } from '@nestjs/common';
import { TypedConfigModule, fileLoader } from 'nest-typed-config';
import { AppService } from './app.service';
import { Config } from './config';

@Module({
    imports: [
        TypedConfigModule.forRoot({
            schema: Config,
            load: fileLoader(),
            isGlobal: true,
        }),
    ],
    providers: [AppService],
})
export class AppModule {}

And that's it! You can inject Config in any service now:

// app.service.ts
import { Config } from './config';

@Injectable()
export class AppService {
    constructor(private readonly config: Config) {}

    show() {
        // Full TypeScript support
        console.log(`http://${this.config.host}:${this.config.port}`)
    }
}

Just create you own service wrapped over default and use it:

import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { ConfigSchema } from "src/other/configuration";

@Injectable()
export class TypedConfigService {
  constructor(private configService: ConfigService<ConfigSchema>) {}

  get<T extends keyof ConfigSchema>(key: T) {
    return this.configService.get(key) as ConfigSchema[T];
  }
}
发布评论

评论列表(0)

  1. 暂无评论