# 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<T> {
  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"
//   }
// }
```
