Error Handling
Kysera provides a comprehensive error handling system with typed errors for different database operations.
Error Hierarchy
DatabaseError (base)
├── UniqueConstraintError
├── ForeignKeyError
├── NotFoundError
├── BadRequestError
├── NotNullError
└── CheckConstraintError
Error Classes
DatabaseError
Base error class for all database errors:
class DatabaseError extends Error {
code: string
detail?: string
originalError?: unknown
toJSON(): Record<string, unknown>
}
UniqueConstraintError
Thrown when a UNIQUE constraint is violated:
class UniqueConstraintError extends DatabaseError {
constraint: string // e.g., 'users_email_unique'
columns: string[] // e.g., ['email']
value?: unknown // The duplicate value
}
ForeignKeyError
Thrown when a FOREIGN KEY constraint is violated:
class ForeignKeyError extends DatabaseError {
constraint: string
table: string
column: string
referencedTable?: string
}
NotFoundError
Thrown when an entity is not found:
class NotFoundError extends DatabaseError {
entity?: string
id?: unknown
}
NotNullError
Thrown when a NOT NULL constraint is violated:
class NotNullError extends DatabaseError {
column: string
}
CheckConstraintError
Thrown when a CHECK constraint is violated:
class CheckConstraintError extends DatabaseError {
constraint: string
}
Parsing Database Errors
Use parseDatabaseError to convert raw database errors into typed errors:
import { parseDatabaseError, UniqueConstraintError, ForeignKeyError } from '@kysera/core'
try {
await db.insertInto('users').values({ email: 'duplicate@test.com' }).execute()
} catch (error) {
const dbError = parseDatabaseError(error, 'postgres')
if (dbError instanceof UniqueConstraintError) {
console.log('Duplicate:', dbError.constraint, dbError.columns)
// Handle duplicate entry
} else if (dbError instanceof ForeignKeyError) {
console.log('Invalid reference:', dbError.referencedTable)
// Handle invalid foreign key
} else {
// Handle other database errors
throw dbError
}
}
Multi-Database Support
The error parser works with all supported databases:
PostgreSQL
const error = parseDatabaseError(pgError, 'postgres')
// Handles: 23505 (unique), 23503 (foreign key), 23502 (not null), 23514 (check)
MySQL
const error = parseDatabaseError(mysqlError, 'mysql')
// Handles: ER_DUP_ENTRY, ER_NO_REFERENCED_ROW, ER_BAD_NULL_ERROR
SQLite
const error = parseDatabaseError(sqliteError, 'sqlite')
// Handles: UNIQUE constraint failed, FOREIGN KEY constraint failed, etc.
Unified Error Codes
Kysera provides a unified error code system:
// Database errors
'DB_CONNECTION_FAILED'
'DB_QUERY_FAILED'
'DB_TRANSACTION_FAILED'
'DB_TIMEOUT'
'DB_POOL_EXHAUSTED'
// Validation errors
'VALIDATION_UNIQUE_VIOLATION'
'VALIDATION_FOREIGN_KEY_VIOLATION'
'VALIDATION_NOT_NULL_VIOLATION'
'VALIDATION_CHECK_VIOLATION'
// Resource errors
'RESOURCE_NOT_FOUND'
'RESOURCE_BAD_REQUEST'
'RESOURCE_ALREADY_EXISTS'
'RESOURCE_CONFLICT'
Error Handling Patterns
In Repositories
async function createUser(data: CreateUserInput): Promise<User> {
try {
return await db.insertInto('users')
.values(data)
.returningAll()
.executeTakeFirstOrThrow()
} catch (error) {
const dbError = parseDatabaseError(error, dialect)
if (dbError instanceof UniqueConstraintError) {
throw new BadRequestError(`Email ${data.email} already exists`)
}
throw dbError
}
}
In API Handlers
app.post('/users', async (req, res) => {
try {
const user = await userRepo.create(req.body)
res.status(201).json(user)
} catch (error) {
if (error instanceof ValidationError) {
return res.status(400).json({
error: 'Validation failed',
details: error.errors
})
}
if (error instanceof UniqueConstraintError) {
return res.status(409).json({
error: 'Resource already exists',
field: error.columns[0]
})
}
if (error instanceof NotFoundError) {
return res.status(404).json({
error: 'Resource not found'
})
}
// Log unexpected errors
logger.error('Unexpected error:', error)
res.status(500).json({ error: 'Internal server error' })
}
})
Error Logging
try {
await userRepo.update(userId, data)
} catch (error) {
logger.error('Failed to update user', {
userId,
data: { ...data, password: '[REDACTED]' }, // Don't log sensitive data
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
code: error instanceof DatabaseError ? error.code : undefined
})
throw error
}
JSON Serialization
All errors support JSON serialization for API responses:
const error = new UniqueConstraintError('users_email_unique', ['email'], 'test@example.com')
console.log(error.toJSON())
// {
// name: 'UniqueConstraintError',
// message: 'Unique constraint violation',
// code: 'VALIDATION_UNIQUE_VIOLATION',
// constraint: 'users_email_unique',
// columns: ['email'],
// value: 'test@example.com'
// }
Best Practices
1. Use Typed Errors
// Good: Specific error handling
if (error instanceof UniqueConstraintError) {
return { error: 'Email already exists' }
}
// Bad: Generic error handling
if (error.message.includes('duplicate')) {
return { error: 'Something is duplicate' }
}
2. Don't Swallow Errors
// Bad: Silent error swallowing
try {
await userRepo.delete(userId)
} catch (error) {
// Nothing here - dangerous!
}
// Good: Explicit handling
try {
await userRepo.delete(userId)
} catch (error) {
logger.warn('User deletion failed', { userId, error })
if (error instanceof DatabaseError) {
throw new ApplicationError('Failed to delete user')
}
throw error
}
3. Preserve Error Context
// Good: Wrap with context
try {
await processOrder(orderId)
} catch (error) {
throw new ApplicationError(
`Failed to process order ${orderId}`,
{ cause: error }
)
}