Events

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

Usage

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

For the purpose of this guide, lets 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 that is invoked everytime 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 events 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 to it. 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 arguments 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 }
}
}

Now, the TypeScript static compiler will make sure that all Event.emit calls for the new:user event are type safe.

Listener classes

Just like controllers and middleware, you can also extract the inline event listeners to their own 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

In order 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()
})

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 following ways.