# ADR-007: API Design & Error Handling Strategy **Status:** ✅ Accepted **Date:** 2025-12-01 **Decision Makers:** Backend Team, System Architect **Related Documents:** [Backend Guidelines](../03-implementation/backend-guidelines.md), [ADR-005: Technology Stack](./ADR-005-technology-stack.md) --- ## Context and Problem Statement ระบบ LCBP3-DMS ต้องการมาตรฐานการออกแบบ API ที่ชัดเจนและสม่ำเสมอทั้งระบบ รวมถึงกลยุทธ์การจัดการ Error และ Validation ที่เหมาะสม ### ปัญหาที่ต้องแก้: 1. **API Consistency:** ทำอย่างไรให้ API response format สม่ำเสมอทั้งระบบ 2. **Error Handling:** จัดการ error อย่างไรให้ client เข้าใจและแก้ไขได้ 3. **Validation:** Validate request อย่างไรให้ครอบคลุมและให้ feedback ที่ดี 4. **Status Codes:** ใช้ HTTP status codes อย่างไรให้ถูกต้องและสม่ำเสมอ --- ## Decision Drivers - 🎯 **Developer Experience:** Frontend developers ต้องใช้ API ได้ง่าย - 🔒 **Security:** ป้องกัน Information Leakage จาก Error messages - 📊 **Debuggability:** ต้องหา Root cause ของ Error ได้ง่าย - 🌍 **Internationalization:** รองรับภาษาไทยและอังกฤษ - 📝 **Standards Compliance:** ใช้มาตรฐานที่เป็นที่ยอมรับ (REST, JSON:API) --- ## Considered Options ### Option 1: Standard REST with Custom Error Format **รูปแบบ:** ```typescript // Success { "data": { ... }, "meta": { "timestamp": "..." } } // Error { "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": [...] } } ``` **Pros:** - ✅ Simple และเข้าใจง่าย - ✅ Flexible สำหรับ Custom needs - ✅ ไม่ต้อง Follow spec ที่ซับซ้อน **Cons:** - ❌ ไม่มี Standard specification - ❌ ต้องสื่อสารภายในทีมให้ชัดเจน - ❌ อาจไม่สม่ำเสมอหากไม่ระวัง ### Option 2: JSON:API Specification **รูปแบบ:** ```typescript { "data": { "type": "correspondences", "id": "1", "attributes": { ... }, "relationships": { ... } }, "included": [...] } ``` **Pros:** - ✅ มาตรฐานที่เป็นที่ยอมรับ - ✅ มี Libraries ช่วย - ✅ รองรับ Relationships ได้ดี **Cons:** - ❌ ซับซ้อนเกินความจำเป็น - ❌ Verbose (ข้อมูลซ้ำซ้อน) - ❌ Learning curve สูง ### Option 3: GraphQL **Pros:** - ✅ Client เลือกข้อมูลที่ต้องการได้ - ✅ ลด Over-fetching/Under-fetching - ✅ Strong typing **Cons:** - ❌ Complexity สูง - ❌ Caching ยาก - ❌ ไม่เหมาะกับ Document-heavy system - ❌ Team ยังไม่มีประสบการณ์ --- ## Decision Outcome **Chosen Option:** **Option 1 - Standard REST with Custom Error Format + NestJS Exception Filters** ### Rationale 1. **Simplicity:** ทีมคุ้นเคยกับ REST API และ NestJS มี Built-in support ที่ดี 2. **Flexibility:** สามารถปรับแต่งตาม Business needs ได้ง่าย 3. **Performance:** Lightweight กว่า JSON:API และ GraphQL 4. **Team Capability:** ทีมมีประสบการณ์ REST มากกว่า GraphQL --- ## Implementation Details ### 1. Success Response Format ```typescript // Single resource { "data": { "id": 1, "document_number": "CORR-2024-0001", "subject": "...", ... }, "meta": { "timestamp": "2024-01-01T00:00:00Z", "version": "1.0" } } // Collection with pagination { "data": [ { "id": 1, ... }, { "id": 2, ... } ], "meta": { "pagination": { "page": 1, "limit": 20, "total": 100, "totalPages": 5 }, "timestamp": "2024-01-01T00:00:00Z" } } ``` ### 2. Error Response Format ```typescript { "error": { "code": "VALIDATION_ERROR", "message": "Validation failed on input data", "statusCode": 400, "timestamp": "2024-01-01T00:00:00Z", "path": "/api/correspondences", "details": [ { "field": "subject", "message": "Subject is required", "value": null } ] } } ``` ### 3. HTTP Status Codes | Status | Use Case | | ------------------------- | ------------------------------------------- | | 200 OK | Successful GET, PUT, PATCH | | 201 Created | Successful POST | | 204 No Content | Successful DELETE | | 400 Bad Request | Validation error, Invalid input | | 401 Unauthorized | Missing or invalid JWT token | | 403 Forbidden | Insufficient permissions (RBAC) | | 404 Not Found | Resource not found | | 409 Conflict | Duplicate resource, Business rule violation | | 422 Unprocessable Entity | Business logic error | | 429 Too Many Requests | Rate limit exceeded | | 500 Internal Server Error | Unexpected server error | ### 4. Global Exception Filter ```typescript // File: backend/src/common/filters/global-exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, } from '@nestjs/common'; @Catch() export class GlobalExceptionFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); let status = HttpStatus.INTERNAL_SERVER_ERROR; let code = 'INTERNAL_SERVER_ERROR'; let message = 'An unexpected error occurred'; let details = null; if (exception instanceof HttpException) { status = exception.getStatus(); const exceptionResponse = exception.getResponse(); if (typeof exceptionResponse === 'object') { code = (exceptionResponse as any).error || exception.name; message = (exceptionResponse as any).message || exception.message; details = (exceptionResponse as any).details; } else { message = exceptionResponse; } } // Log error (but don't expose internal details to client) console.error('Exception:', exception); response.status(status).json({ error: { code, message, statusCode: status, timestamp: new Date().toISOString(), path: request.url, ...(details && { details }), }, }); } } ``` ### 5. Custom Business Exception ```typescript // File: backend/src/common/exceptions/business.exception.ts export class BusinessException extends HttpException { constructor(message: string, code: string = 'BUSINESS_ERROR') { super( { error: code, message, }, HttpStatus.UNPROCESSABLE_ENTITY ); } } // Usage throw new BusinessException( 'Cannot approve correspondence in current status', 'INVALID_WORKFLOW_TRANSITION' ); ``` ### 6. Validation Pipe Configuration ```typescript // File: backend/src/main.ts app.useGlobalPipes( new ValidationPipe({ whitelist: true, // Strip properties not in DTO forbidNonWhitelisted: true, // Throw error if unknown properties transform: true, // Auto-transform payloads to DTO instances transformOptions: { enableImplicitConversion: true, }, exceptionFactory: (errors) => { const details = errors.map((error) => ({ field: error.property, message: Object.values(error.constraints || {}).join(', '), value: error.value, })); return new HttpException( { error: 'VALIDATION_ERROR', message: 'Validation failed', details, }, HttpStatus.BAD_REQUEST ); }, }) ); ``` --- ## Consequences ### Positive Consequences 1. ✅ **Consistency:** API responses มีรูปแบบสม่ำเสมอทั้งระบบ 2. ✅ **Developer Friendly:** Frontend developers ใช้งาน API ได้ง่าย 3. ✅ **Debuggability:** Error messages ให้ข้อมูลเพียงพอสำหรับ Debug 4. ✅ **Security:** ไม่เปิดเผย Internal error details ให้ Client 5. ✅ **Maintainability:** ใช้ NestJS built-in features ทำให้ Maintain ง่าย ### Negative Consequences 1. ❌ **No Standard Spec:** ไม่ใช่ Standard เช่น JSON:API จึงต้องเขียน Documentation ชัดเจน 2. ❌ **Manual Documentation:** ต้อง Document API response format เอง 3. ❌ **Learning Curve:** Team members ใหม่ต้องเรียนรู้ Error code conventions ### Mitigation Strategies - **Documentation:** ใช้ Swagger/OpenAPI เพื่อ Auto-generate API docs - **Code Generation:** Generate TypeScript interfaces สำหรับ Frontend จาก DTOs - **Error Code Registry:** มี Centralized list ของ Error codes พร้อมคำอธิบาย - **Testing:** เขียน Integration tests เพื่อ Validate response formats --- ## Related ADRs - [ADR-005: Technology Stack](./ADR-005-technology-stack.md) - เลือกใช้ NestJS - [ADR-004: RBAC Implementation](./ADR-004-rbac-implementation.md) - Error 403 Forbidden --- ## References - [NestJS Exception Filters](https://docs.nestjs.com/exception-filters) - [HTTP Status Codes](https://httpstatuses.com/) - [REST API Best Practices](https://restfulapi.net/) --- **Last Updated:** 2025-12-01 **Next Review:** 2025-06-01