DEV Community

Cover image for Event Bus Pattern in Nuxt 3 with full TypeScript support
Israel Ortuño
Israel Ortuño

Posted on

Event Bus Pattern in Nuxt 3 with full TypeScript support

I needed to run certain actions at some common user interactions. These interactions could also be the same but in totally different parts of the application so required reusability. The easiest way I found to have a structured and ordered way to either notify about these interactions happening and react to them was using the Event Bus Pattern.

Using an Event Bus is an easy way to communicate different parts of our application. Events can be emitted from anywhere in our app (with or without payload) and then trigger some listener functions (which will receive the payload as parameter) that may be hooked to this particular event. Just like a Vue component emits an event, then its parent can capture and run some code in response. The event bus is essentially the same but in a more global scope without the parent-child structure.

Since the release of Vue 3, using a Vue instance as event bus is no longer the way to go. If we are looking to implement this pattern in our app we may require a library like Mitt, which was recommended by vuejs.org itself (I can't find it anymore in the docs but I am pretty sure it was there).

In this article I want to show you how I have implemented this pattern in my Nuxt 3 applications using Mitt which already provides full TypeScript support. To fully understand what's happening under the hood I recommend to have a quick read of Mitt documentation before continue reading.

Mitt will act as the bus itself so all we have to do is just to create a plugin to be able to use its functionality in a more "Nuxt way". Let's create a plugin (plugins/event-bus.ts) and basically map the functions we need from Mitt to our application.

I am assuming you have some knowledge about how Nuxt plugins and helpers providers work. Visit Automatically Providing Helpers for more info.

Mitt's API and Vue events are quite similar in naming ($emit/emit and $on/on). I have mapped these methods to event and listen (Laravel) to avoid potential collision and confusion.

import mitt from 'mitt'

export default defineNuxtPlugin(() => {
  const emitter = mitt()

  return {
    provide: {
      event: emitter.emit, // Will emit an event
      listen: emitter.on // Will register a listener for an event
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

This will let us use $event and $listen functions anywhere in our application by just using the useNuxtApp composable:

// components/register.vue
const { $event } = useNuxtApp()

const onUserRegistered = (user) => {
  // User registration form was saved and then we want
  // to notify the entire application about that.
  $event('user:registered', user)
}
Enter fullscreen mode Exit fullscreen mode

Then we can capture this event wherever we want:

// app.vue 
// ideally you will have a plugin (or multiple) to register listeners

const { $event } = useNuxtApp()

$listen('user:registered', (user) => {
  console.log('A user was registered!', user)
  // do something here...
  // An interesting example for this use case could be to 
  // just register an event or a conversion in your 
  // favourite analytics tool like Google Analytics, Fathom...
})
Enter fullscreen mode Exit fullscreen mode

You can register multiple listeners for the same event anywhere in your application. Note that listeners can and must be removed at certain use cases. Just want to keep this example as simple as possible to refer to Mitt docs for more info.

That's all about how the event bus is supposed to work! But now we want to move one step further and since Mitt supports TypeScript, we can make sure we are firing safe event names with a safe payload too. Based on Mitt docs again we could improve our plugin like so:

import mitt from 'mitt'

interface User {
  name: string
}

type ApplicationEvents = {
  // translates to: 
  // user:registered -> event name
  // User -> type as payload
  'user:registered': User
  'user:deleted': User
};

export default defineNuxtPlugin(() => {
  // Then we just inform mitt about our event types
  const emitter = mitt<ApplicationEvents>()

  return {
    provide: {
      event: emitter.emit, // Will emit an event
      listen: emitter.on // Will register a listener for an event
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Voilà! Our editor is already aware about this and will suggest accordingly:

VS Code suggestion

But not only that, the payload is also typed so our application will warn if we aren't providing the right type:

Fire an event Screenshot

TypeScript Warning

Now we are ready to use our Event Bus to fire any event we like and capture them for processing.

Based on @jacobandrewsk comments I will also show an example of how to use this as a composable instead of a plugin:

// composables/useEventBus.ts
import mitt from 'mitt'

type ApplicationEvents = {
  'user:registered': User
};

const emitter = mitt<ApplicationEvents>()

export const useEvent = emitter.emit
export const useListen = emitter.on
Enter fullscreen mode Exit fullscreen mode
// fire an event
useEvent('user:registered', { name: 'Israel'})
// capture
useListen('user:registered', (user) => console.log(user))
Enter fullscreen mode Exit fullscreen mode

This way we can benefit from Nuxt 3 auto imports and use these two functions straight away anywhere in our application.

Conclusion

Here I have shown a very basic example but there is no limit on the amount of events your application can fire and react to.

I find this pattern specially useful for code reusability and making certain parts of my applications extensible. Integrating with third party services (like the example above) or displaying notifications could be a couple of examples. Feel free to use your imagination where this can be useful.

Leave a comment below and let me know what you think! 👇

One more thing

I am the co-founder of VueJobs –The #1 Vue.js Job Board– check it out if looking to Hire Vue.js Developers or Looking for Vue.js Jobs

Find me on Twitter: IsraelOrtuno

Top comments (10)

Collapse
 
jacobandrewsky profile image
Jakub Andrzejewski

Really good article!

Just to make it bit more Vue/Nuxt 3 way, I think it would have been useful to wrap the logic from last chapters of the article into a composable like useEmitEvent and useListenEvent (names can be choosen better :D )

But overall really nice one!

Collapse
 
jacobandrewsky profile image
Jakub Andrzejewski

You could pass the name of the event as the first parameter and value as the second one. Thanks to that you could make the composable auto imported so that user would not have to import nuxtApp everytime :)

Collapse
 
kissu profile image
Konstantin BIFERT

Jakub is on fire on Dev. 🔥🔥🔥

Collapse
 
israelortuno profile image
Israel Ortuño

Added a composable example, thanks for the feedback

Collapse
 
gkkirilov profile image
Georgi Kirilov

Isn't this solved in a nicer way by state management plugins like pinia?

Collapse
 
israelortuno profile image
Israel Ortuño

Not sure about Pinta making it that simple but for sure there are multiple ways to get similar behaviour. Even @atinux suggested using unjs/hookable twitter.com/Atinux/status/15946800...

Collapse
 
gkkirilov profile image
Georgi Kirilov

thanks, will check that also

Collapse
 
eserdinyo profile image
Muhammet ESER

Here is my composable solution with hooks.
gist.github.com/eserdinyo/5acf5924...

ref: twitter.com/Atinux/status/15946800...

Collapse
 
fanvvv profile image
Fan

Hi, this is great, how to write the type of emit and on please, thanks!

Collapse
 
bendwarn profile image
Bendwarn

What's the difference with unjs/hookable used by nuxt?