# Backend Architecture Guidelines This document contains comprehensive guidelines for creating robust, scalable, and maintainable backend systems. ## Architecture Philosophy Build backend systems that are: - **Scalable and Resilient** - Able to handle growing loads and recover from failures - **Secure by Design** - Security integrated at every level, not added as an afterthought - **Maintainable** - Clean architecture that's easy to understand and extend - **Performance Optimized** - Efficient resource utilization and response times - **Observable** - Comprehensive logging, monitoring, and debugging capabilities ## Core Architecture Principles ### Clean Architecture - **Layer Separation** - Controllers/Routes (API endpoints) - Services (business logic) - Data Access (repository pattern) - Domain Models (entities) - Infrastructure (external services, databases) - **Dependency Inversion** - High-level modules don't depend on low-level modules - Both depend on abstractions - Abstractions don't depend on details - Details depend upon abstractions - **Single Responsibility** - Each module has one reason to change - Functions do one thing well - Classes encapsulate cohesive functionality - Services manage specific domains - **Domain-Driven Design** - Business logic in the domain layer - Ubiquitous language shared with stakeholders - Bounded contexts to isolate domains - Aggregates to enforce invariants ### API Design - **RESTful Principles** - Resource-oriented endpoints - Appropriate HTTP methods - Proper status codes - Hypermedia links where appropriate - Version management - **GraphQL Considerations** - Schema-first development - Resolver optimization - Query complexity analysis - Batching and caching - Authorization directives - **API Versioning** - URL path versioning (`/v1/resources`) - Accept header versioning - Content negotiation - Deprecation strategy - Documentation of changes - **Contract First** - OpenAPI/Swagger documentation - Schema validation - Consumer-driven contracts - Integration tests against spec - Automated documentation ### Statelessness and Scaling - **Horizontal Scaling** - Stateless services - Load balancing - Session management via tokens - No server affinity - Container orchestration - **Vertical Scaling** - Resource allocation - Performance profiling - Memory optimization - CPU utilization - I/O efficiency - **Microservices Considerations** - Service boundaries - Inter-service communication - API gateways - Service discovery - Circuit breaking - **Monolith Optimization** - Modular architecture - Clear boundaries - Resource isolation - Deployment strategies - Scaling bottleneck identification ## Database and Data Management ### Schema Design - **Relational Database** - Normalization (3NF baseline) - Foreign key constraints - Indexing strategy - Query optimization - Transaction boundaries - **NoSQL Database** - Data access patterns - Denormalization strategy - Eventual consistency - Partition keys - Compound indexes - **Migration Strategy** - Version control for schema - Forward-only migrations - Rollback planning - Zero-downtime updates - Data validation - **Entity Relationships** - One-to-one - One-to-many - Many-to-many - Polymorphic relationships - Self-referential relationships ### Data Access Patterns - **Repository Pattern** - Entity-specific repositories - Query abstraction - Transaction support - Caching integration - Error handling - **Object-Relational Mapping** - Entity mapping - Lazy/eager loading - Change tracking - Query generation - Performance considerations - **Query Optimization** - Execution plans - Index utilization - N+1 query prevention - Batch operations - Connection pooling - **Data Caching** - Cache invalidation - TTL strategies - Distributed caching - Cache warming - Stale-while-revalidate ### Data Integrity and Validation - **Input Validation** - Request schema validation - Type checking - Business rule enforcement - Cross-field validation - Sanitization - **Output Validation** - Response schema conformance - Content security - Sensitive data redaction - Consistent formatting - Pagination metadata - **Data Constraints** - Database constraints - Application-level validation - Cross-service consistency - Idempotency guarantees - State transition rules - **Error Cases** - Input validation errors - Business rule violations - Resource not found - Conflict resolution - Server capability limits ## Security Implementation ### Authentication - **User Identity** - Credential storage - Password policies - Multi-factor authentication - Account lockout - Password reset - **JWT Implementation** - Token structure - Signing algorithms - Expiration strategy - Refresh mechanism - Revocation strategy - **OAuth/OIDC** - Authorization flows - Provider integration - Scope management - Token validation - User info endpoints - **API Authentication** - API keys - Client credentials - Certificate-based - IP whitelisting - Rate limiting ### Authorization - **Role-Based Access** - Role definition - Permission mapping - Role hierarchy - Default deny - Least privilege - **Attribute-Based Access** - Policy evaluation - Context awareness - Dynamic permissions - Environmental conditions - Temporal constraints - **Resource Ownership** - Multi-tenancy - User-based isolation - Team/organization access - Delegation model - Ownership transfer - **Permission Enforcement** - Controller/middleware level - Service layer enforcement - Data access filtering - Object-level security - Field-level security ### Data Protection - **Encryption** - Data at rest - Data in transit - Key management - Rotation policies - Algorithm selection - **PII Handling** - Classification - Minimization - Anonymization - Pseudonymization - Retention policies - **Secrets Management** - Environment variables - Vault services - Access control - Audit logging - Rotation strategy - **Database Security** - Network isolation - Access controls - Query parameterization - Connection security - Auditing ### Security Posture - **Vulnerability Management** - Dependency scanning - Static analysis - Dynamic testing - Penetration testing - Responsible disclosure - **Security Headers** - Content Security Policy - CORS configuration - XSS protection - CSRF prevention - Clickjacking protection - **Rate Limiting** - Request throttling - Account limits - IP-based limiting - Graduated response - Breach detection - **Audit Logging** - Security events - Access attempts - Administrative actions - Data modifications - System changes ## Error Handling and Resilience ### Error Management - **Standardized Errors** - Error classification - Status code mapping - Error codes - User messages - Developer details - **Exception Handling** - Try-catch patterns - Async error handling - Middleware interception - Global error handlers - Service-specific handling - **Graceful Degradation** - Fallback strategies - Default behaviors - Partial content responses - Circuit breakers - Bulkhead pattern - **Retry Logic** - Exponential backoff - Jitter implementation - Maximum attempts - Idempotency guarantees - Failure reporting ### Resilience Patterns - **Circuit Breaker** - Failure threshold - Recovery timeout - Half-open state - Health monitoring - Circuit isolation - **Bulkhead Pattern** - Resource isolation - Thread pools - Connection limitations - Request prioritization - Failure containment - **Timeouts** - Connection timeouts - Read timeouts - Write timeouts - Service timeouts - Cascade prevention - **Rate Limiters** - Request throttling - Token bucket algorithm - Leaky bucket algorithm - Fixed/sliding window - Adaptive strategies ### Recovery Strategies - **Disaster Recovery** - Backup strategy - Restore procedures - Recovery time objectives - Recovery point objectives - Failover systems - **Data Consistency** - Outbox pattern - Saga pattern - Eventual consistency - Two-phase commit - Compensating transactions - **Self-Healing** - Health checks - Automatic restarts - Replication - State reconciliation - Data repair - **Chaos Engineering** - Failure injection - Load testing - Network degradation - Resource exhaustion - Recovery validation ## Performance Optimization ### Response Time - **Caching Strategy** - Response caching - Object caching - Computation caching - Cache hierarchy - Invalidation triggers - **Query Optimization** - Indexing strategy - Query planning - Result limiting - Join optimization - Execution analysis - **Async Processing** - Background jobs - Message queues - Scheduled tasks - Event-driven architecture - Long-running processes - **I/O Management** - Connection pooling - Batch operations - Stream processing - File I/O optimization - Network I/O efficiency ### Resource Utilization - **Memory Management** - Memory profiling - Leak detection - Object pooling - Garbage collection tuning - Buffer management - **CPU Optimization** - Thread allocation - Worker processes - Computation distribution - Algorithm efficiency - Hot path optimization - **Database Efficiency** - Connection pooling - Query optimization - Read/write splitting - Sharding - Replication strategy - **Network Efficiency** - Payload compression - Request batching - Keep-alive connections - Response streaming - Protocol selection ### Scaling Strategies - **Horizontal Scaling** - Stateless design - Load balancing - Session management - Data partitioning - Service discovery - **Vertical Scaling** - Resource allocation - Hardware optimization - Application tuning - Server configuration - Database sizing - **Caching Layers** - Client-side cache - CDN integration - API gateway cache - Application cache - Database cache - **Read/Write Splitting** - Command Query Responsibility Segregation - Read replicas - Write sharding - Cache read optimization - Eventual consistency model ## Observability and Monitoring ### Logging - **Log Levels** - Error: System failures - Warn: Potential issues - Info: System events - Debug: Development details - Trace: Detailed execution flow - **Log Structure** - Timestamp - Severity - Service/component - Correlation ID - Context information - **Log Storage** - Centralized collection - Retention policies - Access controls - Search capabilities - Archival strategy - **Log Analysis** - Pattern detection - Anomaly identification - Performance insights - Error investigation - Audit capabilities ### Metrics - **System Metrics** - CPU utilization - Memory usage - Disk I/O - Network throughput - Connection count - **Application Metrics** - Request rate - Response time - Error rate - Concurrent users - Business transactions - **Database Metrics** - Query performance - Connection utilization - Index efficiency - Lock contention - Storage growth - **Custom Business Metrics** - Conversion rates - User engagement - Feature usage - Business KPIs - Revenue indicators ### Tracing - **Distributed Tracing** - Trace context propagation - Span collection - Service mapping - Latency analysis - Dependency visualization - **Transaction Tracking** - Request lifecycle - Service boundaries - Error propagation - Resource utilization - External calls - **Performance Profiling** - Hotspot identification - Method-level timing - Resource consumption - Lock contention - I/O blocking - **User Journey Tracking** - Session correlation - Flow visualization - Conversion funnels - Abandonment points - Experience metrics ### Alerting - **Alert Configuration** - Threshold definition - Duration conditions - Composite alerts - Priority levels - Notification channels - **Alert Management** - Escalation policies - On-call rotations - Alert grouping - Suppression rules - Maintenance windows - **Alert Response** - Playbooks - Automated remediation - Incident classification - Resolution tracking - Post-mortem analysis - **Business Alerting** - KPI thresholds - Trend deviations - Conversion drops - Revenue impacts - Customer experience degradation ## Testing Strategy ### Test Types - **Unit Testing** - Function/method testing - Component isolation - Mock dependencies - State verification - Edge case coverage - **Integration Testing** - Service interaction - API contracts - Database integration - External service mocking - Environment setup - **System Testing** - End-to-end workflows - Real environment - Data flow validation - UI integration - Cross-service functionality - **Performance Testing** - Load testing - Stress testing - Endurance testing - Spike testing - Scalability testing ### Test Implementation - **Test-Driven Development** - Write tests first - Red-green-refactor cycle - Continuous test execution - Test coverage goals - Regression prevention - **Behavior-Driven Development** - Specification by example - Shared understanding - Business-centric language - Acceptance criteria - Feature validation - **Test Organization** - Test hierarchy - Descriptive naming - Setup and teardown - Shared fixtures - Test isolation - **Test Automation** - CI/CD integration - Parallel execution - Selective testing - Test reporting - Failure analysis ### Test Data Management - **Test Data Creation** - Factories/builders - Realistic data - Randomization - Edge cases - Invalid data - **Database Fixtures** - Known state setup - Test isolation - Transactional tests - Cleanup procedures - Data versioning - **Mocking and Stubbing** - External dependencies - Service virtualization - Response simulation - Behavior verification - State verification - **Property-Based Testing** - Generative testing - Invariant checking - Random inputs - Edge case discovery - Shrinking to minimal examples ## Deployment and Operations ### CI/CD Pipeline - **Continuous Integration** - Automated builds - Test execution - Static analysis - Security scanning - Artifact generation - **Continuous Delivery** - Environment promotion - Configuration management - Deployment approval - Release notes - Rollback capability - **Automated Testing** - Unit test suite - Integration tests - E2E testing - Performance verification - Security validation - **Code Quality Gates** - Test coverage - Static analysis - Duplicate detection - Complexity metrics - Vulnerability scanning ### Infrastructure as Code - **Environment Definition** - Infrastructure templates - Configuration scripts - Network setup - Resource allocation - Security groups - **Configuration Management** - Environment variables - Feature flags - Secrets handling - Parameter stores - Configuration versioning - **Deployment Strategies** - Blue/green deployment - Canary releases - Feature flagging - A/B testing - Rolling updates - **Containerization** - Image building - Registry management - Orchestration - Scaling policies - Service discovery ### Operation Management - **Incident Response** - Alert triage - Escalation procedures - Communication channels - Resolution tracking - Post-incident review - **Runbooks** - Common operations - Troubleshooting guides - Recovery procedures - Maintenance tasks - Emergency protocols - **Capacity Planning** - Resource monitoring - Growth forecasting - Scaling thresholds - Cost optimization - Performance benchmarks - **Change Management** - Change requests - Risk assessment - Approval workflow - Implementation plans - Verification procedures ## Code Organization ### Project Structure - **Directory Organization** - Feature-based grouping - Layer-based separation - Config isolation - Test proximity - Documentation location - **Module System** - Clear dependencies - Interface definitions - Circular dependency prevention - Encapsulation - Public APIs - **Naming Conventions** - Consistent patterns - Descriptive names - Purpose indication - Abbreviation avoidance - Version indicators - **Configuration Management** - Environment separation - Defaults and overrides - Validation - Documentation - Secret handling ### Coding Standards - **Style Guidelines** - Formatting rules - Naming conventions - Comment practices - Module organization - Code documentation - **Code Quality** - Complexity limits - Function size - Class responsibility - Coupling metrics - Duplication prevention - **Documentation** - API documentation - Implementation notes - Architecture documentation - Decision records - Operation guides - **Version Control** - Commit messages - Branch strategy - Pull request process - Code review standards - Merge requirements ## Implementation Examples ### Express.js API Implementation ```typescript // src/controllers/userController.ts import { Request, Response, NextFunction } from 'express'; import { UserService } from '../services/userService'; import { CreateUserDto, UpdateUserDto } from '../dtos/userDtos'; export class UserController { constructor(private userService: UserService) {} async getUsers(req: Request, res: Response, next: NextFunction): Promise { try { const users = await this.userService.findAll(); res.status(200).json(users); } catch (error) { next(error); } } async getUserById(req: Request, res: Response, next: NextFunction): Promise { try { const id = parseInt(req.params.id); const user = await this.userService.findById(id); if (!user) { res.status(404).json({ message: 'User not found' }); return; } res.status(200).json(user); } catch (error) { next(error); } } async createUser(req: Request, res: Response, next: NextFunction): Promise { try { const userData: CreateUserDto = req.body; const newUser = await this.userService.create(userData); res.status(201).json(newUser); } catch (error) { next(error); } } async updateUser(req: Request, res: Response, next: NextFunction): Promise { try { const id = parseInt(req.params.id); const userData: UpdateUserDto = req.body; const updatedUser = await this.userService.update(id, userData); if (!updatedUser) { res.status(404).json({ message: 'User not found' }); return; } res.status(200).json(updatedUser); } catch (error) { next(error); } } async deleteUser(req: Request, res: Response, next: NextFunction): Promise { try { const id = parseInt(req.params.id); const deleted = await this.userService.delete(id); if (!deleted) { res.status(404).json({ message: 'User not found' }); return; } res.status(204).send(); } catch (error) { next(error); } } } ``` ### Service Layer Implementation ```typescript // src/services/userService.ts import { User } from '../models/user'; import { UserRepository } from '../repositories/userRepository'; import { CreateUserDto, UpdateUserDto } from '../dtos/userDtos'; import { NotFoundError, ValidationError } from '../utils/errors'; export class UserService { constructor(private userRepository: UserRepository) {} async findAll(): Promise { return this.userRepository.findAll(); } async findById(id: number): Promise { return this.userRepository.findById(id); } async create(userData: CreateUserDto): Promise { // Validate data this.validateUserData(userData); // Check if email already exists const existingUser = await this.userRepository.findByEmail(userData.email); if (existingUser) { throw new ValidationError('Email already registered'); } // Create new user return this.userRepository.create(userData); } async update(id: number, userData: UpdateUserDto): Promise { // Validate data this.validateUserData(userData, false); // Check if user exists const user = await this.userRepository.findById(id); if (!user) { throw new NotFoundError('User not found'); } // Check email uniqueness if changing email if (userData.email && userData.email !== user.email) { const existingUser = await this.userRepository.findByEmail(userData.email); if (existingUser) { throw new ValidationError('Email already registered'); } } // Update user return this.userRepository.update(id, userData); } async delete(id: number): Promise { return this.userRepository.delete(id); } private validateUserData(data: CreateUserDto | UpdateUserDto, isCreating = true): void { const errors: string[] = []; if (isCreating && !data.email) { errors.push('Email is required'); } if (data.email && !this.isValidEmail(data.email)) { errors.push('Invalid email format'); } if (isCreating && !data.password) { errors.push('Password is required'); } if (data.password && data.password.length < 8) { errors.push('Password must be at least 8 characters long'); } if (errors.length > 0) { throw new ValidationError(errors.join(', ')); } } private isValidEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } } ``` ### Repository Pattern Implementation ```typescript // src/repositories/userRepository.ts import { db } from '../config/database'; import { User } from '../models/user'; import { CreateUserDto, UpdateUserDto } from '../dtos/userDtos'; import { hashPassword } from '../utils/auth'; export class UserRepository { async findAll(): Promise { return db.users.findMany({ select: { id: true, name: true, email: true, role: true, createdAt: true, updatedAt: true, }, }); } async findById(id: number): Promise { return db.users.findUnique({ where: { id }, select: { id: true, name: true, email: true, role: true, createdAt: true, updatedAt: true, }, }); } async findByEmail(email: string): Promise { return db.users.findUnique({ where: { email }, select: { id: true, name: true, email: true, role: true, createdAt: true, updatedAt: true, }, }); } async create(userData: CreateUserDto): Promise { const hashedPassword = await hashPassword(userData.password); return db.users.create({ data: { name: userData.name, email: userData.email, password: hashedPassword, role: userData.role || 'USER', }, select: { id: true, name: true, email: true, role: true, createdAt: true, updatedAt: true, }, }); } async update(id: number, userData: UpdateUserDto): Promise { const data: any = { name: userData.name, email: userData.email, role: userData.role, }; // Only hash password if it's included in the update if (userData.password) { data.password = await hashPassword(userData.password); } // Remove undefined values Object.keys(data).forEach(key => data[key] === undefined && delete data[key]); return db.users.update({ where: { id }, data, select: { id: true, name: true, email: true, role: true, createdAt: true, updatedAt: true, }, }); } async delete(id: number): Promise { try { await db.users.delete({ where: { id }, }); return true; } catch (error) { // If no rows were affected, user didn't exist if (error.code === 'P2025') { return false; } throw error; } } } ``` ### Error Handling Middleware ```typescript // src/middleware/errorHandler.ts import { Request, Response, NextFunction } from 'express'; import { ValidationError, NotFoundError, AuthorizationError } from '../utils/errors'; import logger from '../utils/logger'; export function errorHandler( err: Error, req: Request, res: Response, next: NextFunction ) { // Log all errors logger.error({ message: err.message, stack: err.stack, method: req.method, path: req.path, ip: req.ip, userId: req.user?.id, }); // Handle specific error types if (err instanceof ValidationError) { return res.status(400).json({ status: 'error', message: err.message, code: 'VALIDATION_ERROR', }); } if (err instanceof NotFoundError) { return res.status(404).json({ status: 'error', message: err.message, code: 'NOT_FOUND', }); } if (err instanceof AuthorizationError) { return res.status(403).json({ status: 'error', message: err.message, code: 'FORBIDDEN', }); } // Handle unexpected errors const isDevelopment = process.env.NODE_ENV === 'development'; return res.status(500).json({ status: 'error', message: 'Internal server error', code: 'SERVER_ERROR', ...(isDevelopment && { detail: err.message, stack: err.stack, }), }); } ``` ### Authentication Implementation ```typescript // src/middleware/authenticate.ts import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; import { UserService } from '../services/userService'; import { AuthorizationError } from '../utils/errors'; interface TokenPayload { userId: number; role: string; iat: number; exp: number; } export function authenticate( requiredRoles: string[] = [] ) { return async (req: Request, res: Response, next: NextFunction) => { try { // Get token from authorization header const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new AuthorizationError('Authentication required'); } const token = authHeader.split(' ')[1]; // Verify token let payload: TokenPayload; try { payload = jwt.verify( token, process.env.JWT_SECRET! ) as TokenPayload; } catch (err) { throw new AuthorizationError('Invalid token'); } // Check token expiration const now = Math.floor(Date.now() / 1000); if (payload.exp < now) { throw new AuthorizationError('Token expired'); } // Check role if required if (requiredRoles.length > 0 && !requiredRoles.includes(payload.role)) { throw new AuthorizationError('Insufficient permissions'); } // Get user and attach to request const userService = new UserService(); const user = await userService.findById(payload.userId); if (!user) { throw new AuthorizationError('User not found'); } // Attach user to request object req.user = user; next(); } catch (error) { next(error); } }; } ``` ### Custom Error Classes ```typescript // src/utils/errors.ts export class ValidationError extends Error { constructor(message: string) { super(message); this.name = 'ValidationError'; Object.setPrototypeOf(this, ValidationError.prototype); } } export class NotFoundError extends Error { constructor(message: string) { super(message); this.name = 'NotFoundError'; Object.setPrototypeOf(this, NotFoundError.prototype); } } export class AuthorizationError extends Error { constructor(message: string) { super(message); this.name = 'AuthorizationError'; Object.setPrototypeOf(this, AuthorizationError.prototype); } } export class DatabaseError extends Error { constructor(message: string) { super(message); this.name = 'DatabaseError'; Object.setPrototypeOf(this, DatabaseError.prototype); } } export class ServiceError extends Error { constructor(message: string) { super(message); this.name = 'ServiceError'; Object.setPrototypeOf(this, ServiceError.prototype); } } ``` ## Libraries and Tools ### Core Dependencies - **Web Framework** - Express.js - Fastify - NestJS - Koa - **Database Access** - Prisma - TypeORM - Sequelize - Knex - **Validation** - Joi - Zod - class-validator - Yup - **Authentication** - Passport.js - jsonwebtoken - bcrypt - OAuth libraries ### Development Tools - **Testing** - Jest - Mocha/Chai - SuperTest - Cypress - k6 - **Linting & Formatting** - ESLint - Prettier - TypeScript - Husky (pre-commit hooks) - lint-staged - **Documentation** - Swagger/OpenAPI - JSDoc - Postman collections - Markdown documentation - Architecture Decision Records (ADRs) - **Monitoring** - Prometheus - Grafana - Datadog - New Relic - Sentry ## Best Practices Checklist ### Development Checklist - [ ] Use TypeScript with strict mode - [ ] Document all APIs with OpenAPI/Swagger - [ ] Write comprehensive tests (unit, integration, E2E) - [ ] Automate CI/CD pipeline - [ ] Implement request validation - [ ] Set up logging and monitoring - [ ] Use consistent error handling - [ ] Configure linting and code formatting - [ ] Implement authentication and authorization - [ ] Set up database migrations ### Security Checklist - [ ] Use HTTPS for all communications - [ ] Implement proper authentication - [ ] Apply authorization on all endpoints - [ ] Validate all input data - [ ] Use parameterized queries - [ ] Sanitize output to prevent XSS - [ ] Apply rate limiting - [ ] Set security headers - [ ] Handle secrets securely - [ ] Scan dependencies for vulnerabilities ### Production Readiness - [ ] Configure proper logging - [ ] Set up monitoring and alerts - [ ] Implement health checks - [ ] Configure CI/CD pipeline - [ ] Document deployment procedures - [ ] Create runbooks for common operations - [ ] Set up backup and restore procedures - [ ] Plan for scaling - [ ] Document API contracts - [ ] Create incident response plan ## Code Examples ### Express.js Application Setup ```typescript // src/app.ts import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import compression from 'compression'; import rateLimit from 'express-rate-limit'; import { errorHandler } from './middleware/errorHandler'; import { notFoundHandler } from './middleware/notFoundHandler'; import { requestLogger } from './middleware/requestLogger'; import routes from './routes'; const app = express(); // Middleware app.use(helmet()); // Security headers app.use(cors()); // CORS handling app.use(compression()); // Response compression app.use(express.json()); // Parse JSON bodies app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies app.use(requestLogger); // Log all requests // Rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // 100 requests per IP standardHeaders: true, legacyHeaders: false, message: 'Too many requests from this IP, please try again later', }); app.use(limiter); // Routes app.use('/api', routes); // Error handling app.use(notFoundHandler); app.use(errorHandler); export default app; ``` ### API Route Definition ```typescript // src/routes/userRoutes.ts import { Router } from 'express'; import { UserController } from '../controllers/userController'; import { UserService } from '../services/userService'; import { UserRepository } from '../repositories/userRepository'; import { authenticate } from '../middleware/authenticate'; import { validateRequest } from '../middleware/validateRequest'; import { createUserSchema, updateUserSchema } from '../validation/userSchemas'; const router = Router(); const userRepository = new UserRepository(); const userService = new UserService(userRepository); const userController = new UserController(userService); router.get('/', authenticate(['ADMIN']), (req, res, next) => userController.getUsers(req, res, next) ); router.get('/:id', authenticate(), (req, res, next) => userController.getUserById(req, res, next) ); router.post('/', validateRequest(createUserSchema), (req, res, next) => userController.createUser(req, res, next) ); router.put('/:id', authenticate(), validateRequest(updateUserSchema), (req, res, next) => userController.updateUser(req, res, next) ); router.delete('/:id', authenticate(['ADMIN']), (req, res, next) => userController.deleteUser(req, res, next) ); export default router; ``` ### Dependency Injection Setup ```typescript // src/config/container.ts import { Container } from 'inversify'; import { TYPES } from './types'; import { UserController } from '../controllers/userController'; import { UserService } from '../services/userService'; import { UserRepository } from '../repositories/userRepository'; import { DatabaseConnection } from '../config/database'; import { Logger } from '../utils/logger'; const container = new Container(); // Infrastructure container.bind(TYPES.DatabaseConnection).to(DatabaseConnection).inSingletonScope(); container.bind(TYPES.Logger).to(Logger).inSingletonScope(); // Repositories container.bind(TYPES.UserRepository).to(UserRepository).inSingletonScope(); // Services container.bind(TYPES.UserService).to(UserService).inSingletonScope(); // Controllers container.bind(TYPES.UserController).to(UserController).inSingletonScope(); export { container }; ``` ### Testing Example ```typescript // src/services/__tests__/userService.test.ts import { UserService } from '../userService'; import { UserRepository } from '../../repositories/userRepository'; import { ValidationError, NotFoundError } from '../../utils/errors'; // Mock the repository jest.mock('../../repositories/userRepository'); describe('UserService', () => { let userService: UserService; let userRepository: jest.Mocked; beforeEach(() => { userRepository = new UserRepository() as jest.Mocked; userService = new UserService(userRepository); }); afterEach(() => { jest.clearAllMocks(); }); describe('create', () => { it('should create a user with valid data', async () => { // Arrange const userData = { name: 'John Doe', email: 'john@example.com', password: 'password123', }; userRepository.findByEmail.mockResolvedValue(null); userRepository.create.mockResolvedValue({ id: 1, ...userData, role: 'USER', createdAt: new Date(), updatedAt: new Date(), }); // Act const result = await userService.create(userData); // Assert expect(userRepository.findByEmail).toHaveBeenCalledWith(userData.email); expect(userRepository.create).toHaveBeenCalledWith(userData); expect(result).toHaveProperty('id', 1); expect(result).toHaveProperty('name', userData.name); expect(result).toHaveProperty('email', userData.email); }); it('should throw ValidationError if email already exists', async () => { // Arrange const userData = { name: 'John Doe', email: 'john@example.com', password: 'password123', }; userRepository.findByEmail.mockResolvedValue({ id: 1, name: 'Existing User', email: userData.email, role: 'USER', createdAt: new Date(), updatedAt: new Date(), }); // Act & Assert await expect(userService.create(userData)).rejects.toThrow(ValidationError); expect(userRepository.create).not.toHaveBeenCalled(); }); it('should throw ValidationError if email is invalid', async () => { // Arrange const userData = { name: 'John Doe', email: 'invalid-email', password: 'password123', }; // Act & Assert await expect(userService.create(userData)).rejects.toThrow(ValidationError); expect(userRepository.findByEmail).not.toHaveBeenCalled(); expect(userRepository.create).not.toHaveBeenCalled(); }); }); describe('update', () => { it('should update a user with valid data', async () => { // Arrange const userId = 1; const userData = { name: 'Updated Name', }; userRepository.findById.mockResolvedValue({ id: userId, name: 'Original Name', email: 'user@example.com', role: 'USER', createdAt: new Date(), updatedAt: new Date(), }); userRepository.update.mockResolvedValue({ id: userId, name: 'Updated Name', email: 'user@example.com', role: 'USER', createdAt: new Date(), updatedAt: new Date(), }); // Act const result = await userService.update(userId, userData); // Assert expect(userRepository.findById).toHaveBeenCalledWith(userId); expect(userRepository.update).toHaveBeenCalledWith(userId, userData); expect(result).toHaveProperty('id', userId); expect(result).toHaveProperty('name', 'Updated Name'); }); it('should throw NotFoundError if user does not exist', async () => { // Arrange const userId = 999; const userData = { name: 'Updated Name', }; userRepository.findById.mockResolvedValue(null); // Act & Assert await expect(userService.update(userId, userData)).rejects.toThrow(NotFoundError); expect(userRepository.update).not.toHaveBeenCalled(); }); }); }); ``` ## Best Practices ### Code Structure Best Practices - Keep files small and focused on a single responsibility - Use consistent naming conventions across the codebase - Group related functionality together - Separate business logic from infrastructure concerns - Maintain clear dependency boundaries ### API Design Best Practices - Use nouns for resource endpoints - Keep URLs clean and simple - Use HTTP methods appropriately - Return appropriate status codes - Document all endpoints ### Security Best Practices - Never trust client input - Implement proper authentication and authorization - Follow the principle of least privilege - Keep dependencies up to date - Run regular security scans ### Performance Best Practices - Implement appropriate caching - Optimize database queries - Use async/await for I/O operations - Monitor and optimize bottlenecks - Consider pagination for large data sets ### Testing Best Practices - Write tests at multiple levels (unit, integration, system) - Use test doubles (mocks, stubs, spies) appropriately - Aim for high code coverage - Test happy paths and edge cases - Incorporate testing into the development workflow