Error Handling
Cloudillo uses structured error codes and standardized error responses for consistent error handling across the platform.
All API errors return this structure:
{
"error" : {
"code" : "E-AUTH-UNAUTH" ,
"message" : "Unauthorized access" ,
"details" : {
"reason" : "Token expired" ,
"expiredAt" : 1735000000
}
},
"time" : 1735000000 ,
"reqId" : "req_abc123"
}
Fields:
error.code - Structured error code (see below)
error.message - Human-readable error message
error.details - Optional additional context
time - Unix timestamp (seconds)
reqId - Request ID for tracing
Error codes follow the pattern: E-MODULE-ERRTYPE
E- - Prefix (all error codes start with this)
MODULE - Module identifier (AUTH, CORE, SYS, etc.)
ERRTYPE - Error type (UNAUTH, NOTFOUND, etc.)
Error Codes
Authentication Errors (AUTH)
Code
HTTP Status
Description
E-AUTH-UNAUTH
401
Unauthorized - Invalid or missing token
E-AUTH-FORBID
403
Forbidden - Insufficient permissions
E-AUTH-EXPIRED
401
Token expired
E-AUTH-INVALID
400
Invalid credentials
E-AUTH-MISMATCH
400
Password mismatch
E-AUTH-EXISTS
409
User already exists
Core Errors (CORE)
Code
HTTP Status
Description
E-CORE-NOTFOUND
404
Resource not found
E-CORE-CONFLICT
409
Resource conflict (duplicate)
E-CORE-INVALID
400
Invalid request data
E-CORE-BADREQ
400
Malformed request
E-CORE-LIMIT
429
Rate limit exceeded
System Errors (SYS)
Code
HTTP Status
Description
E-SYS-UNAVAIL
503
Service unavailable
E-SYS-INTERNAL
500
Internal server error
E-SYS-TIMEOUT
504
Request timeout
E-SYS-STORAGE
507
Storage full
File Errors (FILE)
Code
HTTP Status
Description
E-FILE-NOTFOUND
404
File not found
E-FILE-TOOLARGE
413
File too large
E-FILE-BADTYPE
415
Unsupported file type
E-FILE-CORRUPT
422
Corrupted file
Action Errors (ACTION)
Code
HTTP Status
Description
E-ACTION-NOTFOUND
404
Action not found
E-ACTION-INVALID
400
Invalid action data
E-ACTION-DENIED
403
Action not allowed
E-ACTION-EXPIRED
410
Action expired
Federation Errors (FED)
Code
HTTP Status
Description
E-FED-SIGFAIL
400
Action signature verification failed
E-FED-KEYNOTFOUND
400
Issuer public key not found
E-FED-EXPIRED
400
Action token expired
E-FED-NOTRUST
403
No trust relationship with issuer
E-POW-REQUIRED
428
Proof-of-work required (CONN actions)
Validation Errors (VAL)
Code
HTTP Status
Description
E-VAL-INVALID
400
Validation error
E-AUTH-NOPERM
403
Permission denied
Network Errors (NET)
Code
HTTP Status
Description
E-NET-TIMEOUT
408
Network timeout
Database Errors (CORE)
Code
HTTP Status
Description
E-CORE-DBERR
500
Database error
E-CORE-PARSE
500
Parse error
Identity Provider Errors (IDP)
Code
HTTP Status
Description
E-IDP-NOTFOUND
404
Identity not found
E-IDP-EXISTS
409
Identity already exists
E-IDP-INVALID
400
Invalid identity format
RTDB/CRDT Errors
Code
HTTP Status
Description
E-RTDB-NOTFOUND
404
RTDB document not found
E-CRDT-NOTFOUND
404
CRDT document not found
E-CRDT-CONFLICT
409
CRDT merge conflict
Handling Errors with FetchError
The @cloudillo/core library provides FetchError for consistent error handling:
import { FetchError } from '@cloudillo/core'
try {
const api = cloudillo .createApiClient ()
const profile = await api .profiles .getOwn ()
} catch (error ) {
if (error instanceof FetchError ) {
console .error ('Error code:' , error .code )
console .error ('Message:' , error .message )
console .error ('HTTP status:' , error .status )
console .error ('Details:' , error .details )
// Handle specific errors
switch (error .code ) {
case 'E-AUTH-UNAUTH' :
// Redirect to login
window.location .href = '/login'
break
case 'E-AUTH-FORBID' :
// Show permission error
alert ('You do not have permission to access this resource' )
break
case 'E-CORE-NOTFOUND' :
// Show not found message
console .log ('Resource not found' )
break
case 'E-CORE-LIMIT' :
// Rate limited
console .log ('Too many requests, please slow down' )
break
default :
// Generic error
console .error ('An error occurred:' , error .message )
}
} else {
// Non-API error (network, parsing, etc.)
console .error ('Unexpected error:' , error )
}
}
Error Handling Patterns
Pattern 1: Global Error Handler
function createApiWithErrorHandling() {
const api = cloudillo .createApiClient ()
// Wrap API methods with error handling
return new Proxy (api , {
get (target , prop ) {
const original = target [prop ]
if (typeof original === 'function' ) {
return async (...args ) => {
try {
return await original .apply (target , args )
} catch (error ) {
handleApiError (error )
throw error
}
}
}
return original
}
})
}
function handleApiError (error ) {
if (error instanceof FetchError ) {
switch (error .code ) {
case 'E-AUTH-UNAUTH' :
case 'E-AUTH-EXPIRED' :
// Redirect to login
window.location .href = '/login'
break
case 'E-CORE-LIMIT' :
// Show rate limit toast
showToast ('Too many requests. Please slow down.' )
break
default :
// Log to error tracking service
logErrorToSentry (error )
}
}
}
Pattern 2: React Error Boundary
import { Component } from 'react'
import { FetchError } from '@cloudillo/core'
class ApiErrorBoundary extends Component {
state = { error : null }
static getDerivedStateFromError (error ) {
return { error }
}
componentDidCatch (error , errorInfo ) {
if (error instanceof FetchError ) {
console .error ('API Error:' , error .code , error .message )
// Handle specific errors
if (error .code === 'E-AUTH-UNAUTH' ) {
window.location .href = '/login'
}
}
}
render() {
if (this .state .error ) {
return (
<div className = "error" >
<h1 >Something went wrong </h1 >
<p >{this .state .error .message }</p >
<button onClick = {() => this .setState ({ error : null })}>
Try again
</button >
</div >
)
}
return this .props .children
}
}
Pattern 3: Retry Logic
async function fetchWithRetry (fn , maxRetries = 3 ) {
for (let i = 0 ; i < maxRetries ; i ++ ) {
try {
return await fn ()
} catch (error ) {
if (error instanceof FetchError ) {
// Retry on transient errors
if (error .code === 'E-SYS-UNAVAIL' ||
error .code === 'E-SYS-TIMEOUT' ) {
if (i < maxRetries - 1 ) {
// Exponential backoff
await new Promise (r => setTimeout (r , 1000 * Math.pow (2 , i )))
continue
}
}
// Don't retry on auth or client errors
if (error .code .startsWith ('E-AUTH-' ) ||
error .code .startsWith ('E-CORE-' )) {
throw error
}
}
throw error
}
}
}
// Usage
const data = await fetchWithRetry (() => api .profiles .getOwn ())
Pattern 4: User-Friendly Messages
function getUserFriendlyMessage (error : FetchError ): string {
const messages : Record <string , string > = {
'E-AUTH-UNAUTH' : 'Please log in to continue' ,
'E-AUTH-FORBID' : 'You don\'t have permission to do that' ,
'E-AUTH-EXPIRED' : 'Your session has expired. Please log in again' ,
'E-CORE-NOTFOUND' : 'The item you\'re looking for doesn\'t exist' ,
'E-CORE-LIMIT' : 'You\'re making too many requests. Please slow down' ,
'E-FILE-TOOLARGE' : 'This file is too large. Maximum size is 100MB' ,
'E-FILE-BADTYPE' : 'This file type is not supported' ,
'E-SYS-UNAVAIL' : 'The service is temporarily unavailable. Please try again later' ,
}
return messages [error .code ] || 'An unexpected error occurred. Please try again'
}
// Usage
try {
await api .files .uploadBlob ('default' , file .name , file )
} catch (error ) {
if (error instanceof FetchError ) {
alert (getUserFriendlyMessage (error ))
}
}
Pattern 5: Typed Error Handling
type ApiError = {
code : string
message : string
status : number
}
function isAuthError (error : ApiError ): boolean {
return error .code .startsWith ('E-AUTH-' )
}
function isClientError (error : ApiError ): boolean {
return error .status >= 400 && error .status < 500
}
function isServerError (error : ApiError ): boolean {
return error .status >= 500
}
// Usage
try {
const data = await api .actions .create (newAction )
} catch (error ) {
if (error instanceof FetchError ) {
if (isAuthError (error )) {
handleAuthError (error )
} else if (isServerError (error )) {
showRetryPrompt ()
}
}
}
Validation Errors
Client-side validation can prevent many errors:
import type { NewAction } from '@cloudillo/types'
function validateAction (action : NewAction ): string [] {
const errors : string [] = []
if (! action .type ) {
errors .push ('Action type is required' )
}
if (action .type === 'POST' && ! action .content ) {
errors .push ('Post content is required' )
}
if (action .attachments && action .attachments .length > 10 ) {
errors .push ('Maximum 10 attachments allowed' )
}
return errors
}
// Usage
const newAction = {
type : 'POST' ,
content : { text : 'Hello!' }
}
const validationErrors = validateAction (newAction )
if (validationErrors .length > 0 ) {
alert ('Validation errors:\n' + validationErrors .join ('\n' ))
return
}
// Proceed with API call
await api .actions .create (newAction )
Logging and Monitoring
function logApiError (error : FetchError , context? : any ) {
const logData = {
code : error.code ,
message : error.message ,
status : error.status ,
url : error.response?.url ,
context ,
timestamp : new Date().toISOString (),
userAgent : navigator.userAgent ,
user : cloudillo.idTag
}
// Send to logging service
fetch ('/api/logs/error' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON.stringify (logData )
})
// Also log to console in development
if (process .env .NODE_ENV === 'development' ) {
console .error ('API Error:' , logData )
}
}
Best Practices
1. Always Handle Errors
// ✅ Good - handle errors
try {
await api .actions .create (newAction )
} catch (error ) {
handleError (error )
}
// ❌ Bad - unhandled errors crash the app
await api .actions .create (newAction )
2. Provide User Feedback
// ✅ Good - user knows what happened
try {
await api .files .uploadBlob ('default' , file .name , file )
showToast ('File uploaded successfully!' )
} catch (error ) {
showToast ('Upload failed. Please try again.' )
}
// ❌ Bad - silent failure
try {
await api .files .uploadBlob ('default' , file .name , file )
} catch (error ) {
console .error (error )
}
3. Differentiate Error Types
// ✅ Good - handle different errors appropriately
if (error .code === 'E-AUTH-UNAUTH' ) {
redirectToLogin ()
} else if (error .code === 'E-CORE-NOTFOUND' ) {
show404Page ()
} else {
showGenericError ()
}
// ❌ Bad - same handling for all errors
alert ('Error!' )
4. Log Errors for Debugging
// ✅ Good - logs help debug issues
catch (error ) {
console .error ('Failed to create post:' , {
error ,
action : newAction ,
user : cloudillo.idTag
})
showError ('Failed to create post' )
}
// ❌ Bad - no debugging info
catch (error ) {
showError ('Failed' )
}
See Also