Event Bus Pattern in Nuxt 3 with full TypeScript support
Tags: nuxt, vue, typescript
Published: 2022-11-17 23:08:27

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](https://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](https://github.com/developit/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](https://github.com/developit/mitt) 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]( https://nuxt.com/docs/guide/directory-structure/plugins#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.

```js
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
    }
  }
})
```

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

```js
// 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)
}
```

Then we can capture this event wherever we want:

```js
// 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...
})
```

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](https://github.com/developit/mitt#off) 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](https://github.com/developit/mitt) again we could improve our plugin like so:


```ts
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
    }
  }
})
```

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

![VS Code suggestion](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lu2wd7z37ysffbdl3ubo.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a37krcbhbw31vhsoyt9y.png)

![TypeScript Warning](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vresca5ne53m0mtske0n.png)

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:

```ts
// 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
```

```ts
// fire an event
useEvent('user:registered', { name: 'Israel'})
// capture
useListen('user:registered', (user) => console.log(user))
```

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](https://vuejobs.com/?utm_source=dev.to&utm_content=event-bus) or [Looking for Vue.js Jobs](https://vuejobs.com/?utm_source=dev.to&utm_content=event-bus)

Find me on Twitter: [IsraelOrtuno](https://twitter.com/IsraelOrtuno)