# 02.4 API Design & Error Handling (การออกแบบ API และการจัดการข้อผิดพลาด) --- **title:** 'API Design & Error Handling' **version:** 1.8.0 **status:** active **owner:** Nattanin Peancharoen **last_updated:** 2026-02-23 **related:** - specs/02-Architecture/00-01-system-context.md - specs/02-Architecture/02-03-software-architecture.md - specs/03-Implementation/03-01-fullstack-js-v1.7.0.md --- ## 1. 📋 ภาพรวม (Overview) เอกสารนี้กำหนดมาตรฐานการออกแบบ API สำหรับระบบ LCBP3-DMS โดยใช้แนวทาง **API-First Design** ที่เน้นความชัดเจน ความสอดคล้อง และความปลอดภัย รวมถึงกลยุทธ์การจัดการ Error (Error Handling) สำหรับ Backend (NestJS) ## 2. 🎯 หลักการออกแบบ API (API Design Principles) ### 2.1 API-First Approach - **ออกแบบ API ก่อนการ Implement:** ทำการออกแบบ API Endpoint และ Data Contract ให้ชัดเจนก่อนเริ่มเขียนโค้ด - **Documentation-Driven:** ใช้ OpenAPI/Swagger เป็นเอกสารอ้างอิงหลัก - **Contract Testing:** ทดสอบ API ตาม Contract ที่กำหนดไว้ ### 2.2 RESTful Principles - ใช้ HTTP Methods อย่างถูกต้อง: `GET`, `POST`, `PUT`, `PATCH`, `DELETE` - ใช้ HTTP Status Codes ที่เหมาะสม - Resource-Based URL Design - Stateless Communication ### 2.3 Consistency & Predictability - **Naming Conventions:** ใช้ `kebab-case` สำหรับ URL paths - **Property Naming:** ใช้ `camelCase` สำหรับ JSON properties และ query parameters (สอดคล้องกับ TypeScript/JavaScript conventions) - **Database Columns:** Database ใช้ `snake_case` (mapped via TypeORM decorators) - **Versioning:** รองรับการ Version API ผ่าน URL path (`/api/v1/...`) ## 3. 🔐 Authentication & Authorization ### 3.1 Authentication - **JWT-Based Authentication:** ใช้ JSON Web Token สำหรับการยืนยันตัวตน - **Token Management:** - Access Token Expiration: 8 ชั่วโมง - Refresh Token Expiration: 7 วัน - Token Rotation: รองรับการหมุนเวียน Refresh Token - Token Revocation: บันทึก Revoked Tokens จนกว่าจะหมดอายุ **Endpoints คอร์:** ```typescript POST /api/v1/auth/login POST /api/v1/auth/logout POST /api/v1/auth/refresh POST /api/v1/auth/change-password ``` ### 3.2 Authorization (RBAC) (CASL) ใช้ระบบ 4-Level Permission Hierarchy (Global, Organization, Project, Contract) - **Permission Checking:** ใช้ Decorator `@RequirePermission('resource.action')` **Example:** ```typescript @RequirePermission('correspondence.create') @Post('correspondences') async createCorrespondence(@Body() dto: CreateCorrespondenceDto) { // Implementation } ``` ### 3.3 Token Payload Optimization - JWT Payload เก็บเฉพาะ `userId` และ `scope` ปัจจุบัน - **Permissions Caching:** เก็บ Permission List ใน Redis และดึงมาตรวจสอบเมื่อมี Request ## 4. 📡 API Conventions ### 4.1 Base URL Structure ``` https://backend.np-dms.work/api/v1/{resource} ``` ### 4.2 HTTP Methods & Usage | Method | Usage | Idempotent | Example | | :------- | :--------------------------- | :--------- | :----------------------------------- | | `GET` | ดึงข้อมูล (Read) | ✅ Yes | `GET /api/v1/correspondences` | | `POST` | สร้างข้อมูลใหม่ (Create) | ❌ No\* | `POST /api/v1/correspondences` | | `PUT` | อัปเดตทั้งหมด (Full Update) | ✅ Yes | `PUT /api/v1/correspondences/:id` | | `PATCH` | อัปเดตบางส่วน (Partial Update) | ✅ Yes | `PATCH /api/v1/correspondences/:id` | | `DELETE` | ลบข้อมูล (Soft Delete) | ✅ Yes | `DELETE /api/v1/correspondences/:id` | * **Note:** `POST` เป็น Idempotent ได้เมื่อใช้ `Idempotency-Key` Header ### 4.3 Request Format **Request Headers:** ```http Content-Type: application/json Authorization: Bearer Idempotency-Key: # สำหรับ POST/PUT/DELETE ``` ### 4.4 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 | ## 5. 🔄 Response Formats (Standard REST with Meta Data) เราใช้ Standard REST with Custom Error Format ซึ่งเรียบง่ายและยืดหยุ่นกว่า ### 5.1 Success Response **Single Resource:** ```typescript { "data": { "id": 1, "document_number": "CORR-2024-0001", "subject": "...", }, "meta": { "timestamp": "2024-01-01T00:00:00Z", "version": "1.0" } } ``` **Collection (Pagination):** ```typescript { "data": [ { "id": 1, ... }, { "id": 2, ... } ], "meta": { "pagination": { "page": 1, "limit": 20, "total": 100, "totalPages": 5 }, "timestamp": "2024-01-01T00:00:00Z" } } ``` ### 5.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 } ] } } ``` ## 6. 🛠️ NestJS Implementation Details ### 6.1 Global Exception Filter คลาสจัดการ Error หลักที่จะจับและดัดแปลง Error ส่งคืน Client อย่างสม่ำเสมอ ```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 }), }, }); } } ``` ### 6.2 Custom Business Exception สำหรับจัดการข้อผิดพลาดเชิงความสัมพันธ์ หรือเงื่อนไขธุรกิจ เช่น State Conflict. ```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 Example: throw new BusinessException( 'Cannot approve correspondence in current status', 'INVALID_WORKFLOW_TRANSITION' ); ``` ### 6.3 Validation Pipe Configuration บังคับ Validation Pipe ก่อนส่งพารามิเตอร์ให้กับ Controller ```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 ); }, }) ); ``` ## 7. 🛡️ API Security & Rate Limiting ### 7.1 Rate Limiting (Redis-backed) | Endpoint Type | Limit | Scope | | :------------------ | :----------------- | :---- | | Anonymous Endpoints | 100 requests/hour | IP | | Viewer | 500 requests/hour | User | | Editor | 1000 requests/hour | User | | Document Control | 2000 requests/hour | User | | Admin/Superadmin | 5000 requests/hour | User | | File Upload | 50 requests/hour | User | | Search | 500 requests/hour | User | | Authentication | 10 requests/minute | IP | ### 7.2 File Upload Security - **Virus Scanning:** ใช้ ClamAV scan ทุกไฟล์ - **File Type Validation:** White-list (PDF, DWG, DOCX, XLSX, ZIP) - **File Size Limit:** 50MB per file - **Two-Phase Storage:** 1. Upload to `temp/` folder 2. Commit to `permanent/{YYYY}/{MM}/` when operation succeeds ## 8. 🔄 Idempotency - **ทุก Critical Operation** (Create, Update, Delete) ต้องรองรับ Idempotency - Client ส่ง Header: `Idempotency-Key: ` - Server เช็คว่า Key นี้เคยประมวลผลสำเร็จแล้วหรือไม่ - ถ้าเคยทำแล้ว: ส่งผลลัพธ์เดิมกลับไป (ไม่ทำซ้ำ) ## 9. 📈 Optimization & Additional Guidelines ### 9.1 Caching Strategy - Master Data: 1 hour - User Sessions: 30 minutes - Search Results: 15 minutes - File Metadata: 1 hour ### 9.2 API Versioning - **URL-Based Versioning:** `/api/v1/...`, `/api/v2/...` - **Backward Compatibility:** รองรับ API เวอร์ชันเก่าอย่างน้อย 1 เวอร์ชัน - ใช้ Deprecation Headers เมื่อมีการยกเลิก Endpoints ### 9.3 Documentation - **Swagger/OpenAPI:** Auto-generated จาก NestJS Decorators - **URL:** `https://backend.np-dms.work/api/docs` ## 🎯 สรุป Best Practices 1. **ใช้ DTOs สำหรับ Validation ทุก Request** 2. **ส่ง Idempotency-Key สำหรับ Critical Operations** 3. **ใช้ Proper HTTP Status Codes** 4. **Implement Rate Limiting บน Client Side** 5. **Handle Errors Gracefully และอย่าเปิดเผยข้อผิดพลาดภายใน (DB Errors) สู่ Client** 6. **Cache Frequently-Accessed Data** 7. **Use Pagination สำหรับ Large Datasets เสมอ** 8. **Document ทุก Endpoint ด้วย Swagger**