Events

AdonisJS ships with an event emitter module built on top of emittery . It differs from the Node.js native Events module in the following ways.

Usage

We recommend defining all the event listeners inside a dedicated file, just like the way you define routes in a single file.

For this guide, let's define the event listeners inside the start/events.ts file. You can create this file manually or run the following ace command.

node ace make:prldfile events
# SELECT ALL THE ENVIRONMENTS

Open the newly created file and write the following code inside it. The Event.on method registers a listener invoked every time you emit the new:user event from anywhere inside your application.

import Event from '@ioc:Adonis/Core/Event'
Event.on('new:user', (user) => {
console.log(user)
})

Making events type-safe

The event listeners and the code that emits the event are usually not in the same place/file. It is very easy for some part of your code to emit the event and send the wrong data. For example:

Event.on('new:user', (user) => {
console.log(user.email)
})
// There is no email property defined here
Event.emit('new:user', { id: 1 })

You can prevent this behavior by defining the argument's type for a given event inside the contracts/events.ts file.

declare module '@ioc:Adonis/Core/Event' {
interface EventsList {
'new:user': { id: number; email: string }
}
}

The TypeScript static compiler will ensure that all Event.emit calls for the new:user event are type-safe.

Listener classes

Like controllers and middleware, you can also extract the inline event listeners to their dedicated classes.

Conventionally event listeners are stored inside the app/Listeners directory. However, you can customize the namespace inside the .adonisrc.json file.

Customize event listeners namespace
{
"namespaces": {
"eventListeners": "App/CustomDir/Listeners"
}
}

You can create a listener class by running the following ace command.

node ace make:listener User
# CREATE: app/Listeners/User.ts

Open the newly created file and define the following method on the class.

import { EventsList } from '@ioc:Adonis/Core/Event'
export default class User {
public async onNewUser(user: EventsList['new:user']) {
// send email to the new user
}
}

Finally, you can bind the onNewUser method as the event listener inside the start/events.ts file. The binding process is similar to a Route controller binding, and there is no need to define the complete namespace.

Event.on('new:user', 'User.onNewUser')

Trapping events

To make testing easier, the Event module allows you to trap a specific or all the events. The actual listener is not invoked when there is a trap in place.

You can write the following code inside your test block just before the action that triggers the event.

import Event from '@ioc:Adonis/Core/Event'
Event.trap('new:user', (user) => {
assert.property(user, 'id')
assert.property(user, 'email')
})

You can also trap all the events using the trapAll method. The trap for a specific event gets preference over the catch-all trap.

Event.trap('new:user', (data) => {
// called for "new:user"
})
Event.trapAll((event, data) => {
// only called for "send:email"
})
Event.emit('new:user', {})
Event.emit('send:email', {})

Once done with the test, you must restore the trap using the Event.restore method. A better option will be to place this method inside the afterEach lifecycle hook of your testing framework.

afterEach(() => {
// Restores the trap
Event.restore()
})

Error handling

Emittery emits events asynchronously when you call the Event.emit method. One way to handle the errors is to wrap your emit calls inside a try/catch block.

try {
await Event.emit('new:user', { id: 1 })
} catch (error) {
// Handle error
}

However, this is not the most intuitive way to write code. Usually, you want to emit events and then forget about them.

To make error handling a bit easier, AdonisJS allows you to register an error handler invoked for all the errors that occurred during the event emit lifecycle.

You should define the error handler only once (maybe alongside the rest of the event handlers).

Event.onError((event, error, eventData) => {
// handle the error
})

Differences from the Node.js event emitter

As mentioned earlier, the Event module of AdonisJS is built on top of emittery , and it is different from the Node.js event emitter in the following ways.