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.
Built-in error constructors
Section titled “Built-in error constructors”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 })Throwing errors
Section titled “Throwing errors”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)}sendError
Section titled “sendError”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" } }Details exposure
Section titled “Details exposure”- 4xx errors —
detailsare included in the response body (safe to expose to the client) - 5xx errors —
detailsare never sent to the client; the error is logged server-side
// ✅ details sent to client — 422sendError(createValidationError('Invalid input', { field: 'email' }), res)// → { "error": "Invalid input", "code": 422, "details": { "field": "email" } }
// ✅ details NOT sent to client — 500sendError(createInternalServerError('DB query failed', { query: sql }), res)// → { "error": "Internal Server Error", "code": 500 }Global error handler
Section titled “Global error handler”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) } }, },})normalizeError
Section titled “normalizeError”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