Error Handling Best Practices
1. Check HTTP Status First
Always check the HTTP status code before parsing the response body:
async function makeRequest(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
throw new APIError(response.status, error);
}
return response.json();
}
class APIError extends Error {
constructor(status, errorBody) {
super(errorBody.error);
this.status = status;
this.code = errorBody.error_code;
this.timestamp = errorBody.timestamp;
}
}
2. Handle Token Expiration Automatically
Implement automatic token refresh on TOKEN_EXPIRED errors:
async function apiRequestWithRefresh(url, options) {
let response = await fetch(url, options);
if (response.status === 401) {
const error = await response.json();
if (error.error_code === 'TOKEN_EXPIRED') {
// Refresh token
const newTokens = await refreshAccessToken();
// Retry original request with new token
options.headers['Authorization'] = `Bearer ${newTokens.access_token}`;
response = await fetch(url, options);
}
}
return response;
}
3. Implement Exponential Backoff for Rate Limits
Use exponential backoff when encountering rate limits:
async function fetchWithBackoff(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
const backoff = Math.min(retryAfter * 1000, 2 ** i * 1000);
console.log(`Rate limited. Retrying after ${backoff}ms...`);
await new Promise(resolve => setTimeout(resolve, backoff));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
4. Handle Network Errors Separately
Distinguish between API errors and network failures:
async function apiRequest(url, options) {
try {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
throw new APIError(response.status, error);
}
return response.json();
} catch (error) {
if (error instanceof APIError) {
// API returned an error response
console.error('API Error:', error.code, error.message);
throw error;
} else {
// Network error (timeout, DNS failure, etc.)
console.error('Network Error:', error.message);
throw new NetworkError(error);
}
}
}
5. Log Errors with Context
Include request context when logging errors:
async function apiRequest(url, options) {
const requestId = generateRequestId();
try {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
console.error('API Request Failed', {
requestId,
url,
method: options.method,
status: response.status,
errorCode: error.error_code,
errorMessage: error.error,
timestamp: error.timestamp
});
throw new APIError(response.status, error);
}
return response.json();
} catch (error) {
if (!(error instanceof APIError)) {
console.error('Request Failed', {
requestId,
url,
method: options.method,
error: error.message
});
}
throw error;
}
}
6. Display User-Friendly Messages
Don’t expose raw error messages to end users:
function getUserFriendlyMessage(error) {
const friendlyMessages = {
'TOKEN_EXPIRED': 'Your session has expired. Please log in again.',
'UNAUTHORIZED': 'Authentication failed. Please log in.',
'FORBIDDEN': 'You don\'t have permission to perform this action.',
'NOT_FOUND': 'The requested resource could not be found.',
'SERVICE_LIMIT_EXCEEDED': 'You\'ve reached your service limit. Please upgrade your plan.',
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again in a moment.',
'INTERNAL_SERVER_ERROR': 'Something went wrong. Please try again later.'
};
return friendlyMessages[error.code] || 'An unexpected error occurred.';
}
7. Validate Before Sending
Validate input client-side before making API requests:
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new ValidationError('Invalid email format');
}
}
async function createUser(email) {
// Validate first to avoid unnecessary API call
validateEmail(email);
return apiRequest('/api/service/users', {
method: 'POST',
headers: {
'X-API-Key': process.env.SSO_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
}
8. Handle Specific Error Codes
Implement specific handlers for different error codes:
async function handleAPIError(error) {
switch (error.code) {
case 'TOKEN_EXPIRED':
await refreshAndRetry();
break;
case 'ORGANIZATION_NOT_ACTIVE':
redirectToSuspendedPage();
break;
case 'SERVICE_LIMIT_EXCEEDED':
showUpgradeModal();
break;
case 'RATE_LIMIT_EXCEEDED':
await retryWithBackoff();
break;
default:
showGenericError(getUserFriendlyMessage(error));
}
}