Backend
Error Handling
The HttpError hierarchy and global error handler from codi-node-utils.
HttpError Class Hierarchy
All operational errors extend HttpError from @codisolutions23/node-utils:
HttpError (base — isOperational: true)
├── BadRequestError 400
├── UnauthorizedError 401
├── ForbiddenError 403
├── NotFoundError 404
├── ConflictError 409
├── UnprocessableEntityError 422
└── InternalServerError 500
Throwing Errors
Throw these from services (never from controllers — controllers call next(error)):
// Service
import {
NotFoundError,
ConflictError,
UnauthorizedError,
UnprocessableEntityError,
} from '@codisolutions23/node-utils'
async function createUser(input: TUserCreate) {
const existing = await userRepo.getUserByEmail(input.email)
if (existing) throw new ConflictError('Email already in use.')
return userRepo.createUser(input)
}
async function login(input: TLoginInput) {
const user = await userRepo.getUserByEmail(input.email)
if (!user) throw new NotFoundError('User not found.')
const match = await comparePasswords(input.password, user.password!)
if (!match) throw new UnauthorizedError('Invalid credentials.')
// ...
}
Controller Error Flow
Controllers never construct error responses directly. They validate → next(error):
// ✅ Controller
async function create(req, res, next) {
try {
const { error, value } = createSchema.validate(req.body)
if (error) return next(new UnprocessableEntityError(error.message)) // Joi error
const result = await _createService(value)
res.status(201).json(result)
} catch (error) {
log('error', resource, 'create', 'Failed.', { error })
return next(error) // passes HttpError (or unexpected Error) to errorHandler
}
}
Global Error Handler
Registered last in app.ts via errorHandler from codi-node-utils:
// Behaviour of errorHandler
export const errorHandler = (error: HttpError, req, res, next) => {
if (error.isOperational) {
// Known HttpError — send structured response
res.status(error.statusCode).json({
status: 'error',
message: error.message,
})
} else {
// Unexpected error — log and return generic 500
logger.error('Unexpected error', { error })
res.status(500).json({
status: 'error',
message: new InternalServerError().message,
})
}
}
Example Error Responses
// 422 Unprocessable Entity
{
"status": "error",
"message": "\"email\" must be a valid email address."
}
// 401 Unauthorized
{
"status": "error",
"message": "Invalid credentials."
}
// 404 Not Found
{
"status": "error",
"message": "User not found."
}
Frontend Handling
The $api plugin in the frontend reads error.data.message:
// In a composable or handleApiAction catch block
const msg = error?.data?.message || error?.message || 'An error occurred.'