Exception Handling

AdonisJS uses exceptions for flow control. Meaning, instead of writing too many conditionals, we prefer raising exceptions and then handle them to return an appropriate response. For example:

Instead of writing the following code

Route.get('dashboard', async ({ auth, response }) => {
if (!auth.isLoggedIn) {
return response.status(401).send('Unauthenticated')
}
// business logic
})

We prefer writing

In the following example, the auth.authenticate method will raise an exception if the user is not logged in. The exception has the ability to handle itself and return an appropriate response.

Route.get('dashboard', async ({ auth, response }) => {
await auth.authenticate()
// business logic
})

Converting every conditional to an exception is also not the right approach. Instead, you can start by converting conditionals that always result in aborting the request.

Handling exceptions globally

The exceptions raised during an HTTP request are forwarded to the global exception handler stored inside the app/Exceptions/Handler.ts file.

app/Exceptions/Handler.ts
import Logger from '@ioc:Adonis/Core/Logger'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
export default class ExceptionHandler extends HttpExceptionHandler {
protected statusPages = {
'404': 'errors/not-found',
'500..599': 'errors/server-error',
}
constructor() {
super(Logger)
}
}

The handle method in this class is responsible for handling the exceptions and converting them to a response. Either you can let the parent class (HttpExceptionHandler ) handle the errors for you, or you can define the handle method to self handle them.

import Logger from '@ioc:Adonis/Core/Logger'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
export default class ExceptionHandler extends HttpExceptionHandler {
protected statusPages = {
'404': 'errors/not-found',
'500..599': 'errors/server-error',
}
constructor() {
super(Logger)
}
public async handle(error: any, ctx: HttpContextContract) {
/**
* Self handle the validation exception
*/
if (error.code === 'E_VALIDATION_FAILURE') {
return ctx.response.status(422).send(error.messages)
}
/**
* Forward rest of the exceptions to the parent class
*/
return super.handle(error, ctx)
}
}

Error reporting

Alongside the handle method, you can also implement the report method to report the exception to logging or an error monitoring service.

The HTTP response does not wait for the report method to finish. In other words, the report method is executed in the background.

import Logger from '@ioc:Adonis/Core/Logger'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
export default class ExceptionHandler extends HttpExceptionHandler {
protected statusPages = {
'404': 'errors/not-found',
'500..599': 'errors/server-error',
}
constructor() {
super(Logger)
}
public async report(error: any, ctx: HttpContextContract) {
if (!this.shouldReport(error)) {
return
}
someReportingService.report(error.message)
}
}

Http Exception Handler

All of the following features are only available when the global exception handler extends the HttpExceptionHandler , class. If you decide not to extend from this class, then the following features will not work.

Status pages

The statusPages page property on the exception handler allows you to associate edge templates to a range of error status codes.

In the following example, all 404 errors will render the errors/not-found.edge template, and the errors between the range of 500 - 599 will render the errors/server-error.edge template.

import Logger from '@ioc:Adonis/Core/Logger'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
export default class ExceptionHandler extends HttpExceptionHandler {
protected statusPages = {
'404': 'errors/not-found',
'500..599': 'errors/server-error',
}
constructor() {
super(Logger)
}
}

Ignoring exceptions by code and status

You can ignore certain exceptions from being reported by whitelisting them inside the ignoreCodes and ignoreStatuses properties.

import Logger from '@ioc:Adonis/Core/Logger'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
export default class ExceptionHandler extends HttpExceptionHandler {
protected ignoreCodes = ['E_ROUTE_NOT_FOUND']
protected ignoreStatuses = [404, 422, 403, 401]
constructor() {
super(Logger)
}
}

Custom exceptions

You can create custom exceptions by executing the following ace command.

node ace make:exception UnAuthorized
# CREATE: app/Exceptions/UnAuthorizedException.ts

Next, import and raise the exception as follows.

import UnAuthorized from 'App/Exceptions/UnAuthorizedException'
const message = 'You are not authorized'
const status = 403
const errorCode = 'E_UNAUTHORIZED'
throw new UnAuthorized(message, status, errorCode)

You can self-handle this exception by implementing the handle method on the exception class.

app/Exceptions/UnAuthorizedException.ts
import { Exception } from '@adonisjs/core/build/standalone'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class UnAuthorizedException extends Exception {
public async handle(error: this, ctx: HttpContextContract) {
ctx.response.status(error.status).send(error.message)
}
}

Optionally, implement the report method to report the exception to a logging or error reporting service.

app/Exceptions/UnAuthorizedException.ts
import { Exception } from '@adonisjs/core/build/standalone'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class UnAuthorizedException extends Exception {
public report(error: this, ctx: HttpContextContract) {
reportingService.report(error.message)
}
}