This document describes the error handling system in the Aqua Stark Backend API.
All errors in the application are handled through a centralized system that ensures consistent error responses across all endpoints. The system uses custom error classes and a global error middleware.
The application defines custom error classes that extend BaseError. Each error class has a specific HTTP status code and error type.
Thrown when input validation fails.
import { ValidationError } from '../core/errors';
if (!address) {
throw new ValidationError('Address is required');
}
if (address.length < 42) {
throw new ValidationError('Invalid address format');
}Thrown when a requested resource is not found.
import { NotFoundError } from '../core/errors';
const player = await getPlayer(address);
if (!player) {
throw new NotFoundError(`Player with address ${address} not found`);
}Thrown when a request conflicts with the current state.
import { ConflictError } from '../core/errors';
const existingPlayer = await findPlayerByAddress(address);
if (existingPlayer) {
throw new ConflictError('Player already exists');
}Thrown when an on-chain operation fails.
import { OnChainError } from '../core/errors';
try {
const txHash = await executeOnChainAction();
} catch (error) {
throw new OnChainError('Failed to execute on-chain action', txHash);
}All errors are automatically transformed to the standard error response format:
{
"success": false,
"error": {
"type": "ValidationError",
"message": "Address is required",
"code": 400
}
}The global error handler (src/core/middleware/error-handler.ts) automatically:
- Catches all errors thrown in controllers or services
- Identifies the error type (custom error, Fastify validation error, etc.)
- Transforms the error to the standard format
- Returns the appropriate HTTP status code
// In your controller or service
try {
const result = await someOperation();
return createSuccessResponse(result);
} catch (error) {
// If it's a custom error, it will be automatically handled
// If it's a generic error, it will be transformed to InternalError
throw error; // Let the middleware handle it
}Always use the most specific error class for the situation:
- Use
ValidationErrorfor invalid inputs - Use
NotFoundErrorfor missing resources - Use
ConflictErrorfor state conflicts - Use
OnChainErrorfor blockchain-related failures
Error messages should be clear and helpful:
// Good
throw new ValidationError('Address must be 42 characters long');
// Bad
throw new ValidationError('Invalid');In most cases, you can let errors bubble up to the middleware:
// Good - Let middleware handle it
async function getPlayer(address: string) {
if (!address) {
throw new ValidationError('Address is required');
}
// ...
}
// Also acceptable - Catch and transform manually
async function getPlayer(address: string) {
try {
// ...
} catch (error) {
return createErrorResponse(error);
}
}Only catch errors if you need to:
- Add additional context
- Transform the error type
- Perform cleanup operations
// Unnecessary catch
try {
const player = await getPlayer(address);
return createSuccessResponse(player);
} catch (error) {
return createErrorResponse(error); // Middleware does this automatically
}
// Better - Let it bubble up
const player = await getPlayer(address);
return createSuccessResponse(player);| Error Class | HTTP Code | Use Case |
|---|---|---|
| ValidationError | 400 | Invalid input, missing fields |
| NotFoundError | 404 | Resource not found |
| ConflictError | 409 | Duplicate resources, state issues |
| OnChainError | 500 | Blockchain operation failures |
If you need a new error type, extend BaseError:
import { BaseError } from '../core/errors/base-error';
export class CustomError extends BaseError {
constructor(message: string) {
super(message, 418, 'CustomError'); // HTTP code, error type
}
}Then add it to src/core/errors/index.ts for easy importing.