Skip to main content

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 }
)
}