Loading...
Loading...
Comprehensive, standardized error response system for PHP REST APIs with SweetAlert2 integration. Use when building REST APIs that need consistent error formatting, specific error message extraction from database exceptions, validation error handling, and seamless frontend integration. Includes PDOException parsing, business rule extraction, and complete SweetAlert2 error display patterns.
npx skill4agent add peterbamuhigire/skills-web-dev api-error-handlingreferences/examples/{
"success": true,
"data": {
/* payload */
},
"message": "Optional success message",
"meta": {
"timestamp": "2026-01-24T10:30:00Z",
"request_id": "req_abc123"
}
}{
"success": false,
"message": "Human-readable error for SweetAlert2",
"error": {
"code": "ERROR_CODE",
"type": "validation_error",
"details": {
/* field-specific errors */
}
},
"meta": {
"timestamp": "2026-01-24T10:30:00Z",
"request_id": "req_abc123"
}
}| Status | Error Type | Use Case | Error Code Examples |
|---|---|---|---|
| 400 | Bad Request | Malformed JSON, missing params | INVALID_JSON, MISSING_PARAMETER |
| 401 | Unauthorized | Missing/invalid auth token | TOKEN_MISSING, TOKEN_EXPIRED |
| 403 | Forbidden | Valid auth but no permission | PERMISSION_DENIED, ACCESS_FORBIDDEN |
| 404 | Not Found | Resource doesn't exist | RESOURCE_NOT_FOUND, INVOICE_NOT_FOUND |
| 405 | Method Not Allowed | Wrong HTTP method | METHOD_NOT_ALLOWED |
| 409 | Conflict | Business rule violation | ALREADY_EXISTS, OVERPAYMENT |
| 422 | Unprocessable Entity | Validation errors | VALIDATION_FAILED, INVALID_EMAIL |
| 429 | Too Many Requests | Rate limiting | RATE_LIMIT_EXCEEDED |
| 500 | Internal Server Error | Unexpected errors | INTERNAL_ERROR, DATABASE_ERROR |
| 503 | Service Unavailable | Maintenance/overload | SERVICE_UNAVAILABLE, DEADLOCK |
references/ApiResponse.php// Success responses
ApiResponse::success($data, $message, $status);
ApiResponse::created($data, $message);
// Error responses
ApiResponse::error($message, $code, $status, $type, $details);
ApiResponse::validationError($errors, $message);
ApiResponse::notFound($resource, $identifier);
ApiResponse::unauthorized($message);
ApiResponse::forbidden($permission);
ApiResponse::conflict($message, $code);
ApiResponse::methodNotAllowed($allowedMethods);
ApiResponse::rateLimited($retryAfter);
ApiResponse::serverError($message);
ApiResponse::serviceUnavailable($message);references/ExceptionHandler.php// SQLSTATE 45000: Business rule from trigger
// "SQLSTATE[45000]: <<1>>: 1644 Overpayment not allowed"
// Extracted: "Overpayment not allowed"
// SQLSTATE 23000: Duplicate entry
// "Duplicate entry 'john@example.com' for key 'uk_email'"
// Extracted: "A record with this Email already exists: 'john@example.com'"
// SQLSTATE 23000: Foreign key violation
// "Cannot delete or update a parent row..."
// Extracted: "Referenced Customer does not exist or cannot be deleted"references/CustomExceptions.php// Validation (422)
throw new ValidationException([
'email' => 'Invalid email format',
'phone' => 'Phone number required'
], 'Validation failed');
// Authentication (401)
throw new AuthenticationException('Token expired', 'expired');
// Authorization (403)
throw new AuthorizationException('MANAGE_USERS');
// Not Found (404)
throw new NotFoundException('Invoice', 'INV-123');
// Conflict (409)
throw new ConflictException('Already voided', 'ALREADY_VOIDED');
// Rate Limit (429)
throw new RateLimitException(60);references/bootstrap.phprequire_once __DIR__ . '/bootstrap.php';
// Helper functions available:
require_method(['POST', 'PUT']);
$data = read_json_body();
validate_required($data, ['customer_id', 'amount']);
$db = get_db();
$token = bearer_token();
require_auth();
require_permission('MANAGE_INVOICES');
handle_request(function() { /* endpoint logic */ });examples/InvoicesEndpoint.phprequire_once __DIR__ . '/../bootstrap.php';
use App\Http\ApiResponse;
use App\Http\Exceptions\{NotFoundException, ValidationException};
require_auth();
handle_request(function() {
$method = $_SERVER['REQUEST_METHOD'];
match ($method) {
'POST' => handlePost(),
default => ApiResponse::methodNotAllowed('POST')
};
});
function handlePost(): void {
$data = read_json_body();
validate_required($data, ['customer_id', 'items']);
if (empty($data['items'])) {
throw new ValidationException(['items' => 'Required']);
}
// Business logic...
ApiResponse::created(['id' => $id], 'Created successfully');
}examples/ApiClient.jsconst api = new ApiClient("./api");
// GET - Errors automatically shown via SweetAlert2
const response = await api.get("invoices.php", { status: "pending" });
if (response) renderInvoices(response.data);
// POST - Validation errors highlight form fields
showLoading("Creating...");
const result = await api.post("invoices.php", formData);
hideLoading();
if (result) showSuccess("Created successfully");
// DELETE - With confirmation
const { value: reason } = await Swal.fire({
title: "Void?",
input: "textarea",
showCancelButton: true,
});
if (reason) {
const res = await api.delete(`invoices.php?id=${id}`, { reason });
if (res) showSuccess("Voided");
}
// Helpers: showSuccess/Error/Warning/Info, showConfirm, showLoading, hideLoadingtry {
const response = await $.ajax({
url: "./api/endpoint.php?action=verify",
method: "POST",
contentType: "application/json",
data: JSON.stringify({ id: 123 }),
});
// Check response.success BEFORE using data
if (!response.success) {
await Swal.fire({
icon: "error",
title: "Operation Failed",
text: response.message || "An error occurred",
confirmButtonText: "OK",
});
return; // Stop execution
}
// Success path
await Swal.fire({
icon: "success",
title: "Success!",
text: response.message || "Operation completed",
});
} catch (error) {
// Extract error message from different formats
let errorMessage = "An unexpected error occurred";
if (error.responseJSON && error.responseJSON.message) {
errorMessage = error.responseJSON.message; // API error message
} else if (error.message) {
errorMessage = error.message; // JavaScript error
}
await Swal.fire({
icon: "error",
title: "Error",
text: errorMessage,
confirmButtonText: "OK",
});
}response.successresponse.messageerror.responseJSON.messageawaitnot a function{ sales: [...] }const rows = Array.isArray(data)
? data
: Array.isArray(data?.sales)
? data.sales
: [];references/contract-validation.md// Frontend sends incomplete data
{ agent_id: 2, payment_method: "Cash", amount: 10000 }
// API expects (but doesn't communicate):
{ agent_id: 2, sales_point_id: 5, remittance_date: "2026-02-10", ... }
// Result: Generic 400 Bad Request ❌// Backend: Return specific errors (HTTP 422)
$errors = [];
if (empty($input['sales_point_id'])) {
$errors['sales_point_id'] = 'Sales point ID is required';
}
if (empty($input['remittance_date'])) {
$errors['remittance_date'] = 'Remittance date is required';
}
if (!empty($errors)) {
ResponseHandler::validationError($errors);
}// ✅ CORRECT: Action in URL
url: "./api/endpoint.php?action=create",
data: JSON.stringify({ agent_id: 2, amount: 10000 })
// ❌ WRONG: Action in body
url: "./api/endpoint.php",
data: JSON.stringify({ action: "create", agent_id: 2 })$_GET$inputSIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Overpayment not allowed';
-- Extracted: "Overpayment not allowed" → Code: OVERPAYMENT_NOT_ALLOWED"Duplicate entry 'john@example.com' for key 'uk_email'"
-- Extracted: "A record with this Email already exists: 'john@example.com'""Cannot delete or update a parent row..."
-- Extracted: "Referenced Customer does not exist or cannot be deleted""Deadlock found when trying to get lock..."
-- Extracted: "Database conflict. Please try again." → HTTP 503$errors = [];
if (empty($data['email'])) $errors['email'] = 'Email required';
elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL))
$errors['email'] = 'Invalid email';
if ($errors) ApiResponse::validationError($errors);.is-invalid.invalid-feedback$stmt = $db->prepare("SELECT status FROM invoices WHERE id = ? FOR UPDATE");
$stmt->execute([$id]);
$invoice = $stmt->fetch();
if (!$invoice) throw new NotFoundException('Invoice', $id);
if ($invoice['status'] === 'voided')
throw new ConflictException('Already voided', 'ALREADY_VOIDED');
if ($invoice['status'] === 'paid')
throw new ConflictException('Cannot void paid invoice', 'INVOICE_PAID');[req_abc123] PDOException: SQLSTATE[45000]: Overpayment not allowed in /api/payments.php:45
Context: {"user_id":123,"franchise_id":5,"url":"/api/payments.php","method":"POST"}ApiResponseExceptionHandlerApiClient// 400 - Bad Request
ApiResponse::error('Invalid request', 'BAD_REQUEST', 400);
// 401 - Unauthorized
ApiResponse::unauthorized('Session expired');
// 403 - Forbidden
ApiResponse::forbidden('MANAGE_USERS');
// 404 - Not Found
ApiResponse::notFound('Invoice', 'INV-123');
// 409 - Conflict
ApiResponse::conflict('Already voided', 'ALREADY_VOIDED');
// 422 - Validation
ApiResponse::validationError(['email' => 'Invalid format']);
// 500 - Server Error
ApiResponse::serverError('Unexpected error occurred');APP_DEBUG=truebootstrap.phpApiResponseExceptionHandlerApiClientreferences/ApiResponse.phpreferences/ExceptionHandler.phpreferences/CustomExceptions.phpreferences/bootstrap.phpexamples/InvoicesEndpoint.phpexamples/ApiClient.js