summary |
---|
Exception are errors raised during the HTTP request lifecycle. AdonisJS provides a robust exception handling mechanism to convert exceptions to HTTP responses and report them to the logger. |
Exceptions raised during an HTTP request are handled by the HttpExceptionHandler
defined inside the ./app/exceptions/handler.ts
file. Inside this file, you can decide how to convert exceptions to responses and log them using the logger or report them to an external logging provider.
The HttpExceptionHandler
extends the ExceptionHandler class, which does all the heavy lifting of handling errors and provides you with high-level APIs to tweak the reporting and rendering behavior.
import app from '@adonisjs/core/services/app'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http'
export default class HttpExceptionHandler extends ExceptionHandler {
protected debug = !app.inProduction
protected renderStatusPages = app.inProduction
async handle(error: unknown, ctx: HttpContext) {
return super.handle(error, ctx)
}
async report(error: unknown, ctx: HttpContext) {
return super.report(error, ctx)
}
}
The error handler is registered with the AdonisJS HTTP server inside the start/kernel.ts
file. We lazily import the HTTP handler using the #exceptions
alias defined in the package.json
file.
server.errorHandler(() => import('#exceptions/handler'))
The exceptions are handled by the handle
method on the exceptions handler class. By default, the following steps are performed while handling an error.
- Check if the error instance has a
handle
method. If yes, call the error.handle method and return its response. - Check if a status page is defined for the
error.status
code. If yes, render the status page. - Otherwise, render the exception using content negotiation renderers.
If you want to handle a specific exception differently, you can do that inside the handle
method. Make sure to use the ctx.response.send
method to send a response, since the return value from the handle
method is discarded.
import { errors } from '@vinejs/vine'
export default class HttpExceptionHandler extends ExceptionHandler {
async handle(error: unknown, ctx: HttpContext) {
if (error instanceof errors.E_VALIDATION_ERROR) {
ctx.response.status(422).send(error.messages)
return
}
return super.handle(error, ctx)
}
}
Status pages are a collection of templates you want to render for a given or a range of status codes.
The range of status codes can be defined as a string expression. Two dots separate the starting and the ending status codes (..
).
If you are creating a JSON server, you may not need status pages.
import { StatusPageRange, StatusPageRenderer } from '@adonisjs/http-server/types'
export default class HttpExceptionHandler extends ExceptionHandler {
protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
'404': (_, { view }) => view.render('errors/not-found'),
'500..599': (_, { view }) => view.render('errors/server-error')
}
}
The content negotiation renderers handle exceptions that are not self-handled and not converted to a status page.
The content negotiation renderers have support for debug mode. They can parse and pretty-print errors in debug mode using the Youch npm package.
You can toggle the debug mode using the debug
property on the exceptions handler class. However, turning off the debug mode in production is recommended, as it exposes sensitive information about your app.
export default class HttpExceptionHandler extends ExceptionHandler {
protected debug = !app.inProduction
}
The report
method on the exceptions handler class handles reporting of exceptions.
The method receives the error as the first argument and the HTTP context as the second argument. You should not write a response from the report
method and use the context only to read the request information.
All exceptions are reported using the logger by default.
- Exceptions with status codes in the
400..499
range are logged in thewarning
level. - Exceptions with the status code
>=500
are logged in theerror
level. - All other exceptions are logged in the
info
level.
You can add custom properties to the log messages by returning an object from the context
method.
export default class HttpExceptionHandler extends ExceptionHandler {
protected context(ctx: HttpContext) {
return {
requestId: ctx.requestId,
userId: ctx.auth.user?.id,
ip: ctx.request.ip(),
}
}
}
You can ignore exceptions from being reported by defining an array of status codes via the ignoreStatuses
property.
export default class HttpExceptionHandler extends ExceptionHandler {
protected ignoreStatuses = [
401,
400,
422,
403,
]
}
You can also ignore exceptions by defining an array of error codes or error classes to ignore.
import { errors } from '@adonisjs/core'
import { errors as sessionErrors } from '@adonisjs/session'
export default class HttpExceptionHandler extends ExceptionHandler {
protected ignoreCodes = [
'E_ROUTE_NOT_FOUND',
'E_INVALID_SESSION'
]
}
An array of exception classes can be ignored using the ignoreExceptions
property.
import { errors } from '@adonisjs/core'
import { errors as sessionErrors } from '@adonisjs/session'
export default class HttpExceptionHandler extends ExceptionHandler {
protected ignoreExceptions = [
errors.E_ROUTE_NOT_FOUND,
sessionErrors.E_INVALID_SESSION,
]
}
The logic to ignore status codes or exceptions is written inside the shouldReport
method. If needed, you can override this method and define your custom logic for ignoring exceptions.
import { HttpError } from '@adonisjs/core/types/http'
export default class HttpExceptionHandler extends ExceptionHandler {
protected shouldReport(error: HttpError) {
// return a boolean
}
}
You can create an exception class using the make:exception
ace command. An exception extends the Exception
class from the @adonisjs/core
package.
See also: Make exception command
node ace make:exception UnAuthorized
import { Exception } from '@adonisjs/core'
export default class UnAuthorizedException extends Exception {}
You can raise the exception by creating a new instance of it. When raising the exception, you can assign a custom error code and status code to the exception.
import UnAuthorizedException from '#exceptions/unauthorized_exception'
throw new UnAuthorizedException('You are not authorized', {
status: 403,
code: 'E_UNAUTHORIZED'
})
The error and status codes can also be defined as static properties on the exception class. The static values will be used if no custom value is defined when throwing the exception.
import { Exception } from '@adonisjs/core'
export default class UnAuthorizedException extends Exception {
static status = 403
static code = 'E_UNAUTHORIZED'
}
To self-handle the exception, you can define the handle
method on the exception class. This method should convert an error to an HTTP response using the ctx.response.send
method.
The error.handle
method receives an instance of the error as the first argument and the HTTP context as the second argument.
import { Exception } from '@adonisjs/core'
import { HttpContext } from '@adonisjs/core/http'
export default class UnAuthorizedException extends Exception {
async handle(error: this, ctx: HttpContext) {
ctx.response.status(error.status).send(error.message)
}
}
You can implement the report
method on the exception class to self-handle the exception reporting. The report method receives an instance of the error as the first argument and the HTTP context as the second argument.
import { Exception } from '@adonisjs/core'
import { HttpContext } from '@adonisjs/core/http'
export default class UnAuthorizedException extends Exception {
async report(error: this, ctx: HttpContext) {
ctx.logger.error({ err: error }, error.message)
}
}
The framework core and other official packages exports the exceptions raised by them. You can verify if an error is an instance of a specific exception using the instanceof
check. For example:
import { errors } from '@adonisjs/core'
try {
router.builder().make('articles.index')
} catch (error: unknown) {
if (error instanceof errors.E_CANNOT_LOOKUP_ROUTE) {
// handle error
}
}
Please check the exceptions reference guide to view the list of known errors.