Back to all posts

Structuring a NestJS Project for Maintainability

S
sonhp
10 min read

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.

S

Written by sonhp

Technical writer and developer passionate about web technologies.

Related Articles