user11762401
user11762401

Reputation: 445

NestJS - how to use ConfigService outside NestJS managed class?

I am developing a NestJS project. And here is my project structure:

-src
 -config
  -configuration.ts
 -modules
  -auth
  -user
 -utils
  -util.ts

In my auth and user module, I import ConfigModule and then I can use ConfigService to get config values from configuration.ts file.

But how do I get config values in utils/util.ts file?
It's just a dir that holds some helper files instead of a module dir.

Upvotes: 6

Views: 9758

Answers (2)

Luan Henning
Luan Henning

Reputation: 87

when declaring a config module you can pass an object like that:

ConfigModule.forRoot({
  envFilePath: ['.env'],
  isGlobal: true,
  load: [config],
}),

this load parameter is a list of functions that return javascript objects which are then added to your configuration, Then you can use ConfigService to get your configuration like that:

ConfigService.get("NODE_ENV") //This gets a configuration loaded from .env file

ConfigService.get("mongoose.connectionOptions") //This is a config you setted in app.config.ts for example and return an object containing mongoose connection options

your config file may look something like this:

var config = {
  app: {
    environment: process.env.NODE_ENV,
  },
  sentry: {
    dsn: process.env.SENTRY_DSN,
    tracesSampleRate: 1.0,
  },
  rabbit: {
    connection: {
      protocol: process.env.RABBITMQ_PROTOCOL,
      hostname: process.env.RABBITMQ_HOST,
      port: process.env.RABBITMQ_PORT,
      vhost: process.env.RABBITMQ_VHOST,
      username: process.env.RABBITMQ_USERNAME,
      password: process.env.RABBITMQ_PASSWORD,
    },
  },
  mongo: {
    user: process.env.MONGO_USER,
    password: process.env.MONGO_PASS,
    dbName: process.env.MONGO_NAME,
    port: process.env.MONGO_PORT,
    host: process.env.MONGO_HOST,
    connString: `mongodb://${process.env.MONGO_HOST}:${process.env.MONGO_PORT}/${process.env.MONGO_NAME}?authSource=admin`,
  },
  mongooseOptions: {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: false,
    user: process.env.MONGO_USER,
    pass: process.env.MONGO_PASS,
    keepAlive: true,
    uri: `mongodb://${process.env.MONGO_HOST}:${process.env.MONGO_PORT}/${process.env.MONGO_NAME}?authSource=admin`,
  },
};

var {
  protocol,
  username,
  password,
  hostname,
  port,
  vhost,
} = config.rabbit.connection;
config.rabbit.connection.connString = `${protocol}://${username}:${password}@${hostname}:${port}/${vhost}`;

export default () => config;

Upvotes: 0

VinceOPS
VinceOPS

Reputation: 2720

Assuming that you want the same instance of ConfigService to be used... From a NestJS perspective, I see two options:

  • Group your util functions into a single Provider and treat it as a legit component of your NestJS application (or group them by responsibilities, into multiple Providers, this could make more sense too)
  • Make your config service available as a node.js module, that you can import "as is" from util, and declare as a custom Provider from your NestJS app.

Regarding the second option:

// regular typescript class
// exported so you can type your injected "configService" in other classes
export class ConfigService {
 // your code
}

export default new ConfigService();

This can be imported as is by util.
And injected into your NestJS application by creating a custom provider:

import configService, { ConfigService } from './somewhere/config-service.ts';

const configServiceProvider = {
  provide: ConfigService,
  useValue: configService,
};

// and use it in your ConfigModule
@Module({ 
  providers: [configServiceProvider],
  exports: [configServiceProvider],
})
export class ConfigModule {}

When injecting ConfigService in your NestJS app, the value will be resolved to configService (as long as ConfigModule is in the scope, obviously).

However, this does not allow your ConfigService to inject anything in its constructor parameters... Unless you are ready to use two different instances of ConfigService (one for util, one for your NestJS app). In this case, you could use a Factory provider, looking like the following:

const configServiceProvider = {
  provide: ConfigService,
  useFactory: (somethingService: SomethingService) => new ConfigService(somethingService),
  inject: [SomethingService],
};

// and in the ConfigModule
@Module({
  imports: [SomethingModule], // so you can inject SomethingService
  providers: [configServiceProvider],
  exports: [configServiceProvider],
})
export class ConfigModule {}

Yet this doesn't make much sense, as you would also need the dependencies of ConfigService available out of the scope of NestJS, in order to use it in util functions.


An other option (not considering any framework) would be to allow yourself passing (by parameter) the ConfigService in your util... e.g.:

// old version
export const readDeploymentConfigFile = (filePath: string) => {
  // ...
};

// new version
export const readDeploymentConfigFile = (configService: ConfigService) => (filePath: string) => {
  // ...
};

This video might be inspiring ("Dead-Simple Dependency Injection" by Rúnar Óli Bjarnason, 2012), even if that's not related to TypeScript nor Nest.

But this is not ideal to use as an API and it might also reveal some design flaws... If your util functions need some dynamic/external config values, can we still consider them as util functions?

Upvotes: 3

Related Questions