Lesson 4
Modules in NestJS
Introduction to Modules in NestJS

Welcome to this lesson on Modules in NestJS. So far, we've explored how to set up a basic NestJS application, delved into the roles of key files, and understood the concepts of controllers and providers. In our last lesson, we covered the essentials of providers and how they work with dependency injection. This lesson will build on that foundation by examining how modules in NestJS help structure your application, making it organized, scalable, and maintainable.

Understanding Dependency Injection and the Purpose of Modules

Before diving in, let's recap the concept of Dependency Injection (DI), which we touched upon in the last lesson. DI is a design pattern used to implement IoC (Inversion of Control), allowing dependencies to be injected into a class rather than the class creating them itself. This improves code modularity and ease of testing.

In NestJS, DI is implemented through the use of decorators like @Injectable() and types that indicate what should be injected. Modules in NestJS group related components, like services and controllers, together. This modular approach keeps your code organized and helps manage dependencies effectively.

Creating a Basic NestJS Module

A module is defined using the @Module() decorator, which takes a metadata object. This object typically includes arrays of providers, controllers, and other imported modules.

Here's a simple example:

TypeScript
1import { Module } from '@nestjs/common'; 2import { BooksController } from './books.controller'; 3import { BooksService } from './books.service'; 4 5@Module({ 6 controllers: [BooksController], 7 providers: [BooksService], 8}) 9export class BooksModule {}

This code snippet shows BooksModule registering BooksController and BooksService. By doing this, we keep related components together, making the application more organized.

Implementing the Book Ratings Service

Next, let's introduce a service to fetch book ratings. We'll name this service BookRatingsService. Services in NestJS are annotated with @Injectable() to mark them as providers that can be injected into other components.

Here’s how we define the BookRatingsService:

TypeScript
1import { Injectable } from '@nestjs/common'; 2 3const ratings = { 4 "1": 4, 5 "2": 5, 6 "3": 3, 7}; 8 9@Injectable() 10export class BookRatingsService { 11 getRatingForBook(id: string): number { 12 return ratings[id] ?? 0; 13 } 14}

In this example:

  • We use the @Injectable() decorator to allow BookRatingsService to be injected as a dependency into other classes.
  • The getRatingForBook method returns the rating for a specific book ID from the ratings object. If the book ID doesn't exist in the ratings object, it returns 0 by default.
Integrating Book Ratings with the Books Service

Now let's integrate the BookRatingsService with BooksService. The goal is to fetch book ratings when retrieving book data.

Here's how you can achieve this:

TypeScript
1import { Injectable } from '@nestjs/common'; 2import { BookRatingsService } from './bookRatings.service'; 3import { Book, books } from './books.data'; 4 5@Injectable() 6export class BooksService { 7 constructor(private readonly bookRatingsService: BookRatingsService) {} 8 9 getAllBooks(): Book[] { 10 return books.map(book => ({ 11 ...book, 12 rating: this.bookRatingsService.getRatingForBook(book.id), 13 })); 14 } 15}

In this example:

  • The BooksService class has a dependency on BookRatingsService, injected via the constructor.
  • The getAllBooks method now includes a rating for each book, fetched using BookRatingsService.
Configuring the Books Module

We need to register our BookRatingsService within the BooksModule so that it can be injected into BooksService.

Here’s how you configure BooksModule:

TypeScript
1import { Module } from '@nestjs/common'; 2import { BooksController } from './books.controller'; 3import { BooksService } from './books.service'; 4import { BookRatingsService } from './bookRatings.service'; 5 6@Module({ 7 controllers: [BooksController], 8 providers: [ 9 BooksService, 10 BookRatingsService, 11 ], 12}) 13export class BooksModule {}

By registering both BooksService and BookRatingsService in the BooksModule, we are grouping related services that handle book-related logic together. This approach keeps related functionality in one place, making the module easier to maintain and test.

Once you’ve created a module like BooksModule, it can easily be reused in different parts of your application. For example, if you later need to build a AuthorsModule, it could follow the same structure, allowing you to build a highly modular and maintainable system.

Integration into the Main Application

Finally, let's integrate our BooksModule into the main AppModule. This step makes the BooksModule a part of the overall application. The existing top-level AppModule is still there, serving its initial purpose, but now we're also including everything in the BooksModule so both sets of routes are exposed. This means that any endpoints defined in BooksController will be accessible alongside any other routes defined in the AppController or any other modules you might add in the future.

Here’s how you do it:

TypeScript
1import { Module } from '@nestjs/common'; 2import { AppController } from './app.controller'; 3import { AppService } from './app.service'; 4import { BooksModule } from './books/books.module'; 5 6@Module({ 7 imports: [BooksModule], 8 controllers: [AppController], 9 providers: [AppService], 10}) 11export class AppModule {}

In this example:

  • The BooksModule is added to the imports array of AppModule, making it part of the main application.
  • This allows NestJS to recognize and use the controllers and providers defined within BooksModule.
Summary and Preparing for Practice

In this lesson, we covered the creation and integration of modules in NestJS. We implemented a BookRatingsService and integrated it with BooksService, encapsulating these services within BooksModule, which was then integrated into the main AppModule.

Understanding modules is crucial for building scalable and maintainable NestJS applications. Modules help organize your code and manage dependencies effectively.

Next, you'll get hands-on practice with these concepts through various exercises designed to reinforce your understanding. These exercises will help you apply what you've learned and build confidence in working with NestJS modules. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.