Woozar
Woozar

Reputation: 1130

NestJs using same instance of service in multiple modules

I have got a NestJs app, that uses two services. The DbService that connects to the Db and the SlowService that does stuff rather slow and uses the injected DbService.

Now the app shall provide health routes outside of the api base path, so i need a different module that provides the controllers for the health routes.

I created a base module.

import { Module } from '@nestjs/common'
import { SlowService } from './slow.service'
import { DbService } from './db.service'

@Module({
  imports: [],
  controllers: [],
  providers: [DbService, SlowService],
  exports: [DbService, SlowService]
})
export class BaseModule {
}

The ApiModule and the HealthModule now both import the base module to be able to use the services.

  imports: [BaseModule],

There is only a small problem. Both modules seem to construct their own instance of the service but I need it to be the same instance. I assume this, because the console.log from the constructor appear twice when starting the app. Am I missing a setting or something?

UPDATE

Here is my bootstrap method, so you can see how I initialize the modules.

async function bootstrap (): Promise<void> {
  const server = express()
  const api = await NestFactory.create(AppModule, server.application, { cors: true })
  api.setGlobalPrefix('api/v1')
  await api.init()
  const options = new DocumentBuilder()
    .setTitle('...')
    .setLicense('MIT', 'https://opensource.org/licenses/MIT')
    .build()
  const document = SwaggerModule.createDocument(api, options)
  server.use('/swaggerui', SwaggerUI.serve, SwaggerUI.setup(document))
  server.use('/swagger', (req: express.Request, res: express.Response, next?: express.NextFunction) => res.send(document))
  const health = await NestFactory.create(HealthModule, server.application, { cors: true })
  health.setGlobalPrefix('health')
  await health.init()
  http.createServer(server).listen(Number.parseInt(process.env.PORT || '8080', 10))
}
const p = bootstrap()

Upvotes: 14

Views: 22070

Answers (2)

MartijnvdB
MartijnvdB

Reputation: 1002

Maybe you defined the services as providers for 2 modules. What you need to do is only define your BaseModule as import in the module where you need it.

This example demonstrates the service OtherService in OtherModule which needs the DbService from BaseModule. If you run the example you will see that it only instantiates the DbService once.

import {Injectable, Module} from '@nestjs/common';
import {NestFactory} from '@nestjs/core';

@Injectable()
export class SlowService {
  constructor() {
    console.log(`Created SlowService`);
  }
}

@Injectable()
export class DbService {
  constructor() {
    console.log(`Created DbService`);
  }
}

@Module({
  imports: [],
  providers: [SlowService, DbService],
  exports: [SlowService, DbService]
})
export class BaseModule {}

@Injectable()
export class OtherService {
  constructor(private service: DbService) {
    console.log(`Created OtherService with dependency DbService`);
  }
}

@Module({
  imports: [BaseModule],
  providers: [OtherService],
})
export class OtherModule {}

@Module({
  imports: [
    BaseModule,
    OtherModule
  ],
})
export class AppModule {}

NestFactory.createApplicationContext(AppModule).then((app) => console.log('🥑 context created'));

This gist demonstrates BAD usage of providers, resulting in instantiating the DbService twice: https://gist.github.com/martijnvdbrug/12faf0fe0e1fc512c2a73fba9f31ca53

Upvotes: 26

dkregen
dkregen

Reputation: 477

I just drop it here in case someone else needed this.

If you really want every modules in your app sharing the same instance, then probably you need to use global variable and put your services inside of it.

First, define your service on the root of your source app, which is app.module.

nest g s shared-services/db

Second, mention your service in the global variable. (Focus on commented code)

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

/* Define global variable as "any" so we don't get nasty error. */
declare var global: any;

@Injectable()
export class DbService {
  constructor() {
    console.log(`Created DbService`);

    /* Put the class inside global variable. */
    global.dbService = this;
  }
}

Finally, you can call your service from other controller, or services.

import { Injectable } from '@nestjs/common';
import { DbService} from './../shared-services/db.service';

/* Define global variable as "any" so we don't get nasty error. */
declare var global: any;

@Injectable()
export class OtherService {

  /* Call the service. */
  protected readonly dbService: DbService = global.dbService;
  constructor() {
  }
} 

And you are good to go. I really hope in the future NestJs has the same Injectable features as Angular did so we don't really need to bothered about export-import any services.

Upvotes: -3

Related Questions