@webberec/api-contracts
v2.0.1
Published
Shared TypeScript types and utilities for API contracts across microservices
Maintainers
Readme
@webberec/api-contracts
Shared TypeScript types and utilities for consistent API contracts across Webber Electrocorp microservices.
Table of Contents
- Features
- Installation
- Response Builder Functions
- Usage Examples
- Complete Example
- API Reference
- Error Handling
- Response Examples
- Change Log
Features
- Response Builders:
createSuccessResponse,createPaginatedResponse,createErrorResponse. - Standardized Responses:
ApiResponse,PaginatedResponse, andApiErrorResponse. - Flexible Error Response Structure supporting both string and object error arrays
- Comprehensive Error Handling with custom error classes
- HTTP Status Code Management with error code mapping
- Pagination Utilities with metadata calculation
- TypeScript Support with full type definitions
- Express Middleware for error handling
Installation
npm install @webberec/api-contractsResponse Builder Functions
import {
createSuccessResponse,
createPaginatedResponse,
createErrorResponse
} from '@webberec/api-contracts';- createSuccessResponse
createSuccessResponse<T>(
data: T,
message?: string,
pagination?: PaginationMeta,
meta?: Record<string, unknown>
): ApiResponse<T>- createPaginatedResponse
createPaginatedResponse<T>(
data: T[],
pagination: PaginationMeta,
message?: string,
meta?: Record<string, unknown>
): PaginatedResponse<T>- createErrorResponse
createErrorResponse(
code: ErrorCode,
message: string,
errors?: (string|object)[]
): ApiErrorResponseUsage Examples
Creating Responses
const success = createSuccessResponse(
{ id: 'u1', name: 'Alice' },
'User fetched'
);
const paginated = createPaginatedResponse(
usersArray,
calculatePagination(1, 10, totalCount),
'User list'
);
const error = createErrorResponse(
ErrorCode.NOT_FOUND,
'User not found',
[`No user with ID ${userId}`]
);Express Integration
import express from 'express';
import {
ApiResponse,
ApiErrorResponse,
ErrorCode,
createSuccessResponse,
createErrorResponse,
calculatePagination,
notFoundHandler,
errorHandler
} from '@webberec/api-contracts';
interface MyHeaders {
authorization?: string;
'x-customer-id'?: string;
'x-trace-id'?: string;
}
const app = express();
app.use(express.json());
app.get(
'/api/users',
(req, res: express.Response | express.Response<ApiErrorResponse>) => {
const { page, limit } = req.query;
const users = getUsers(page, limit);
if (!users) {
return res.status(404).json(createErrorResponse(
ErrorCode.NOT_FOUND, 'No users found'
));
}
const pagination = calculatePagination(page, limit, totalUsers);
res.json(createPaginatedResponse(users, pagination, 'Users list'));
}
);
app.use('*', notFoundHandler);
app.use(errorHandler);
app.listen(3000);cURL Example
curl -X GET http://localhost:3000/api/user-profile \
-H "Authorization: Bearer abc123" \
-H "X-Customer-Id: cust-789" \
-H "X-Trace-Id: trace-456"Basic Usage with ApiResponse
import {
ApiResponse,
ApiErrorResponse,
ErrorCode,
HttpStatusCode
} from '@webberec/api-contracts';
// Define your response data types
interface User {
id: string;
email: string;
name: string;
}
// Use in your Express route
app.post('/users', async (req, res) => {
const user = await createUser(req.body);
const response: ApiResponse<User> = {
status: 'success',
data: user,
message: 'User created successfully'
};
res.status(HttpStatusCode.CREATED).json(response);
});Complete Example
import express from 'express';
import {
ApiResponse,
PaginatedResponse,
ApiErrorResponse,
ErrorCode,
HttpStatusCode,
AppError,
errorHandler,
notFoundHandler,
PaginationMeta,
createSuccessResponse,
createPaginatedResponse,
createErrorResponse,
calculatePagination
} from '@webberec/api-contracts';
const app = express();
app.use(express.json());
// Types
interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
// Mock data
const users: User[] = [];
// Routes
app.post('/api/users', async (req: Request, res: express.Response<ApiResponse<User> | ApiErrorResponse>) => {
try {
const { email, name } = req.body;
// Check if user already exists
const existingUser = users.find(u => u.email === email);
if (existingUser) {
const errorResponse = createErrorResponse(
ErrorCode.ALREADY_EXISTS,
'User already exists',
[
{ field: 'email', message: 'Email is already registered', value: email },
'Please use a different email address'
]
);
return res.status(HttpStatusCode.CONFLICT).json(errorResponse);
}
const user: User = {
id: `user-${Date.now()}`,
email,
name,
createdAt: new Date()
};
users.push(user);
const response = createSuccessResponse(
user,
'User created successfully',
undefined,
{ timestamp: new Date().toISOString() }
);
res.status(HttpStatusCode.CREATED).json(response);
} catch (error) {
throw error; // Will be handled by global error handler
}
});
app.get('/api/users', async (req: Request, res: express.Response<PaginatedResponse<User>>) => {
const page = Number(req.query.page) || 1;
const limit = Number(req.query.limit) || 10;
const search = req.query.search;
let filteredUsers = users;
if (search) {
const searchTerm = search.toLowerCase();
filteredUsers = users.filter(u =>
u.name.toLowerCase().includes(searchTerm) ||
u.email.toLowerCase().includes(searchTerm)
);
}
const total = filteredUsers.length;
const startIndex = (page - 1) * limit;
const paginatedUsers = filteredUsers.slice(startIndex, startIndex + limit);
const pagination = calculatePagination(page, limit, total);
const response = createPaginatedResponse(
paginatedUsers,
pagination,
search ? `Found ${total} users matching "${search}"` : undefined,
{ timestamp: new Date().toISOString() }
);
res.json(response);
});
app.get('/api/users/:id', async (req: Request, res: express.Response<ApiResponse<User> | ApiErrorResponse>) => {
const user = users.find(u => u.id === req.params.id);
if (!user) {
const errorResponse = createErrorResponse(
ErrorCode.NOT_FOUND,
'User not found',
[`No user found with ID: ${req.params.id}`]
);
return res.status(HttpStatusCode.NOT_FOUND).json(errorResponse);
}
const response = createSuccessResponse(user);
res.json(response);
});
// Apply error handling middleware
app.use('*', notFoundHandler);
app.use(errorHandler);
app.listen(3000, () => {
console.log('Server running on port 3000');
});
API Reference
Response Builders Functions
Utility functions to create standardized API responses for success, pagination, and errors. These ensure consistent response formats across all microservices.
createSuccessResponse<T>(data, message?, pagination?, meta?)
Creates a standardized success response.
Type Parameters
T: Data payload type.
Parameters
data: The response data.message(optional): A success message string.pagination(optional): Pagination metadata.meta(optional): Additional metadata object.
Returns
ApiResponse<T>
Example
import { createSuccessResponse } from '@webberec/api-contracts';
const response = createSuccessResponse(
{ id: 1, name: 'Alice' },
'User fetched successfully'
);
console.log(response);
// {
// status: "success",
// data: { id: 1, name: "Alice" },
// message: "User fetched successfully"
// }createPaginatedResponse<T>(data, pagination, message?, meta?)
Creates a standardized paginated success response.
Type Parameters
T: Item type for returned array.
Parameters
data: Array of response data.pagination: Pagination metadata (page, limit, total, etc.).message(optional): A success message string.meta(optional): Additional metadata object.
Returns
PaginatedResponse<T>
Example
import { createPaginatedResponse } from '@webberec/api-contracts';
const response = createPaginatedResponse(
[{ id: 1 }, { id: 2 }],
{ page: 1, limit: 2, total: 10 },
'Users fetched successfully',
{ source: 'database' }
);
console.log(response);
// {
// status: "success",
// data: [{ id: 1 }, { id: 2 }],
// pagination: { page: 1, limit: 2, total: 10 },
// message: "Users fetched successfully",
// meta: { source: "database" }
// }createErrorResponse(code, message, errors?)
Creates a standardized error response.
Parameters
code: Error code from theErrorCodeenum.message: Human-readable error message.errors(optional): Additional error details (array of strings or objects).
Returns
ApiErrorResponse
Example
import { createErrorResponse, ErrorCode } from '@webberec/api-contracts';
const response = createErrorResponse(
ErrorCode.NOT_FOUND,
'User not found',
['No user records match the given id']
);
console.log(response);
// {
// code: "NOT_FOUND",
// status: "error",
// message: "User not found",
// errors: ["No user records match the given id"]
// }When to Use
- Use
createSuccessResponsewhen returning a single resource or generic success payload. - Use
createPaginatedResponsewhen results include pagination metadata. - Use
createErrorResponsefor structured error handling with standardized codes.
Response Types
ApiResponse<T>
Standard success response envelope.
interface ApiResponse<T = unknown> {
status: 'success';
data: T;
message?: string;
meta?: {
timestamp?: string;
requestId?: string;
[key: string]: unknown;
};
}Example:
const response: ApiResponse<User[]> = {
status: 'success',
data: users,
message: 'Users retrieved successfully',
meta: {
timestamp: '2025-09-26T15:30:00Z',
requestId: 'req-12345'
}
};PaginatedResponse<T>
Paginated data response.
interface PaginatedResponse<T = unknown> {
status: 'success';
data: T[];
message?: string;
pagination: PaginationMeta;
meta: {
timestamp?: string;
requestId?: string;
[key: string]: unknown;
};
}PaginationMeta
Pagination metadata.
interface PaginationMeta {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
}Error Types
ApiErrorResponse
Standard error response format.
interface ApiErrorResponse {
code: number | string;
status: 'error';
message: string;
errors: (string | object)[];
}Example with mixed error types:
const errorResponse: ApiErrorResponse = {
code: 400,
status: 'error',
message: 'Validation failed',
errors: [
{ field: 'email', message: 'Email is required', value: null },
'Please provide all required fields'
]
};ValidationError
Field-level validation error.
interface ValidationError {
field: string;
message: string;
value?: unknown;
}ErrorDetail
Generic structured error detail.
interface ErrorDetail {
type?: string;
message: string;
context?: Record<string, unknown>;
}Enums
ErrorCode
Standard error codes with HTTP status values.
enum ErrorCode {
// Validation Errors (400)
VALIDATION_ERROR = 400,
INVALID_INPUT = 400,
MISSING_REQUIRED_FIELD = 400,
BAD_REQUEST = 400,
// Authentication & Authorization
UNAUTHORIZED = 401,
FORBIDDEN = 403,
INVALID_TOKEN = 401,
EXPIRED_TOKEN = 401,
INSUFFICIENT_PERMISSIONS = 403,
// Resource Errors
NOT_FOUND = 404,
ALREADY_EXISTS = 409,
CONFLICT = 409,
GONE = 410,
// Request Errors
METHOD_NOT_ALLOWED = 405,
UNSUPPORTED_MEDIA_TYPE = 415,
REQUEST_TOO_LARGE = 413,
// Rate Limiting
RATE_LIMIT_EXCEEDED = 429,
QUOTA_EXCEEDED = 429,
// Server Errors
INTERNAL_ERROR = 500,
SERVICE_UNAVAILABLE = 503,
DATABASE_ERROR = 500,
EXTERNAL_SERVICE_ERROR = 502,
TIMEOUT = 504,
// Business Logic Errors
BUSINESS_RULE_VIOLATION = 422,
INSUFFICIENT_BALANCE = 422,
OPERATION_NOT_ALLOWED = 422,
RESOURCE_LOCKED = 423,
}HttpStatusCode
HTTP status codes.
enum HttpStatusCode {
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NO_CONTENT = 204,
// Redirection
MOVED_PERMANENTLY = 301,
FOUND = 302,
NOT_MODIFIED = 304,
}Error Handling
AppError Class
Custom error class for consistent error throwing.
import { AppError, ErrorCode } from '@webberec/api-contracts';
throw new AppError(
ErrorCode.NOT_FOUND,
'User not found',
[`No user found with ID: ${userId}`]
);Global Error Handler
Express middleware for centralized error handling.
import { errorHandler, notFoundHandler } from '@webberec/api-contracts';
// Apply to your Express app
app.use('/api', routes);
// Handle 404s
app.use('*', notFoundHandler);
// Global error handler (must be last)
app.use(errorHandler);Response Examples
Success Response
{
"status": "success",
"data": {
"id": "user-123",
"email": "[email protected]",
"name": "John Doe"
},
"message": "User retrieved successfully"
}Paginated Response
{
"status": "success",
"data": [
{ "id": "1", "name": "User 1" },
{ "id": "2", "name": "User 2" }
],
"pagination": {
"page": 1,
"limit": 10,
"total": 25,
"totalPages": 3,
"hasNext": true,
"hasPrev": false
},
"meta": {
"timestamp": "2025-09-26T15:30:00Z"
}
}Error Response with Mixed Error Types
{
"code": 400,
"status": "error",
"message": "Validation failed",
"errors": [
{
"field": "email",
"message": "Valid email is required",
"value": "invalid-email"
},
{
"field": "age",
"message": "Age must be at least 18",
"value": 16
},
"Please correct the validation errors and try again"
]
}Business Logic Error
{
"code": 422,
"status": "error",
"message": "Insufficient balance for transaction",
"errors": [
{
"type": "balance_check",
"message": "Current balance: $50.00, Required: $100.00",
"context": {
"currentBalance": 50.00,
"requiredAmount": 100.00,
"currency": "USD"
}
},
"Please add funds to your account or reduce the transaction amount"
]
}Change Log
v2.0.1
- Update express peer dependency to "express": ">=4.21.2"
v2.0.0
- Add Response Builders: createSuccessResponse, createPaginatedResponse, createErrorResponse
- Remove ApiRequest type, Use Express Request type
v1.0.2
- Update version
v1.0.1
- Remove pagination from Generic ApiResponse
v1.0.0
- Initial release
- Basic request/response types
- Error handling utilities
- Pagination support
- HTTP status management
Made with ❤️ for consistent APIs across Webber Electrocorp microservices
