NestJS Best Practices for Developers
My Spring Boot Best Practices for Developers article has become one of the most viewed in my articles. It offers valuable insights on how to effectively work with Spring Boot, covering tips, techniques, and strategies that can help developers write cleaner, more efficient code. I also tackled a trending debate in the developer community with my article Which is the Best: NestJS or Spring Boot? With NestJS gaining significant popularity as a backend framework, I compared it to Spring Boot, weighing the pros and cons of each, and helping developers choose the right framework based on their project requirements. In this article, I will discuss some tips when you work with the NestJS framework. Use the Modular architecture NestJS stands out for its modular architecture. It’s a great idea to structure your code into modules based on specific features or domains, such as users, authentication, or products. Each module should handle its own controllers, services, and DTOs (Data Transfer Objects). This approach helps keep your code organized, clean, and easy to maintain. Stick to the MVC flow Using the MVC pattern in NestJS helps organize your application efficiently, especially for larger applications. By following this pattern, you create a clean separation of concerns and improve code organization, which makes the application more scalable and maintainable. Keep controllers focused on HTTP logic, delegate business logic to services, and use models for data structures. Use DTOs properly for validations You can use class-validator and class-transformer to validate incoming requests. This will make your code clean. It’s recommended that DTOs be used to define request payloads and pass them through the ValidationPipe for validation. import { IsString, IsInt, Min, Max, IsOptional, IsEmail } from 'class-validator'; export class CreateUserDto { @IsString() @IsNotEmpty() name: string; @IsEmail() @IsNotEmpty() email: string; @IsInt() @Min(18) @Max(100) age: number; @IsOptional() @IsString() @IsNotEmpty() address?: string; } Use caching (In memory or distributed) For lightweight apps, you can use in-memory caching, and for large-scale projects, you can go with distributed caching like Redis. Caching will improve performance and scalability Remember to carefully manage cache invalidation to ensure your data remains accurate and up-to-date. Take the advantage of Dependency Injection Just like Spring Boot, NestJS uses dependency inject to make your life easier with decoupling components to make it easy to test and maintain. You can use @Injectable decorator. Unnecessary injections will make circular dependencies and it causes performance issues and memory leaks. Try to avoid them using forwardRef(). import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private readonly users = []; createUser(name: string, email: string) { const user = { name, email }; this.users.push(user); return user; } getAllUsers() { return this.users; } } import { Controller, Get, Post, Body } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Post() create(@Body() body: { name: string; email: string }) { return this.usersService.createUser(body.name, body.email); } @Get() findAll() { return this.usersService.getAllUsers(); } } Use an ORM framework TypeORM and Prisma are two common ORM frameworks that can be used with NestJS. Compared to Prisma, TypeORM is powerful since it is a fully featured ORM framework with a massive community base. Exercise caution when implementing Middleware and Interceptors Every middleware or interceptor introduces some processing overhead. It’s best to use them sparingly and, when possible, combine multiple interceptors into one streamlined and efficient one. Middleware runs before route handlers and has access to the request/response lifecycle. If you use middleware, you can limit the scope and handle errors. Interceptors wrap around request handling and can modify input/output. If you use interceptors, keep it lightweight. Always try to avoid overriding the response. It is a common pitfall of using interceptors. Use Fastify Fastify is a powerful web framework that is created on top of NodeJS. If your project is a large-scale one, with a large number of concurrent requests, you can use Fastufy as the HTTP server to improve performance. Use Lazy Loading for Modules Load modules only when needed to improve startup speed and reduce memory consumption. Here is a sample lazy load module. import { Module, DynamicModule } from '@nestjs/common'; import { LazyService } from './lazy.service'; @Module({}) export class LazyModule { static forRoot(): DynamicMod

My Spring Boot Best Practices for Developers article has become one of the most viewed in my articles. It offers valuable insights on how to effectively work with Spring Boot, covering tips, techniques, and strategies that can help developers write cleaner, more efficient code.
I also tackled a trending debate in the developer community with my article Which is the Best: NestJS or Spring Boot? With NestJS gaining significant popularity as a backend framework, I compared it to Spring Boot, weighing the pros and cons of each, and helping developers choose the right framework based on their project requirements.
In this article, I will discuss some tips when you work with the NestJS framework.
Use the Modular architecture
NestJS stands out for its modular architecture. It’s a great idea to structure your code into modules based on specific features or domains, such as users, authentication, or products. Each module should handle its own controllers, services, and DTOs (Data Transfer Objects). This approach helps keep your code organized, clean, and easy to maintain.
Stick to the MVC flow
Using the MVC pattern in NestJS helps organize your application efficiently, especially for larger applications. By following this pattern, you create a clean separation of concerns and improve code organization, which makes the application more scalable and maintainable.
Keep controllers focused on HTTP logic, delegate business logic to services, and use models for data structures.
Use DTOs properly for validations
You can use class-validator and class-transformer to validate incoming requests. This will make your code clean. It’s recommended that DTOs be used to define request payloads and pass them through the ValidationPipe for validation.
import { IsString, IsInt, Min, Max, IsOptional, IsEmail } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string;
@IsEmail()
@IsNotEmpty()
email: string;
@IsInt()
@Min(18)
@Max(100)
age: number;
@IsOptional()
@IsString()
@IsNotEmpty()
address?: string;
}
Use caching (In memory or distributed)
For lightweight apps, you can use in-memory caching, and for large-scale projects, you can go with distributed caching like Redis. Caching will improve performance and scalability
Remember to carefully manage cache invalidation to ensure your data remains accurate and up-to-date.
Take the advantage of Dependency Injection
Just like Spring Boot, NestJS uses dependency inject to make your life easier with decoupling components to make it easy to test and maintain. You can use @Injectable decorator. Unnecessary injections will make circular dependencies and it causes performance issues and memory leaks. Try to avoid them using forwardRef().
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private readonly users = [];
createUser(name: string, email: string) {
const user = { name, email };
this.users.push(user);
return user;
}
getAllUsers() {
return this.users;
}
}
import { Controller, Get, Post, Body } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() body: { name: string; email: string }) {
return this.usersService.createUser(body.name, body.email);
}
@Get()
findAll() {
return this.usersService.getAllUsers();
}
}
Use an ORM framework
TypeORM and Prisma are two common ORM frameworks that can be used with NestJS. Compared to Prisma, TypeORM is powerful since it is a fully featured ORM framework with a massive community base.
Exercise caution when implementing Middleware and Interceptors
Every middleware or interceptor introduces some processing overhead. It’s best to use them sparingly and, when possible, combine multiple interceptors into one streamlined and efficient one.
Middleware runs before route handlers and has access to the request/response lifecycle. If you use middleware, you can limit the scope and handle errors.
Interceptors wrap around request handling and can modify input/output. If you use interceptors, keep it lightweight. Always try to avoid overriding the response. It is a common pitfall of using interceptors.
Use Fastify
Fastify is a powerful web framework that is created on top of NodeJS. If your project is a large-scale one, with a large number of concurrent requests, you can use Fastufy as the HTTP server to improve performance.
Use Lazy Loading for Modules
Load modules only when needed to improve startup speed and reduce memory consumption. Here is a sample lazy load module.
import { Module, DynamicModule } from '@nestjs/common';
import { LazyService } from './lazy.service';
@Module({})
export class LazyModule {
static forRoot(): DynamicModule {
return {
module: LazyModule,
providers: [LazyService],
exports: [LazyService],
};
}
}
Optimize your DB queries
Although this isn’t specific to NestJS, I feel it’s important to mention it here.
- Well design your database
- Use an ORM tool (Optional)
- Use indexes
- Use pagination
Find more Database optimization tips…
Optimize modules
Remove unnecessary imports, relations, dependencies, and declarations. You can always take advantage of using the asynchronous behavior of NestJS.
I hope you find this article enjoyable, and I’d love to hear your thoughts!
Enjoyed this article? If you found this helpful, consider supporting my work by buying me a coffee! Your support helps me create more content like this.