Files
flyer-crawler.projectium.com/docs/adr/0028-api-response-standardization.md
Torben Sorensen 4a04e478c4
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 16m58s
integration test fixes - claude for the win? try 4 - i have a good feeling
2026-01-09 05:56:19 -08:00

3.9 KiB

ADR-028: API Response Standardization and Envelope Pattern

Date: 2026-01-09

Status: Proposed

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

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

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

  • Not yet implemented

What Needs To Be Done

  1. Create src/utils/apiResponse.ts with helper functions
  2. Create src/types/api.ts with response type definitions
  3. Update errorHandler.ts to use standard error format
  4. Create migration guide for existing endpoints
  5. Update 2-3 routes as examples
  6. Document pattern in this ADR

Example Usage

// 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"
//   }
// }