Skip to content

Errors

error(status, body) produces a response instead of a crash:

ts
import { get, error } from "clovejs"

export default get(async (req, res, ctx) => {
  const user = await ctx.users.findById(Number(req.params.id))
  if (!user) throw error(404, { message: "No such user" })
  return user
})

Anything else that escapes a handler becomes a 500, with the details logged.

Two kinds of failure

Rendered asLoggedBody
throw error(...)The status you choseNo — it is an expected outcomeThe body you passed
Any other throw500Yes, with the stackGeneric, unless exposeErrors

The split is deliberate. A 401 from a login route is not an incident and should not page anyone; a TypeError from a service is.

Throwing from anywhere

error() works in handlers, middlewares, services and di/ factories alike — the pipeline catches it wherever it originates:

ts
// src/services/auth.ts
export default service(async (ctx) => ({
  async login({ username, password }) {
    const user = await ctx.db.user.find({ username })
    if (!user) throw error(401, { message: "Username / password pair mismatch" })
    return user
  },
}))

The route stays free of translation code.

The body

error() accepts anything serialisable:

ts
throw error(400, {
  message: "Validation failed",
  fields: { email: "must be an email address" },
})

Passing a string uses it as the message, and omitting the body entirely produces { message: "HTTP 404" }. When the body is an object with a message property, that value also becomes the JS Error.message, so logs and responses agree.

Stack traces

Stacks are included in 500 response bodies only outside production. Force either way with the exposeErrors option:

ts
bootstrap({ exposeErrors: false })

Leave it unset unless you have a reason — the default already does the right thing per environment.

Catching errors in a middleware

Because errors propagate through the chain, one middleware can render them all:

ts
// src/middlewares/errors.0.ts
import { middleware, isHttpError } from "clovejs"

export default middleware(async ({ handler, ctx }) => {
  try {
    return await handler.execute()
  } catch (err) {
    if (isHttpError(err)) throw err        // let the pipeline render it
    ctx.logger.error("unhandled", err)
    throw err
  }
})

Use isHttpError() rather than instanceof HttpError: a project can end up with more than one copy of the framework loaded (ESM alongside CJS, or a hoisting miss), and an error thrown by one copy must still be recognised by the other. isHttpError checks a shared symbol brand, so it works across copies.

Boot errors

Convention violations found while scanning the project throw CloveBootErrorbefore the server starts, and always name the offending files:

CloveBootError: Route file method does not match its handler
  - src/api/v1/login.post.ts

There is no partial start: a project either satisfies its conventions or does not run.

Released under the MIT License.