Skip to content

Error Handling

Lacis exports typed HTTP error constructors and a sendError helper. Use them to send consistent, properly-formatted error responses without writing res.status(...).json(...) by hand.

import {
createBadRequestError, // 400
createUnauthorizedError, // 401
createForbiddenError, // 403
createNotFoundError, // 404
createConflictError, // 409
createValidationError, // 422
createInternalServerError, // 500
createServiceUnavailableError, // 503
createGatewayTimeoutError, // 504
} from 'lacis'

Each accepts an optional message and details object:

createNotFoundError('User not found', { id: req.params.id })

The simplest pattern: throw any error constructor and Lacis will send the correct status code automatically.

import type { Request, Response } from 'lacis'
import { createUnauthorizedError, createNotFoundError } from 'lacis'
export async function GET(req: Request, res: Response) {
const token = req.getHeader('authorization')
if (!token) throw createUnauthorizedError('Missing token')
const user = await db.findUser(req.params.id)
if (!user) throw createNotFoundError('User not found', { id: req.params.id })
res.json(user)
}

An alternative to throwing — sends the error as JSON and sets the correct status code inline. Useful when you need to send an error and then do cleanup work before returning.

import type { Request, Response } from 'lacis'
import { sendError, createUnauthorizedError, createNotFoundError } from 'lacis'
export async function GET(req: Request, res: Response) {
const token = req.getHeader('authorization')
if (!token) {
sendError(createUnauthorizedError('Missing token'), res)
return
}
const user = await db.findUser(req.params.id)
if (!user) {
sendError(createNotFoundError('User not found', { id: req.params.id }), res)
return
}
res.json(user)
}

The client receives:

{ "error": "User not found", "code": 404, "details": { "id": "42" } }
  • 4xx errorsdetails are included in the response body (safe to expose to the client)
  • 5xx errorsdetails are never sent to the client; the error is logged server-side
// ✅ details sent to client — 422
sendError(createValidationError('Invalid input', { field: 'email' }), res)
// → { "error": "Invalid input", "code": 422, "details": { "field": "email" } }
// ✅ details NOT sent to client — 500
sendError(createInternalServerError('DB query failed', { query: sql }), res)
// → { "error": "Internal Server Error", "code": 500 }

Use the onError middleware hook to centralize logging or forward errors to an external service:

import { createServer, normalizeError, sendError } from 'lacis'
createServer('./routes', {
middleware: {
onError: async (req, res, ctx) => {
const error = normalizeError(ctx.error)
// Forward to Sentry, Datadog, etc.
captureException(error)
if (!res.headersSent) {
sendError(error, res)
}
},
},
})

Converts any unknown error into a typed HttpError. Useful in onError when you don’t control the error source:

import { normalizeError } from 'lacis'
const error = normalizeError(unknownErr)
// ECONNREFUSED / ENOTFOUND → 503
// ETIMEDOUT → 504
// { statusCode: 422 } → 422
// anything else → 500