# ADR-028: API Response Standardization and Envelope Pattern **Date**: 2026-01-09 **Status**: Implemented ## Context The API currently has inconsistent response formats across different endpoints: 1. Some endpoints return raw data arrays (`[{...}, {...}]`) 2. Some return wrapped objects (`{ data: [...] }`) 3. Pagination is handled inconsistently (some use `page`/`limit`, others use `offset`/`count`) 4. Error responses vary in structure between middleware and route handlers 5. No standard for including metadata (pagination info, request timing, etc.) This inconsistency creates friction for: - Frontend developers who must handle multiple response formats - API documentation and client SDK generation - Implementing consistent error handling across the application - Future API versioning transitions ## Decision We will adopt a standardized response envelope pattern for all API responses. ### Success Response Format ```typescript interface ApiSuccessResponse { success: true; data: T; meta?: { // Pagination (when applicable) pagination?: { page: number; limit: number; total: number; totalPages: number; hasNextPage: boolean; hasPrevPage: boolean; }; // Timing requestId?: string; timestamp?: string; duration?: number; }; } ``` ### Error Response Format ```typescript interface ApiErrorResponse { success: false; error: { code: string; // Machine-readable error code (e.g., 'VALIDATION_ERROR') message: string; // Human-readable message details?: unknown; // Additional context (validation errors, etc.) }; meta?: { requestId?: string; timestamp?: string; }; } ``` ### Implementation Approach 1. **Response Helper Functions**: Create utility functions in `src/utils/apiResponse.ts`: - `sendSuccess(res, data, meta?)` - `sendPaginated(res, data, pagination)` - `sendError(res, code, message, details?, statusCode?)` 2. **Error Handler Integration**: Update `errorHandler.ts` to use the standard error format 3. **Gradual Migration**: Apply to new endpoints immediately, migrate existing endpoints incrementally 4. **TypeScript Types**: Export response types for frontend consumption ## Consequences ### Positive - **Consistency**: All responses follow a predictable structure - **Type Safety**: Frontend can rely on consistent types - **Debugging**: Request IDs and timestamps aid in issue investigation - **Pagination**: Standardized pagination metadata reduces frontend complexity - **API Evolution**: Envelope pattern makes it easier to add fields without breaking changes ### Negative - **Verbosity**: Responses are slightly larger due to envelope overhead - **Migration Effort**: Existing endpoints need updating - **Learning Curve**: Developers must learn and use the helper functions ## Implementation Status ### What's Implemented - ✅ Created `src/utils/apiResponse.ts` with helper functions (`sendSuccess`, `sendPaginated`, `sendError`, `sendNoContent`, `sendMessage`, `calculatePagination`) - ✅ Created `src/types/api.ts` with response type definitions (`ApiSuccessResponse`, `ApiErrorResponse`, `PaginationMeta`, `ErrorCode`) - ✅ Updated `src/middleware/errorHandler.ts` to use standard error format - ✅ Migrated all route files to use standardized responses: - `health.routes.ts` - `flyer.routes.ts` - `deals.routes.ts` - `budget.routes.ts` - `personalization.routes.ts` - `price.routes.ts` - `reactions.routes.ts` - `stats.routes.ts` - `system.routes.ts` - `gamification.routes.ts` - `recipe.routes.ts` - `auth.routes.ts` - `user.routes.ts` - `admin.routes.ts` - `ai.routes.ts` ### Error Codes The following error codes are defined in `src/types/api.ts`: | Code | HTTP Status | Description | | ------------------------ | ----------- | ----------------------------------- | | `VALIDATION_ERROR` | 400 | Request validation failed | | `BAD_REQUEST` | 400 | Malformed request | | `UNAUTHORIZED` | 401 | Authentication required | | `FORBIDDEN` | 403 | Insufficient permissions | | `NOT_FOUND` | 404 | Resource not found | | `CONFLICT` | 409 | Resource conflict (e.g., duplicate) | | `RATE_LIMITED` | 429 | Too many requests | | `PAYLOAD_TOO_LARGE` | 413 | Request body too large | | `INTERNAL_ERROR` | 500 | Server error | | `NOT_IMPLEMENTED` | 501 | Feature not yet implemented | | `SERVICE_UNAVAILABLE` | 503 | Service temporarily unavailable | | `EXTERNAL_SERVICE_ERROR` | 502 | External service failure | ## Example Usage ```typescript // In a route handler router.get('/flyers', async (req, res, next) => { try { const { page = 1, limit = 20 } = req.query; const { flyers, total } = await flyerService.getFlyers({ page, limit }); return sendPaginated(res, flyers, { page, limit, total, }); } catch (error) { next(error); } }); // Response: // { // "success": true, // "data": [...], // "meta": { // "pagination": { // "page": 1, // "limit": 20, // "total": 150, // "totalPages": 8, // "hasNextPage": true, // "hasPrevPage": false // }, // "requestId": "abc-123", // "timestamp": "2026-01-09T12:00:00.000Z" // } // } ```