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.'