Event-Driven Microservices with Node.js and Kafka
This article provides a practical guide on how to implement an event-driven microservice architecture using Node.js and Apache Kafka. You will learn how to set up a local Kafka broker, write a Node.js producer service to publish events, and create a Node.js consumer service to process those events in real-time. By the end of this guide, you will have a working prototype of decoupled, scalable microservices communicating asynchronously.
Understanding the Architecture
In an event-driven microservices architecture, services communicate by producing and consuming events rather than making direct HTTP requests.
- Producer Service: A microservice that detects a state change (an event) and publishes a message to a Kafka topic.
- Apache Kafka: A distributed event streaming platform that acts as the message broker, storing and routing messages.
- Consumer Service: A microservice that subscribes to a Kafka topic, listens for new messages, and processes them independently.
Step 1: Setting Up Apache Kafka
The fastest way to run Apache Kafka locally is using Docker Compose.
Create a file named docker-compose.yml with the following
configuration to spin up Zookeeper and Kafka:
version: '3'
services:
zookeeper:
image: wurstmeister/zookeeper:3.4.6
ports:
- "2181:2181"
kafka:
image: wurstmeister/kafka:2.13-2.8.1
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
volumes:
- /var/run/docker.sock:/var/run/docker.sockRun the following command in your terminal to start the services:
docker-compose up -dStep 2: Initializing the Node.js Projects
Create a directory for your project and initialize two separate Node.js services: a producer (e.g., an Order Service) and a consumer (e.g., a Notification Service).
mkdir kafka-microservices && cd kafka-microservices
mkdir producer consumerIn both the producer and consumer
directories, initialize a Node.js project and install
kafkajs, the popular, modern Kafka client for Node.js:
npm init -y
npm install kafkajsStep 3: Implementing the Node.js Producer
The producer service will connect to the Kafka broker and send
message payloads to a specific topic. Save the following code as
index.js inside the producer folder.
const { Kafka } = require('kafkajs');
const kafka = new Kafka({
clientId: 'order-service',
brokers: ['localhost:9092']
});
const producer = kafka.producer();
const sendOrderEvent = async () => {
await producer.connect();
console.log('Producer connected successfully');
const orderData = {
orderId: 'ORD-99281',
customerEmail: 'user@example.com',
amount: 129.99,
status: 'CREATED'
};
await producer.send({
topic: 'order-events',
messages: [
{ value: JSON.stringify(orderData) }
],
});
console.log('Event published: Order Created');
await producer.disconnect();
};
sendOrderEvent().catch(console.error);Step 4: Implementing the Node.js Consumer
The consumer service runs continuously, listening for new messages on
the order-events topic. Save the following code as
index.js inside the consumer folder.
const { Kafka } = require('kafkajs');
const kafka = new Kafka({
clientId: 'notification-service',
brokers: ['localhost:9092']
});
const consumer = kafka.consumer({ groupId: 'notification-group' });
const runConsumer = async () => {
await consumer.connect();
console.log('Consumer connected successfully');
await consumer.subscribe({ topic: 'order-events', fromBeginning: true });
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
const order = JSON.parse(message.value.toString());
console.log(`Received order event for Order ID: ${order.orderId}`);
console.log(`Sending confirmation email to ${order.customerEmail}...`);
// Add notification sending logic here
},
});
};
runConsumer().catch(console.error);Step 5: Testing the Ecosystem
Start your consumer microservice first so it is ready to listen for incoming events:
node consumer/index.jsIn a separate terminal window, run your producer microservice to publish a new event:
node producer/index.js
Upon running the producer, the console output of the consumer will instantly update, showing that it received the payload and initiated the notification sequence.
Scaling and Best Practices
To scale this architecture in a production environment:
- Consumer Groups: Run multiple instances of your
consumer microservice using the same
groupId. Kafka will automatically balance the partition load across all active instances, allowing parallel message processing. - Error Handling and Retries: Implement dead-letter queues (DLQ) to handle malformed messages. If a consumer fails to process a message after multiple retries, send it to a DLQ topic for manual inspection without blocking the main event pipeline.