14

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()

2
  • This is surprising to me. Both controllers use the default injection scope when injecting the SlowService? Commented Apr 24, 2019 at 12:53
  • Yes they do. I tried to debug into it and it seems they even use the same context. Still the console.log from the constructor appears twice. And believe me, I was very surprised aswell as the new lightwight health controller ruined the performance of my test system. Commented Apr 24, 2019 at 13:21

3 Answers 3

26

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

Sign up to request clarification or add additional context in comments.

7 Comments

Thank you very much for your detailed answer. This is exactly what I thought I did, but I will investigate it again.
@Woozar, I've got the same issue and this answer have helped me too. I suggest selecting it as accepted.
This is valuable information that is not easily extracted from the documentation. Thank you very much for this level of detail; for some like me, the subtlety is very difficult to understand.
If you import BaseService on multiple modules, then the services inside of it will be instatiated multiple times. Indeed every module have the same services, but it is not quite true for the module have the same instance of services with the other modules.
@Khatri, if a provider is not exported, it will only be available to members (and children) of the module. Think of it like a "private" service, while exporting it will then make it available to other modules importing this one. Hope that makes sense
|
0

There are two logs generated because you are initialising two NestJS applications.
Each application will have its own instances.

Here is where you initialise the first app:

const api = await NestFactory.create(AppModule, server.application, { cors: true })
  api.setGlobalPrefix('api/v1')
  await api.init()

And here is the second app:

const health = await NestFactory.create(HealthModule, server.application, { cors: true })
  health.setGlobalPrefix('health')
  await health.init()

Comments

-3

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.

3 Comments

Isn't this anti-pattern? Why take this approach vs importing/providing this in the root module and/or a @Global module? Wouldn't this also mean that it isn't injected anywhere, defeating the purpose of @Injectable? If this is the approach you need, seems sensible to just stick it in an export file that is imported where you need it.
This is not a very good approach
Why then use Nest at all if you wanna store it outside...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.