Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 16m58s
3.9 KiB
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:
- Some endpoints return raw data arrays (
[{...}, {...}]) - Some return wrapped objects (
{ data: [...] }) - Pagination is handled inconsistently (some use
page/limit, others useoffset/count) - Error responses vary in structure between middleware and route handlers
- 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
-
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?)
-
Error Handler Integration: Update
errorHandler.tsto use the standard error format -
Gradual Migration: Apply to new endpoints immediately, migrate existing endpoints incrementally
-
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
- Create
src/utils/apiResponse.tswith helper functions - Create
src/types/api.tswith response type definitions - Update
errorHandler.tsto use standard error format - Create migration guide for existing endpoints
- Update 2-3 routes as examples
- 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"
// }
// }