Code Patterns & Architecture
This document outlines the coding patterns, architectural decisions, and best practices used in the Comdeall backend application. These patterns ensure consistency, maintainability, and scalability across the codebase.
๐๏ธ Architecture Overviewโ
Layered Architectureโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ API Layer โ โ Controllers, DTOs, Services
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Business Logic โ โ Domain Services, Validation
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Data Access Layer โ โ Repositories, DB Services
โโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Infrastructure โ โ Database, Redis, External APIs
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Module Structureโ
Each module in the Comdeall backend follows a consistent structure across the codebase. The modules are divided across two main directories: src/api for the API layer and src/db for the database layer.
src/
โโโ api/ # API Controllers & Services
โ โโโ auth/ # Authentication & Authorization
โ โโโ user/ # User Management (Parents, Therapists)
โ โโโ assessment/ # Child Assessment Management
โ โโโ lesson-plan/ # Lesson Plan Management
โ โโโ appointments/ # Appointment Scheduling
โ โโโ payment/ # Payment Processing
โ โโโ subscription/ # Subscription Management
โ โโโ notifications/ # Push Notifications
โ โโโ support/ # Support Ticket System
โ โโโ media/ # File Upload/Management
โโโ db/ # Data Access Layer
โ โโโ user/ # User Repository & DB Service
โ โโโ assessment/ # Assessment Repository & DB Service
โ โโโ lesson-plan/ # Lesson Plan Repository & DB Service
โ โโโ prisma/ # Database Schema
โโโ common/ # Shared Utilities
โ โโโ dto/ # Common DTOs
โ โโโ enum/ # Shared Enums
โ โโโ helpers/ # Utility Functions
โ โโโ services/ # Common Services
โโโ background/ # Background Jobs & Queues
โ โโโ queue/ # Queue Services & Processors
โ โโโ cron/ # Scheduled Jobs
โโโ interceptors/ # Request/Response Interceptors
โโโ middlewares/ # Custom Middlewares
โโโ socket/ # WebSocket Implementation
API Layer Structureโ
The API layer in src/api/module-name/ follows this structure:
module-name/
โโโ dto/
โ โโโ create-*.dto.ts
โ โโโ update-*.dto.ts
โ โโโ *-response.dto.ts
โ โโโ [other DTOs]
โโโ enum/
โ โโโ *-status.enum.ts
โ โโโ *-type.enum.ts
โ โโโ [other enums]
โโโ helpers/
โ โโโ *-helper.ts
โโโ module-name.controller.ts
โโโ module-name.module.ts
โโโ module-name.service.ts
โโโ module-name.transform.ts
Database Layer Structureโ
The corresponding database layer in src/db/module-name/ follows this structure:
module-name/
โโโ module-name.repository.ts
โโโ module-name-db.service.ts
๐ Naming Conventionsโ
Files & Classesโ
// Files: kebab-case
user.service.ts, create-parent.dto.ts
// Classes: PascalCase
export class UserService { }
export class CreateParentDto { }
// Enums: PascalCase with descriptive values
export enum UserRole {
ADMIN = "ADMIN",
PARENT = "PARENT",
THERAPIST = "THERAPIST"
}
๐ง Service Layer Patternsโ
API Service Structureโ
@Injectable()
export class UserService {
private readonly logger = new Logger(UserService.name);
constructor(
private readonly userDBService: UserDBService,
private readonly configService: ConfigService<EnvConfig>
) {}
async createParent(parentData: CreateParentDto): Promise<InternalResponse> {
// 1. Validate business rules
// 2. Call DB service
// 3. Queue background jobs
// 4. Return standardized response
}
}
Key Points:
- Services handle business logic
- Use dependency injection for DB services
- Always return
InternalResponseformat - Log important operations
๐ DTO Patternsโ
Request DTOs with Validationโ
export class CreateParentDto {
@ApiProperty({ description: 'Parent name', example: 'John Doe' })
@IsString()
@IsNotEmpty()
@Length(3, 30)
@Matches(REGEX.NAME)
parent_name: string;
@IsEmail()
email: string;
}
Key Points:
- Use
@ApiPropertyfor Swagger documentation - Validate all inputs with class-validator decorators
- Use custom regex patterns for specific formats
Response Wrapperโ
export class CreateParentInput {
@ValidateNested()
@Type(() => CreateParentObject)
input: CreateParentObject;
}
Key Points:
- Wrap complex DTOs in input objects
- Use
@ValidateNested()for nested validation - Use
@Type()for proper transformation
๐ฎ Controller Patternsโ
Standard Controllerโ
@Controller('user-management')
@ApiTags('User Management')
@UseGuards(JwtAuthGuard)
export class UserController {
@Post('create-parent')
@ApiOperation({ summary: 'Create parent account' })
@ApiResponse({ status: 201, type: UserResponseDto })
async createParent(@Body() dto: CreateParentDto): Promise<InternalResponse> {
return this.userService.createParent(dto);
}
}
Key Points:
- Use route names from
RouteNamesenum - Add comprehensive Swagger documentation
- Apply appropriate guards (JWT, Roles)
- Keep controllers thin - delegate to services
๐ Background Job Patternsโ
Queue Serviceโ
@Injectable()
export class EmailQueueService {
async sendOtpEmail(data: IOtpEmailJob): Promise<void> {
// Process email sending logic
await this.emailService.sendEmail(emailData);
}
}
Job Interfaceโ
export interface IOtpEmailJob {
email: string;
otp: string;
}
Key Points:
- Define clear interfaces for job data
- Use descriptive job names from
JobNameenum - Handle errors gracefully in processors
- Log job processing for monitoring
๐ก๏ธ Error Handlingโ
Service Levelโ
async createUser(userData: CreateUserDto): Promise<InternalResponse> {
try {
// Business logic
return { success: true, data: result };
} catch (error) {
this.logger.error(`Error: ${error.message}`, error.stack);
throw new InternalServerErrorException('Operation failed');
}
}
Key Points:
- Always wrap in try-catch
- Log errors with context
- Throw appropriate HTTP exceptions
- Return consistent response format
๐ Validation Patternsโ
Custom Validatorsโ
@IsValidPhoneNumber({ message: 'Invalid phone format' })
contact: string;
Regex Constantsโ
export const REGEX = {
NAME: /^[a-zA-Z\s]+$/,
PHONE: /^\+[1-9]\d{1,14}$/,
EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
};
Key Points:
- Create reusable regex patterns
- Use custom validators for complex validation
- Provide clear error messages
๐ Interceptor Patternsโ
Response Transformationโ
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
return next.handle().pipe(
map((data) => ({
success: data?.success,
message: data?.message,
data: data?.data || data
}))
);
}
}
Key Points:
- Standardize all API responses
- Exclude health/metrics endpoints
- Handle both HTTP and GraphQL contexts
๐๏ธ Database Patternsโ
Repository Patternโ
@Injectable()
export class UserRepository {
constructor(private readonly prisma: DBService) {}
async getUserById(id: string) {
return this.prisma.user.findUnique({
where: { id },
include: { parent: { include: { children: true } } }
});
}
}
DB Service Layerโ
@Injectable()
export class UserDBService {
constructor(private readonly userRepository: UserRepository) {}
async getUserById(id: string) {
return this.userRepository.getUserById(id);
}
}
Key Points:
- Repository handles raw database operations
- DB Service provides business-focused methods
- Use proper includes for related data
- Keep queries optimized with select/where clauses
๐ Authentication Patternsโ
JWT Guard Usageโ
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN, UserRole.THERAPIST)
@Get('admin-only')
async adminEndpoint() { }
Key Points:
- Use JWT guard for authentication
- Use Roles guard for authorization
- Apply guards at controller or method level
๐ Logging & Monitoringโ
Structured Loggingโ
private readonly logger = new Logger(ServiceName.name);
this.logger.log('Operation completed successfully');
this.logger.error('Operation failed', error.stack);
this.logger.debug('Debug information', context);
Key Points:
- Use NestJS Logger with service name
- Log at appropriate levels (log, error, debug, warn)
- Include context and stack traces for errors
๐งช Testing Patternsโ
Unit Test Structureโ
describe('UserService', () => {
let service: UserService;
let userDBService: UserDBService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UserService,
{ provide: UserDBService, useValue: mockUserDBService }
]
}).compile();
service = module.get<UserService>(UserService);
});
it('should create user successfully', async () => {
// Arrange
const userData = { name: 'John', email: 'john@test.com' };
jest.spyOn(userDBService, 'createUser').mockResolvedValue(mockUser);
// Act
const result = await service.createUser(userData);
// Assert
expect(result.success).toBe(true);
expect(userDBService.createUser).toHaveBeenCalledWith(userData);
});
});
Key Points:
- Use AAA pattern (Arrange, Act, Assert)
- Mock dependencies properly
- Test both success and error scenarios
- Use descriptive test names
๐ Performance Best Practicesโ
Cachingโ
@Cacheable('user', 300) // 5 minutes
async getUserById(id: string) {
return this.userDBService.getUserById(id);
}
Database Optimizationโ
// Use select to limit fields
async getUsersList() {
return this.prisma.user.findMany({
select: { id: true, email: true, role: true },
take: 100
});
}
Key Points:
- Cache frequently accessed data
- Use select to limit returned fields
- Implement pagination for large datasets
- Use proper database indexes
๐ Quick Referenceโ
โ Do'sโ
- Use consistent naming conventions
- Implement proper error handling
- Validate all inputs
- Use dependency injection
- Follow repository pattern
- Log important operations
- Write comprehensive tests
โ Don'tsโ
- Don't expose database models directly
- Don't use
anytype - Don't ignore error handling
- Don't hardcode configuration
- Don't skip input validation
- Don't use console.log in production
This guide provides the essential patterns used in Comdeall backend. Follow these patterns for consistency and maintainability across the codebase.