DEV Community

Cover image for Understanding the Importance of @Injectable in NestJS
Ivor
Ivor

Posted on • Edited on

Understanding the Importance of @Injectable in NestJS

When we dive into the world of modern web development, especially with frameworks like NestJS, we encounter a lot of concepts that are both powerful and a bit complex to grasp at first. One of these concepts is Dependency Injection (DI), a design pattern used extensively in building scalable and easily maintainable applications. In NestJS, this pattern is beautifully simplified through the use of decorators like @Injectable. Let's explore how this little piece of code makes our development life easier.

The Basics: What is @Injectable?

In simple terms, when you annotate a class with @Injectable(), you are essentially storing metadata using the reflect-metadata library, then when you add the class to the providers array you're telling NestJS: "Hey, please manage this class for me!" What does that mean? It means that NestJS will take care of creating an instance of this class and manage its entire lifecycle, based on the scope you define—singleton, request, or transient. But there’s more to it.

Consider a simple service in a NestJS app, like CarService:

export class CarService {
  getMake(): string {
    return 'Tesla';
  }
}
Enter fullscreen mode Exit fullscreen mode

Without following NestJS's conventions, you might end up manually creating an instance of CarService inside your controllers or other services. However, this approach is not just cumbersome but also contrary to the principles of good software architecture that NestJS advocates.

The Right Way: Leveraging DI

Instead of manually handling the object lifecycle, you should lean on NestJS's powerful DI system. By marking CarService as @Injectable and registering it in a module, NestJS handles its instantiation and ensures that it's ready to be injected wherever it's needed:

@Injectable()
export class CarService {
  getMake(): string {
    return 'Tesla';
  }
}

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService, CarService /* register as a provider */],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Now, CarService can be easily injected into any controller or service without the need to manually create an instance:

@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService,
    private readonly carService: CarService,
  ) {}

  @Get()
  getHello(): string {
    console.log(this.carService.getMake());
    return this.appService.getHello();
  }
}
Enter fullscreen mode Exit fullscreen mode

What If You Forget @Injectable?

Interestingly, even if you don’t explicitly use @Injectable() in your classes, NestJS's DI mechanism might still handle the injection correctly if the class doesn’t depend on other providers. However, this works until it doesn't—especially when your classes start having dependencies of their own.

For instance, if CarService requires LoggerService, you must decorate CarService with @Injectable() to ensure that LoggerService is properly injected:

@Injectable()
export class LoggerService {
  log(message: string): void {
    console.log(message);
  }
}

export class CarService {
  constructor(private readonly loggerService: LoggerService) {}

  getMake(): string {
    console.log(this.loggerService); // 💣💥
    return 'Tesla';
  }
}
Enter fullscreen mode Exit fullscreen mode

If CarService is not properly decorated with @Injectable, this.loggerService would be undefined.

Conclusion: Embrace the Simplicity

NestJS's DI system is robust and designed to simplify your development process. By using @Injectable(), you ensure that your services are properly managed and that dependencies are handled correctly. This not only makes your code cleaner and more modular but also aligns with best practices that allow for greater scalability and easier maintenance.

So, next time you’re setting up services in your NestJS application, remember the importance of @Injectable(). It’s a small annotation that carries a lot of weight, making your coding journey much smoother and enjoyable. Happy coding!

Top comments (2)

Collapse
 
kostyatretyak profile image
Костя Третяк

when you annotate a class with @Injectable(), you are essentially adding metadata to the class to tell the framework (IoC container), "Hey, please manage this class for me!"

No, when you add @Injectable() in front of a class, you are scanning that class and storing metadata using the reflect-metadata library. When you add providers to the providers array, only then do you ask NestJS "Hey, please manage this class for me!"

Collapse
 
ivordev profile image
Ivor

Thanks for the clarification, will update.