DEV Community

Cover image for Modules & Dependency Injection in Nestjs P1
ngtrvinc
ngtrvinc

Posted on

Modules & Dependency Injection in Nestjs P1

Module

In NestJS, a module is a fundamental organizational unit of code. It acts as a container for related components, such as controllers, services, and providers.

The primary purpose of a module is to group these components together, ensuring that your codebase remains well-structured and easily maintainable.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Think of a company divided into departments like Sales, Marketing, and IT. Each department, like a NestJS module, manages specific tasks and components, keeping the organization efficient.

NestJS modules are not just simple containers; they can be categorized to serve specific purposes. According to the official NestJS documentation, you'll commonly encounter the following types of modules: Root module, Feature module, etc

To gain a deeper understanding of each module type and how to use them, it's highly recommended to refer to the official NestJS documentation

  • The Role of main.ts: The main.ts file serves as the entry point of your NestJS application. Its primary responsibility is to bootstrap the application, creating an instance of the NestJS application and starting the server. In simple terms, it's where your application begins its execution.

Dependency Injection in NestJS

Dependency Injection (DI) is a design pattern where an object receives its dependencies from an external source, promoting loose coupling and improving code testability and maintainability.

For example, we have 3 classes User, Post and Comment. Both Comment class and Post class need User class while creating page or post.

This is because you might want to check if a user who's creating the Comment or Post exists in the database or not then associate that particular Post to a specific user or a Comment to a specific user.

So how does it look like if we manage the dependency without dependency injection.

// user.service.ts
class UserService {
  getUser(id: number): { id: number; name: string } {
    return { id: id, name: "John Doe" };
  }
}
Enter fullscreen mode Exit fullscreen mode
// post.service.ts
class PostService {
  createPost(userId: number, content: string): void {
    const userService = new UserService(); // we have to create an instance directly
    const user = userService.getUser(userId);
    if (user) {
      console.log(`The post created by ${user.name}: ${content}`);
    } else {
      console.log("User not found!");
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
// comment.service.ts
class CommentService {
  createComment(userId: number, text: string): void {
    const userService = new UserService();  // we have to create an instance directly
    const user = userService.getUser(userId);
    if (user) {
      console.log(`The comment created by ${user.name}: ${text}`);
    } else {
      console.log("User not found!");
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

So whenever you need a User class as a standalone object, you go ahead and create a new instance of the User class and within the Post class and Comment class respectively as well, you are creating new User classes whenever you need them.

Now how about the dependency management with dependency injection ?

// user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  getUser(id: number): { id: number; name: string } {
    return { id: id, name: "John Doe" };
  }
}
Enter fullscreen mode Exit fullscreen mode
// post.service.ts
import { Injectable } from '@nestjs/common';
import { UserService } from './user.service';

@Injectable()
export class PostService {
  constructor(private readonly userService: UserService) {} // Inject UserService

  createPost(userId: number, content: string): void {
    const user = this.userService.getUser(userId);
   if (user) {
      console.log(`The post created by ${user.name}: ${content}`);
    } else {
      console.log("User not found!");
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
// comment.service.ts
import { Injectable } from '@nestjs/common';
import { UserService } from './user.service';

@Injectable()
export class CommentService {
  constructor(private readonly userService: UserService) {} // Inject UserService

  createComment(userId: number, text: string): void {
    const user = this.userService.getUser(userId);
    if (user) {
      console.log(`The comment created by ${user.name}: ${text}`);
    } else {
      console.log("User not found!");
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

With dependency injection, you invert the control, which means that you create a new user once and then you inject this object wherever it is needed.

Now the responsibility of creating this user instance before the Comment class and the Post class are created lies with the dependency injection.

Conclusion, this article provides a quick introduction to modules and a aspect of Dependency Injection (DI) in NestJS. Modules help organize code, while DI enhances flexibility and testability. Understanding these fundamentals is crucial for building effective NestJS applications

In upcoming sections, we'll dive deeper into how NestJS manages class instantiation based on dependency order. You'll see how NestJS's DI system automatically analyzes and resolves dependencies, ensuring classes are instantiated in the correct sequence, reducing coupling, enhancing testability, and promoting code reusability.

Bye

Top comments (0)