Writing Clean and Maintainable Code in NestJS
Part 2 of 12
Structuring a NestJS Project for Maintainability
A well-structured project is easier to read, test, and scale. Poor organization leads to spaghetti code, tight coupling, and maintenance nightmares.
In this post, we’ll cover best practices for structuring a NestJS project.
1. Use a Feature-Based Folder Structure
✅ Recommended Folder Structure
src/
├── modules/
│ ├── users/
│ │ ├── users.controller.ts
│ │ ├── users.service.ts
│ │ ├── users.repository.ts
│ │ ├── users.module.ts
│ │ ├── dto/
│ │ │ ├── create-user.dto.ts
│ │ │ ├── update-user.dto.ts
│ │ ├── entities/
│ │ │ ├── user.entity.ts
2. Keep Controllers Thin
// ✅ Good Example (Delegating to Service)
@Controller('users')
export class UsersController {
constructor(private readonly userService: UsersService) {}
@Post()
createUser(@Body() userDto: CreateUserDto) {
return this.userService.createUser(userDto);
}
}
3. Separate Data Transfer Objects (DTOs)
export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string;
@IsEmail()
email: string;
}
4. Use a Repository Layer for Database Access
@Injectable()
export class UsersRepository {
constructor(
@InjectRepository(User) private readonly userRepo: Repository<User>,
) {}
async save(user: User) {
return await this.userRepo.save(user);
}
}
5. Group Shared Logic in the common Directory
✅ Example: Logging Interceptor
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Incoming Request:', context.switchToHttp().getRequest().url);
return next.handle();
}
}
6. Use Configuration Management
export default () => ({
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 5432,
},
});
7. Summary
✅ Use a feature-based folder structure.
✅ Keep controllers thin, moving logic to services.
✅ Use DTOs for structured data transfer.
✅ Separate database logic into repositories.
✅ Store shared logic (guards, filters) in common/.
✅ Use ConfigModule to manage environment variables.
In the next post, we’ll dive into writing modular and reusable code.