Error Handling
Overview
Use domain errors from @sws/common/domain instead of plain new Error() for domain-level failures. Each error carries an HTTP status code as a static property, which infrastructure layers can use when mapping to HTTP responses.
Base Error Types
From @sws/common/domain:
| Error | Status | When to Use |
|---|---|---|
BadRequestError | 400 | Invalid input, malformed request |
UnauthorizedError | 401 | Missing or invalid authentication |
ForbiddenError | 403 | Insufficient permissions |
NotFoundError | 404 | Resource not found |
RateLimitError | 429 | Too many requests |
Usage Examples
typescript
import { NotFoundError, BadRequestError, ForbiddenError } from '@sws/common/domain'
// Resource not found
throw new NotFoundError(`CexAccount with id '${id}' not found`)
// Validation error
throw new BadRequestError(`Invalid CEX name: '${cex}'. Must be one of: binance, kucoin, htx`)
// Authorization
throw new UnauthorizedError('Invalid API credentials')
// Permissions
throw new ForbiddenError('Partner does not have access to this resource')Custom Domain Errors
Bounded contexts can define their own errors by extending base errors:
typescript
// packages/cex-wallets/domain/errors/CexAccountErrors.ts
import { NotFoundError, BadRequestError } from '@sws/common/domain'
export class CexAccountNotFoundError extends NotFoundError {
constructor(id: string) {
super(`CexAccount with id '${id}' not found`)
this.name = 'CexAccountNotFoundError'
}
}
export class InvalidCexError extends BadRequestError {
constructor(cex: string) {
super(`Invalid CEX: '${cex}'. Must be one of: binance, kucoin, htx`)
this.name = 'InvalidCexError'
}
}Error Handling in Services
Catch errors at the application boundary (controllers/event subscribers), not inside domain/application services:
typescript
// In event subscriber — log error, don't let it crash the subscriber
async on(event: SomeDomainEvent): Promise<void> {
try {
await this.service.run({ ... })
} catch (error) {
this.logger.error(`Error in DoSomethingOnEventHappened`, {
error: error instanceof Error ? error.message : String(error)
})
}
}Error Mapping in Elysia Controllers
Map domain errors to HTTP responses in the Elysia route handler:
typescript
import { NotFoundError } from '@sws/common/domain'
app.get('/accounts/:id', async ({ params: { id }, set }) => {
try {
return await CexAccountFactory.cexAccountFinderController().run(id)
} catch (error) {
if (error instanceof NotFoundError) {
set.status = NotFoundError.statusCode
return { error: error.message }
}
set.status = 500
return { error: 'Internal server error' }
}
})