Skip to content

Instantly share code, notes, and snippets.

@sangdth
Created December 7, 2025 10:04
Show Gist options
  • Select an option

  • Save sangdth/9af14eff206ce6c195fc8cbd179ffc9b to your computer and use it in GitHub Desktop.

Select an option

Save sangdth/9af14eff206ce6c195fc8cbd179ffc9b to your computer and use it in GitHub Desktop.
---
description: Designs scalable NestJS applications using domain-driven modular patterns with clear separation of concerns and service-based business logic.
alwaysApply: false
---
# NestJS Architecture
## Modular Architecture Rules
- Use modular architecture with clear separation of concerns
- One module per main domain/route
- Each module should be self-contained with its own controllers, services, and models
## Module Structure
```text
src/
├── users/
│ ├── dto/
│ │ ├── create-user.dto.ts
│ │ └── update-user.dto.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ └── users.module.ts
```
## Controller Organization
- One main controller per module for primary routes
- Additional controllers for secondary/nested routes
- Keep controllers thin - delegate business logic to services
Example:
```typescript
@Controller('users')
export class UsersController {
@Inject(UsersService)
private readonly usersService: UsersService
@Post()
async createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponse> {
return this.usersService.createUser(createUserDto);
}
}
@Controller('users/:userId/profile')
export class UserProfileController {
@Inject(UserProfileService)
private readonly userProfileService: UserProfileService
@Get()
async getProfile(@Param('userId') userId: string): Promise<ProfileResponse> {
return this.userProfileService.getProfile(userId);
}
}
```
## DTO and Validation
- Use DTOs with class-validator for all inputs
- Define simple types for outputs
- Validate at the boundary (controllers)
Example:
```typescript
import { IsEmail, IsNotEmpty, IsOptional, MinLength } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
@MinLength(2)
name: string;
@IsEmail()
email: string;
@IsOptional()
@MinLength(8)
password?: string;
}
// Simple type for response
export interface DemoResponse {
id: string;
createdAt: Date;
}
```
## Service Organization
- One service per entity/domain concept
- Services contain business logic and persistence coordination
- Use dependency injection for service composition
```typescript
@Injectable()
export class UsersService {
@Inject(UserRepository)
private readonly userRepository: UserRepository
@Inject(EmailService)
private readonly emailService: EmailService
@Inject(Logger)
private readonly logger: Logger
async createUser(createUserDto: CreateUserDto): Promise<UserResponse> {
const user = await this.userRepository.create(createUserDto);
await this.emailService.sendWelcomeEmail(user.email);
this.logger.log(`User created: ${user.id}`);
return this.mapToResponse(user);
}
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment