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 thefindAll
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
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!