Error Handling & Troubleshooting Guide
Table of Contents
- Error Response Format
- HTTP Status Codes
- Common Error Scenarios
- Validation Errors
- Authentication Errors
- Permission Errors
- Database Errors
- File Upload Errors
- Query Errors
- Client-Side Error Handling
- Debugging and Logging
- Recovery Strategies
Error Response Format
All BAASIX API errors follow a consistent JSON format:
Standard Error Response
{
"error": {
"message": "Human-readable error message",
"code": "ERROR_CODE_CONSTANT",
"details": {
"field": "specific_field_name",
"value": "invalid_value",
"expected": "expected_format",
"additional_context": {}
},
"timestamp": "2025-01-15T10:30:00.000Z",
"path": "/items/posts/123",
"method": "POST"
}
}Error Object Properties
| Property | Type | Description |
|---|---|---|
message | string | Human-readable error description |
code | string | Machine-readable error code |
details | object | Additional context and metadata |
timestamp | string | When the error occurred (ISO 8601) |
path | string | API endpoint that caused the error |
method | string | HTTP method used |
HTTP Status Codes
Success Codes (2xx)
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 204 | No Content | Request successful, no content returned |
Client Error Codes (4xx)
| Code | Status | Description | Common Causes |
|---|---|---|---|
| 400 | Bad Request | Invalid request format | Malformed JSON, missing required fields |
| 401 | Unauthorized | Authentication required | Missing/invalid token, expired session |
| 403 | Forbidden | Insufficient permissions | User lacks required role/permission |
| 404 | Not Found | Resource not found | Invalid ID, collection doesn't exist |
| 409 | Conflict | Resource conflict | Unique constraint violation |
| 422 | Unprocessable Entity | Validation failed | Invalid field values, constraint violations |
| 429 | Too Many Requests | Rate limit exceeded | Too many requests in time window |
Server Error Codes (5xx)
| Code | Status | Description | Typical Issues |
|---|---|---|---|
| 500 | Internal Server Error | Server-side error | Database connection, code bugs |
| 502 | Bad Gateway | Gateway error | Proxy/load balancer issues |
| 503 | Service Unavailable | Service temporarily down | Maintenance, overload |
| 504 | Gateway Timeout | Gateway timeout | Slow database queries |
Common Error Scenarios
1. Authentication Errors
Invalid Credentials
{
"error": {
"message": "Invalid email or password",
"code": "INVALID_CREDENTIALS",
"details": {
"field": "password",
"attempts_remaining": 3
}
}
}Expired Token
{
"error": {
"message": "Token has expired",
"code": "TOKEN_EXPIRED",
"details": {
"expired_at": "2025-01-15T10:00:00.000Z",
"current_time": "2025-01-15T11:00:00.000Z"
}
}
}Invalid Token Format
{
"error": {
"message": "Invalid token format",
"code": "INVALID_TOKEN_FORMAT",
"details": {
"expected_format": "Bearer <jwt_token>"
}
}
}2. Validation Errors
Required Field Missing
{
"error": {
"message": "Validation failed",
"code": "VALIDATION_ERROR",
"details": {
"field": "title",
"message": "title is required",
"type": "required"
}
}
}Invalid Field Type
{
"error": {
"message": "Validation failed",
"code": "VALIDATION_ERROR",
"details": {
"field": "age",
"message": "age must be a number",
"value": "not_a_number",
"expected_type": "integer"
}
}
}Multiple Validation Errors
{
"error": {
"message": "Multiple validation errors",
"code": "VALIDATION_ERROR",
"details": {
"errors": [
{
"field": "email",
"message": "email must be a valid email address",
"value": "invalid-email"
},
{
"field": "password",
"message": "password must be at least 8 characters long",
"value": "123"
}
]
}
}
}3. Permission Errors
Insufficient Permissions
{
"error": {
"message": "Insufficient permissions",
"code": "INSUFFICIENT_PERMISSIONS",
"details": {
"required_permission": "posts:create",
"user_permissions": ["posts:read"],
"resource": "posts"
}
}
}Field Access Denied
{
"error": {
"message": "Access denied to field",
"code": "FIELD_ACCESS_DENIED",
"details": {
"field": "salary",
"collection": "employees",
"action": "read",
"reason": "Field not in allowed fields list"
}
}
}4. Resource Errors
Resource Not Found
{
"error": {
"message": "Item not found",
"code": "ITEM_NOT_FOUND",
"details": {
"collection": "posts",
"id": "non-existent-id",
"suggestion": "Check if the ID is correct and the item exists"
}
}
}Collection Not Found
{
"error": {
"message": "Collection not found",
"code": "COLLECTION_NOT_FOUND",
"details": {
"collection": "invalid_collection",
"available_collections": ["posts", "users", "comments"]
}
}
}5. Database Constraint Errors
Unique Constraint Violation
{
"error": {
"message": "Unique constraint violation",
"code": "UNIQUE_CONSTRAINT_VIOLATION",
"details": {
"field": "email",
"value": "duplicate@example.com",
"constraint": "users_email_unique"
}
}
}Foreign Key Constraint
{
"error": {
"message": "Foreign key constraint violation",
"code": "FOREIGN_KEY_CONSTRAINT_VIOLATION",
"details": {
"field": "author_Id",
"value": "non-existent-user-id",
"referenced_table": "baasix_User",
"referenced_field": "id"
}
}
}Client-Side Error Handling
Comprehensive Error Handler
class ErrorHandler {
constructor() {
this.errorCodes = {
// Authentication errors
'INVALID_CREDENTIALS': 'Invalid email or password',
'TOKEN_EXPIRED': 'Your session has expired. Please log in again.',
'INVALID_TOKEN_FORMAT': 'Authentication token is malformed',
// Permission errors
'INSUFFICIENT_PERMISSIONS': 'You don\'t have permission to perform this action',
'FIELD_ACCESS_DENIED': 'Access denied to requested field',
// Validation errors
'VALIDATION_ERROR': 'Please check the form for errors',
'REQUIRED_FIELD_MISSING': 'Please fill in all required fields',
// Resource errors
'ITEM_NOT_FOUND': 'The requested item was not found',
'COLLECTION_NOT_FOUND': 'Invalid data collection',
// Database errors
'UNIQUE_CONSTRAINT_VIOLATION': 'This value already exists',
'FOREIGN_KEY_CONSTRAINT_VIOLATION': 'Referenced item does not exist',
// Rate limiting
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again later.',
// Server errors
'INTERNAL_SERVER_ERROR': 'An unexpected error occurred. Please try again.',
'SERVICE_UNAVAILABLE': 'Service is temporarily unavailable'
};
}
handle(error, context = {}) {
console.error('API Error:', error);
// Extract error information
const errorInfo = this.extractErrorInfo(error);
// Handle specific error types
switch (errorInfo.code) {
case 'TOKEN_EXPIRED':
case 'INVALID_TOKEN_FORMAT':
return this.handleAuthenticationError(errorInfo, context);
case 'INSUFFICIENT_PERMISSIONS':
case 'FIELD_ACCESS_DENIED':
return this.handlePermissionError(errorInfo, context);
case 'VALIDATION_ERROR':
return this.handleValidationError(errorInfo, context);
case 'RATE_LIMIT_EXCEEDED':
return this.handleRateLimitError(errorInfo, context);
default:
return this.handleGenericError(errorInfo, context);
}
}
extractErrorInfo(error) {
// Handle different error formats
if (error.response && error.response.data && error.response.data.error) {
// Axios-style error
return {
...error.response.data.error,
status: error.response.status
};
} else if (error.error) {
// Direct API error response
return error.error;
} else if (error.message) {
// Generic error
return {
message: error.message,
code: 'GENERIC_ERROR'
};
} else {
// Unknown error format
return {
message: 'An unknown error occurred',
code: 'UNKNOWN_ERROR'
};
}
}
handleAuthenticationError(errorInfo, context) {
// Clear stored authentication
localStorage.removeItem('baasix_token');
localStorage.removeItem('baasix_user');
// Redirect to login if not already there
if (window.location.pathname !== '/login') {
window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname);
}
return {
type: 'authentication',
message: this.getErrorMessage(errorInfo),
action: 'redirect_login'
};
}
handlePermissionError(errorInfo, context) {
const message = this.getErrorMessage(errorInfo);
// Show permission denied message
this.showNotification(message, 'error');
// Optional: redirect to appropriate page
if (context.redirectOnPermissionDenied) {
setTimeout(() => {
window.location.href = context.redirectOnPermissionDenied;
}, 2000);
}
return {
type: 'permission',
message,
action: 'show_error'
};
}
handleValidationError(errorInfo, context) {
const errors = this.extractValidationErrors(errorInfo);
// Display field-specific errors
if (context.form) {
this.displayFieldErrors(context.form, errors);
} else {
this.showNotification('Please check the form for errors', 'warning');
}
return {
type: 'validation',
message: 'Validation failed',
errors,
action: 'show_field_errors'
};
}
handleRateLimitError(errorInfo, context) {
const retryAfter = errorInfo.details?.retry_after || 60;
const message = `Too many requests. Please try again in ${retryAfter} seconds.`;
this.showNotification(message, 'warning');
// Optional: implement automatic retry
if (context.autoRetry && context.retryFunction) {
setTimeout(() => {
context.retryFunction();
}, retryAfter * 1000);
}
return {
type: 'rate_limit',
message,
retryAfter,
action: 'show_warning'
};
}
handleGenericError(errorInfo, context) {
const message = this.getErrorMessage(errorInfo);
this.showNotification(message, 'error');
return {
type: 'generic',
message,
action: 'show_error'
};
}
extractValidationErrors(errorInfo) {
const errors = {};
if (errorInfo.details && errorInfo.details.errors) {
// Multiple validation errors
errorInfo.details.errors.forEach(error => {
errors[error.field] = error.message;
});
} else if (errorInfo.details && errorInfo.details.field) {
// Single validation error
errors[errorInfo.details.field] = errorInfo.details.message || errorInfo.message;
}
return errors;
}
displayFieldErrors(form, errors) {
// Clear previous errors
form.querySelectorAll('.field-error').forEach(el => el.remove());
// Display new errors
Object.entries(errors).forEach(([field, message]) => {
const fieldElement = form.querySelector(`[name="${field}"]`);
if (fieldElement) {
const errorElement = document.createElement('div');
errorElement.className = 'field-error';
errorElement.textContent = message;
fieldElement.parentNode.appendChild(errorElement);
}
});
}
getErrorMessage(errorInfo) {
return this.errorCodes[errorInfo.code] || errorInfo.message || 'An error occurred';
}
showNotification(message, type = 'info') {
// Implement your notification system here
console.log(`[${type.toUpperCase()}] ${message}`);
// Example notification implementation
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 5000);
}
}
// Usage in API client
class BaaSixClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.errorHandler = new ErrorHandler();
}
async request(endpoint, options = {}) {
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(JSON.stringify({
error: errorData.error || {
message: response.statusText,
code: `HTTP_${response.status}`
},
status: response.status
}));
}
return await response.json();
} catch (error) {
// Handle error with context
const context = {
endpoint,
options,
form: options.formElement,
autoRetry: options.autoRetry,
retryFunction: options.retryFunction
};
const handledError = this.errorHandler.handle(error, context);
// Re-throw for calling code to handle if needed
throw new BaaSixError(handledError.message, handledError.type, handledError);
}
}
}
class BaaSixError extends Error {
constructor(message, type, details) {
super(message);
this.name = 'BaaSixError';
this.type = type;
this.details = details;
}
}Retry Logic with Exponential Backoff
class RetryManager {
constructor(maxRetries = 3, baseDelay = 1000) {
this.maxRetries = maxRetries;
this.baseDelay = baseDelay;
}
async executeWithRetry(operation, context = {}) {
let lastError;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
// Don't retry client errors (4xx) except rate limiting
if (this.shouldNotRetry(error)) {
throw error;
}
// Don't retry on last attempt
if (attempt === this.maxRetries) {
throw error;
}
// Calculate delay with exponential backoff
const delay = this.baseDelay * Math.pow(2, attempt);
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
await this.delay(delay);
}
}
throw lastError;
}
shouldNotRetry(error) {
const errorInfo = this.extractErrorInfo(error);
// Don't retry authentication errors
if (['TOKEN_EXPIRED', 'INVALID_CREDENTIALS', 'INSUFFICIENT_PERMISSIONS'].includes(errorInfo.code)) {
return true;
}
// Don't retry validation errors
if (errorInfo.code === 'VALIDATION_ERROR') {
return true;
}
// Don't retry 4xx errors except rate limiting
if (errorInfo.status >= 400 && errorInfo.status < 500 && errorInfo.status !== 429) {
return true;
}
return false;
}
extractErrorInfo(error) {
if (error.details && error.details.status) {
return error.details;
}
return { status: 0, code: 'UNKNOWN_ERROR' };
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage with retry logic
const retryManager = new RetryManager(3, 1000);
async function createPostWithRetry(postData) {
return retryManager.executeWithRetry(async () => {
return await baasix.post('/items/posts', postData);
});
}Debugging and Logging
Comprehensive Logging System
class APILogger {
constructor(level = 'info') {
this.level = level;
this.levels = {
error: 0,
warn: 1,
info: 2,
debug: 3
};
}
log(level, message, data = {}) {
if (this.levels[level] <= this.levels[this.level]) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
data,
url: window.location.href,
userAgent: navigator.userAgent
};
console[level](message, logEntry);
// Send to monitoring service (optional)
this.sendToMonitoring(logEntry);
}
}
error(message, data) { this.log('error', message, data); }
warn(message, data) { this.log('warn', message, data); }
info(message, data) { this.log('info', message, data); }
debug(message, data) { this.log('debug', message, data); }
logAPICall(method, url, requestData, responseData, duration, error = null) {
const logData = {
method,
url,
requestData,
responseData,
duration,
error
};
if (error) {
this.error(`API call failed: ${method} ${url}`, logData);
} else {
this.info(`API call successful: ${method} ${url}`, logData);
}
}
sendToMonitoring(logEntry) {
// Implement your monitoring service integration here
// Example: send to external logging service
if (logEntry.level === 'error' && window.MonitoringService) {
window.MonitoringService.captureException(logEntry);
}
}
}
// Enhanced API client with logging
class LoggingBaaSixClient extends BaaSixClient {
constructor(baseUrl) {
super(baseUrl);
this.logger = new APILogger('debug');
}
async request(endpoint, options = {}) {
const startTime = Date.now();
const method = options.method || 'GET';
this.logger.debug(`Making API call: ${method} ${endpoint}`, {
options: { ...options, headers: { ...options.headers } }
});
try {
const response = await super.request(endpoint, options);
const duration = Date.now() - startTime;
this.logger.logAPICall(method, endpoint, options.body, response, duration);
return response;
} catch (error) {
const duration = Date.now() - startTime;
this.logger.logAPICall(method, endpoint, options.body, null, duration, error);
throw error;
}
}
}Error Monitoring Integration
// Integration with error monitoring services
class ErrorMonitor {
constructor(config = {}) {
this.config = {
enableConsoleLogging: true,
enableRemoteLogging: false,
remoteEndpoint: '/api/errors',
maxErrorsPerMinute: 10,
...config
};
this.errorQueue = [];
this.errorCounts = new Map();
this.setupGlobalErrorHandler();
}
setupGlobalErrorHandler() {
// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
this.captureError('Unhandled Promise Rejection', {
reason: event.reason,
promise: event.promise
});
});
// Catch JavaScript errors
window.addEventListener('error', (event) => {
this.captureError('JavaScript Error', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack
});
});
}
captureError(type, error, context = {}) {
const errorEntry = {
id: this.generateErrorId(),
type,
message: error.message || String(error),
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent,
context,
stack: error.stack || new Error().stack
};
if (this.shouldCaptureError(errorEntry)) {
this.processError(errorEntry);
}
}
shouldCaptureError(errorEntry) {
const minute = Math.floor(Date.now() / 60000);
const key = `${errorEntry.type}-${minute}`;
const count = this.errorCounts.get(key) || 0;
if (count >= this.config.maxErrorsPerMinute) {
return false; // Rate limiting
}
this.errorCounts.set(key, count + 1);
return true;
}
processError(errorEntry) {
if (this.config.enableConsoleLogging) {
console.error('Captured error:', errorEntry);
}
this.errorQueue.push(errorEntry);
if (this.config.enableRemoteLogging) {
this.sendErrorToRemote(errorEntry);
}
// Clean up old error counts
this.cleanupErrorCounts();
}
async sendErrorToRemote(errorEntry) {
try {
await fetch(this.config.remoteEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(errorEntry)
});
} catch (error) {
console.warn('Failed to send error to remote endpoint:', error);
}
}
generateErrorId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
cleanupErrorCounts() {
const currentMinute = Math.floor(Date.now() / 60000);
for (const [key] of this.errorCounts) {
const keyMinute = parseInt(key.split('-').pop());
if (currentMinute - keyMinute > 5) { // Keep 5 minutes of history
this.errorCounts.delete(key);
}
}
}
getErrorReport() {
return {
totalErrors: this.errorQueue.length,
recentErrors: this.errorQueue.slice(-10),
errorCountsByType: this.groupErrorsByType(),
errorCountsByMinute: Array.from(this.errorCounts.entries())
};
}
groupErrorsByType() {
const groups = {};
this.errorQueue.forEach(error => {
groups[error.type] = (groups[error.type] || 0) + 1;
});
return groups;
}
}
// Initialize error monitoring
const errorMonitor = new ErrorMonitor({
enableRemoteLogging: true,
remoteEndpoint: `${BAASIX_BASE_URL}/api/client-errors`
});
// Enhanced error handler with monitoring
class MonitoredErrorHandler extends ErrorHandler {
constructor() {
super();
this.monitor = errorMonitor;
}
handle(error, context = {}) {
// Capture error for monitoring
this.monitor.captureError('API Error', error, context);
// Continue with normal error handling
return super.handle(error, context);
}
}Recovery Strategies
Data Recovery Patterns
class DataRecoveryManager {
constructor(client) {
this.client = client;
this.pendingOperations = new Map();
}
async executeWithRecovery(operation, recoveryOptions = {}) {
const operationId = this.generateOperationId();
try {
// Store operation details for potential recovery
this.pendingOperations.set(operationId, {
operation,
options: recoveryOptions,
timestamp: Date.now(),
attempts: 0
});
const result = await operation();
// Remove successful operation
this.pendingOperations.delete(operationId);
return result;
} catch (error) {
return this.handleOperationFailure(operationId, error);
}
}
async handleOperationFailure(operationId, error) {
const operationDetails = this.pendingOperations.get(operationId);
if (!operationDetails) {
throw error;
}
const { operation, options, attempts } = operationDetails;
// Update attempt count
operationDetails.attempts = attempts + 1;
// Check if we should retry
if (this.shouldRetry(error, operationDetails)) {
console.log(`Retrying operation ${operationId}, attempt ${operationDetails.attempts}`);
// Wait before retrying
await this.delay(this.calculateRetryDelay(operationDetails.attempts));
// Retry the operation
return this.executeWithRecovery(operation, options);
}
// Check if we can recover data
if (options.enableDataRecovery && this.canRecoverData(error)) {
try {
const recoveredData = await this.recoverData(options);
this.pendingOperations.delete(operationId);
return recoveredData;
} catch (recoveryError) {
console.warn('Data recovery failed:', recoveryError);
}
}
// Remove failed operation
this.pendingOperations.delete(operationId);
throw error;
}
shouldRetry(error, operationDetails) {
// Don't retry if max attempts reached
if (operationDetails.attempts >= (operationDetails.options.maxRetries || 3)) {
return false;
}
// Don't retry client errors (except rate limiting)
if (error.details && error.details.status >= 400 && error.details.status < 500 && error.details.status !== 429) {
return false;
}
return true;
}
canRecoverData(error) {
// Implement logic to determine if data can be recovered
return error.details && (
error.details.status === 503 || // Service unavailable
error.details.status === 502 || // Bad gateway
error.details.status === 504 // Gateway timeout
);
}
async recoverData(options) {
if (options.backupSource) {
// Try to recover from backup source
return this.recoverFromBackup(options.backupSource);
}
if (options.cacheSource) {
// Try to recover from cache
return this.recoverFromCache(options.cacheSource);
}
throw new Error('No recovery source available');
}
async recoverFromBackup(backupSource) {
// Implement backup recovery logic
console.log('Recovering data from backup source...');
// This would implement your specific backup recovery logic
throw new Error('Backup recovery not implemented');
}
async recoverFromCache(cacheKey) {
try {
const cached = localStorage.getItem(cacheKey);
if (cached) {
return JSON.parse(cached);
}
} catch (error) {
console.warn('Cache recovery failed:', error);
}
throw new Error('No cached data available');
}
generateOperationId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
calculateRetryDelay(attempt) {
return Math.min(1000 * Math.pow(2, attempt - 1), 10000); // Cap at 10 seconds
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Cleanup old pending operations
cleanup() {
const now = Date.now();
const maxAge = 5 * 60 * 1000; // 5 minutes
for (const [id, operation] of this.pendingOperations) {
if (now - operation.timestamp > maxAge) {
this.pendingOperations.delete(id);
}
}
}
}
// Usage example
const recoveryManager = new DataRecoveryManager(baasix);
async function savePostWithRecovery(postData) {
return recoveryManager.executeWithRecovery(
() => baasix.post('/items/posts', postData),
{
maxRetries: 3,
enableDataRecovery: true,
cacheSource: `post_draft_${postData.id}`,
backupSource: 'local_posts_backup'
}
);
}This comprehensive error handling guide provides AI developers with everything they need to properly handle errors, implement robust retry logic, monitor issues, and recover from failures when integrating with the BAASIX API.
Related Documentation
- Integration Guide - Complete integration examples
- API Routes Reference - All available endpoints
- Authentication Routes - Auth error specifics