Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Events and listeners

When a comment is added to a post, you need to save it, email the author and maybe update a counter. Written in the controller, all of this makes it grow. An event is a message "something happened", and a listener is code that reacts to it. The controller only announces the event and does not know who will do what in response.

An event class

node ace make:event CommentCreated

The file app/events/comment_created.ts appears. The event holds data about what happened:

// app/events/comment_created.ts
import { BaseEvent } from '@adonisjs/core/events'
import Comment from '#models/comment'
 
export default class CommentCreated extends BaseEvent {
  constructor(public comment: Comment) {
    super()
  }
}

Dispatching the event

// app/controllers/comments_controller.ts
import CommentCreated from '#events/comment_created'
 
const comment = await post.related('comments').create({ body: payload.body })
CommentCreated.dispatch(comment)

The static dispatch method creates the event with the given arguments and announces it to all listeners. The controller carries on right away.

A listener

node ace make:listener NotifyPostAuthor
// app/listeners/notify_post_author.ts
import mail from '@adonisjs/mail/services/main'
import CommentCreated from '#events/comment_created'
 
export default class NotifyPostAuthor {
  async handle(event: CommentCreated) {
    const comment = event.comment
    await comment.load('post', (query) => query.preload('author'))
 
    await mail.sendLater((message) => {
      message
        .to(comment.post.author.email)
        .subject('New comment')
        .html(`<p>Someone commented on "${comment.post.title}".</p>`)
    })
  }
}

The handle method receives the event object. Here we assume a post has an author relationship with the User model.

Registering the listener

Events and listeners are connected in start/events.ts. If it does not exist, create it with node ace make:preload events:

// start/events.ts
import emitter from '@adonisjs/core/services/emitter'
import CommentCreated from '#events/comment_created'
 
const NotifyPostAuthor = () => import('#listeners/notify_post_author')
 
emitter.on(CommentCreated, [NotifyPostAuthor, 'handle'])

The listener is attached lazily, just like a controller. Now after every new comment the post author gets an email. To add another action, a new listener is enough — the controller does not change.

The simple form

For small reactions you can pass a function instead of a class:

// start/events.ts
emitter.on(CommentCreated, (event) => {
  console.log(`New comment on post ${event.comment.postId}`)
})

The server terminal shows a line about every new comment.

Common mistake

An error in a listener does not reach the controller: the event has already been dispatched, and the visitor sees a successful response even if the email was not sent. So only secondary actions go into listeners. Keep in the controller whatever the operation cannot succeed without, such as the database write.

Thanks for reading!

Donate to the site — we work hard for you.