NestJS Module Best Practices for Clean Code Organization

NestJS Module Best Practices for Clean Code Organization

Top Techniques for Structuring Code with NestJS Modules

NestJS is a powerful framework for building server-side applications using Node.js. It is built with TypeScript and offers an out-of-the-box application architecture that allows for the creation of highly testable, scalable, and maintainable applications. One of the core concepts in NestJS is modules. This guide will break down what modules are and how to use them in simple, human-readable language.

What Are Modules?

In NestJS, a module is a class annotated with a @Module() decorator. Modules are used to organize the application's components, such as controllers, providers, and services, into cohesive blocks of functionality. This modular structure makes it easier to manage and scale applications. Modules can be thought of as the building blocks of a NestJS application. Each module focuses on a specific feature or a set of related features.

Think of modules as different sections of a house—each room has its own purpose and contains related items.

Why Use Modules?

Modules help keep your code organized and maintainable. By grouping related functionality together, you can easily manage and scale your application. For example, you might have separate modules for users, products, and orders in an e-commerce application.

Creating a Module

Let's create a module called UsersModule. First, make sure you have NestJS CLI installed:

npm install -g @nestjs/cli

Next, create a new NestJS project:

nest new my-nest-app
cd my-nest-app

Generate a new module using the CLI:

nest generate module users

This command creates a users module with a file structure like this:

src/
  users/
    users.module.ts

Here’s a basic UsersModule:

// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

Understanding the @Module() Decorator

  • controllers: This array lists the controllers that handle incoming requests and send responses. In this case, it includes UsersController.

  • providers: This array lists the providers like services that handle the business logic. It includes UsersService.

Creating a Controller

Controllers handle incoming requests and send responses to the client. Let's create a UsersController:

nest generate controller users

This generates a controller file:

src/
  users/
    users.controller.ts

Here’s a simple UsersController:

// users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll(): string {
    return this.usersService.findAll();
  }
}
  • @Controller('users'): This decorator specifies that this controller handles requests to the /users route.

  • @Get(): This decorator indicates that the findAll method handles GET requests to /users.

  • findAll(): This method returns a string response.

Creating a Service

Services handle the business logic. Let’s create a UsersService:

nest generate service users

This generates a service file:

src/
  users/
    users.service.ts

Here’s a simple UsersService:

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

@Injectable()
export class UsersService {
  findAll(): string {
    return 'This action returns all users';
  }
}
  • @Injectable(): This decorator marks the class as a provider that can be injected into other classes.

  • findAll(): This method contains the business logic to return a string response.

Using the Module

Now that we have a module, a controller, and a service, let's put them together in the main application module. Open app.module.ts:

// app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';

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

Here, UsersModule is imported and added to the imports array. This tells NestJS to include the UsersModule in the application.

Advanced Module Concepts

Module Re-Exporting

Modules can be re-exported to make their components available to other modules. For instance, if a SharedModule is created and it exports common services, this module can be imported and its components will be available to any module that imports the SharedModule.

// shared.module.ts
import { Module } from '@nestjs/common';
import { CommonService } from './common.service';

@Module({
  providers: [CommonService],
  exports: [CommonService],
})
export class SharedModule {}

Global Modules

Sometimes, certain services or components need to be available globally. This can be achieved by marking a module as global. A global module makes its components available across the entire application without needing to import the module in other modules.

// common.module.ts
import { Global, Module } from '@nestjs/common';
import { CommonService } from './common.service';

@Global()
@Module({
  providers: [CommonService],
  exports: [CommonService],
})
export class CommonModule {}

Dynamic Modules

Dynamic modules allow for a module to be configured dynamically. This can be useful for setting up modules that require configuration settings to be passed in at runtime.

// database.module.ts
import { Module, DynamicModule } from '@nestjs/common';
import { DatabaseService } from './database.service';

@Module({})
export class DatabaseModule {
  static forRoot(config: DatabaseConfig): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [
        {
          provide: 'DATABASE_CONFIG',
          useValue: config,
        },
        DatabaseService,
      ],
      exports: [DatabaseService],
    };
  }
}

Conclusion
Modules are a fundamental part of NestJS, helping to organize an application into manageable, scalable, and maintainable parts. By grouping related components together, modules make it easier to build complex applications with clear boundaries and dependencies.

In this simple guide, the following points were covered:

  • What modules are

  • How to create a module

  • How to create and use controllers and services within a module

  • Advanced module concepts, including re-exporting, global modules, and dynamic modules

With these basics and advanced concepts, a robust application can be built using NestJS. Happy coding!