In this post, I will show you how to prepare your nodeJS application to microservices using events, and then split your application easily
Imagine the following scenario
- A web frontend (made using your favorite framework)
- A backend made in node js exposing some routes (REST, GraphQL, or anything you need)
The backend application is huge, and you need to add some features at some key points of the code, for example sending an email to the user after registration, and “maybe”, one day, create the user as a customer in your CRM application.
One way to achieve this can be to add one function call for each need at the end of the
registerUser method, something like that :
This was has worked for decades, and still works, so if it fits your needs, it’s fine.
But as I don’t really know what, in the future, I will have to do after a user has registered, I will, now, prefer another way using events.
Using events, you will “emit” a
USER_HAS_REGISTERED event at the end of the
registerUser function with as payload the user. And somewhere else in your code you can create "listeners" to do something when the event is fired.
It will be something like that :
The idea is to “decorrelate” the
registerUser function and some of the actions you want to do when a user registers.
Another benefit is that if the function
registerUser is 100% test covered (and it is I'm sure ...), adding some feature after a user registers will not impact the code of the
A big warning before we continue … Events in Node.JS are a little bit counter-intuitive, I highly recommend you to read my article on this Understand Events with Node.JS
So, let’s see a small example to work with events.
A small “monolithic” example
We will create a small “monolithic” application using ExpressJS, actually, the application is only exposing one route,
POST /event for firing an event.
The package nodejs-event-manager will help you to migrate to RabbitMQ later (as they both expose the “same” API).
The code source of the small “monolithic” application is here : https://github.com/mimiz/road-to-microservice
First we will create an
EventManager.ts file to "encapsulate" the nodejs-event-manager :
See the documentation of nodejs-event-manager, to check about it’s options.
Then, the only route exposed :
And the listeners :
It’s trivial, and of course, in your real application, you will externalize handlers in modules.
Then the main
So now, if you post (with postman) an event on the main route :
You may see the following output in your server console :
Our listener caught the events and do the stuff they had to do … You can try with the other event …
NOTE: As you can see the nodejs-event-manager add a
_metas_ key to the payload, this can be useful for logging, but it can be disabled (or override), please read the documentation if you want to know how to do it.
This can work, because nodeJS is “mono-threaded”, so all listeners and emitters are sharing the same EventManager instance.
So as we want to split our small “monolithic” application into “micro” applications, we need to user “something” for sharing/storing/distributing events … Let’s do it with RabbitMQ.
Prepare the migration.
First, we will set up our small “monolithic” application to use RabbitMQ, as an “EventManager”, so the application will still be “ monolithic “ but all events will be distributed and listened via RabbitMQ.
You can either, start a local RabbitMQ server, or use CloudAMQP — RabbitMQ as a Servioce. For the purpose of this article, I will use a local (docker) RabbitMQ Server.
As you can see, I just changed the dependency, and add the
URL when creating the instance of the EventManager.
If you start your application and look at RabbitMQ admin application, you will see that two Exchanges have been created with the name of the events you are listening.
Two queues have also been created with the name of the application and the name of the event.
So if you post on the
/event route, the following :
Your server console will print out
So it worked as expected.
Now if you comment the line
eventManager.on('USER_HAS_REGISTERED', userRegisteredHandler); in
listeners.ts, restart your server, and post a USER_HAS_REGISTERED event, you will see that the server doesn't log anything (it's normal, we do not listen to the event), but the event is stored in the monolith::USER_HAS_REGISTERED queue, waiting for listeners.
Then uncomment the line, and restart your server, it should print out the event quite immediately, and so empty the queue. Great no?
Now create our “micro” applications
So now we will split the monolith into three applications :
- One for the Express APP (API)
- One for the
- One for the
Create the API Application
This is our small “monolithic” application, without listeners, here is the
index.ts file modified :
Nothing else… Just remember to remove the unused file
listeners.ts. That's all.
Create the userRegistered Application
This “micro” application is a simple listener, so we can create it with only one
index.ts file :
Create the anotherEvent Application
Just copy / paste and adapt from the
userRegistered application :
Now you may be able to start 3 nodeJS processes and try to post an event on the API, and you will see the event logged in the console of the micro-application listening for this event.
So now we migrate from a “monolithic” application to microservices.
Now you can, if you want, dockerize everything and deploy on your favorite cloud provider.
The source code of all application is shared on my Github road-to-microservice
Thanks for reading