messageCross Icon
Cross Icon
Software Development

How to Build Event-Driven Microservices with NestJS and Kafka

How to Build Event-Driven Microservices with NestJS and Kafka
How to Build Event-Driven Microservices with NestJS and Kafka

Event-driven architecture is one of the most effective ways to build scalable, loosely coupled microservices. Instead of services calling each other directly, they communicate by publishing and consuming events. This reduces tight coupling, improves resilience, and makes your system easier to evolve.

In this post, we’ll walk through how to build event-driven microservices using NestJS and Kafka, covering concepts, setup, and real code examples.

What Is Event-Driven Architecture (EDA)?

Event-Driven Architecture is a design paradigm where services communicate by emitting and reacting to events rather than invoking each other directly.

Key characteristics:

  • Events are facts, not commands
  • Producers don’t know who will consume the event
  • Consumers react asynchronously
  • The system evolves by adding new consumers, not modifying producers

Simple Example

  1. A user places an order
  2. Order Service sends an event: “Order Created
  3. Other services listen to that event:
    • Payment Service charges the user
    • The Notification Service sends an email

The services don’t know about each other - they only care about the event.

This makes the system:

  • Easier to scale
  • Less tightly connected
  • More reliable

This decoupling is what enables systems to scale both technically and organizationally.

Why Kafka + NestJS?

Kafka is a distributed event streaming platform designed for:

  • High throughput
  • Fault tolerance
  • Horizontal scalability
  • Event replay

NestJS is a perfect match because it provides:

  • First-class microservices support
  • Built-in Kafka transport
  • Strong typing and dependency injection
  • Clean, testable architecture

Together, they make event-driven systems both robust and developer-friendly

Kafka Setup Using Docker

Code

docker-compose.yml
version: '3.8'

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    container_name: zookeeper
    ports:
      - "2181:2181"
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  kafka:
    image: confluentinc/cp-kafka:7.5.0
    container_name: kafka
    ports:
      - "9092:9092"
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

Run Kafka
docker-compose up -d
Kafka is now running on localhost:9092 ✅
      
Hire Now!

Hire NestJS Developers Today!

Ready to bring your web application vision to life? Start your journey with Zignuts expert NestJS developers.

**Hire now**Hire Now**Hire Now**Hire now**Hire now

Common NestJS Kafka Setup

Install dependencies (for ALL services)

Code

npm install @nestjs/microservices kafkajs
      

Order Service (Producer)

Purpose

The Order Service is responsible for managing the order lifecycle. It acts as the starting point of the business flow.

Responsibilities

  • Exposes an HTTP API for creating orders
  • Validates incoming order requests
  • Creates an order record
  • Publishes an order.created event to Kafka

How It Works

When a client sends a request to create an order:

  1. The Order Service processes the request
  2. It constructs an order object containing order details
  3. It emits an order.created event to Kafka
  4. It immediately responds to the client without waiting for other services

Code

main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { OrderService } from './order.service';
import { OrderController } from './order.controller';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'KAFKA_SERVICE',
        transport: Transport.KAFKA,
        options: {
          client: {
            brokers: ['localhost:9092'],
          },
          consumer: {
            groupId: 'order-group',
          },
        },
      },
    ]),
  ],
  controllers: [OrderController],
  providers: [OrderService],
})
export class AppModule {}

order.controller.ts
import { Controller, Post } from '@nestjs/common';
import { OrderService } from './order.service';

@Controller('orders')
export class OrderController {
  constructor(private readonly orderService: OrderService) {}

  @Post()
  createOrder() {
    return this.orderService.createOrder();
  }
}


order.service.ts
import { Injectable, Inject, OnModuleInit } from '@nestjs/common';
import { ClientKafka } from '@nestjs/microservices';

@Injectable()
export class OrderService implements OnModuleInit {
  constructor(
    @Inject('KAFKA_SERVICE')
    private readonly kafkaClient: ClientKafka,
  ) {}

  async onModuleInit() {
    await this.kafkaClient.connect();
  }

  createOrder() {
    const order = {
      orderId: 'ORDER_1',
      userId: 'USER_1',
      amount: 100,
    };

    this.kafkaClient.emit('order.created', order);
    return { message: 'Order created successfully' };
  }
}
      

Payment Service (Consumer + Producer)

Purpose

The Payment Service is responsible for handling payments for created orders.

Responsibilities

  • Subscribes to the order.created Kafka topic
  • Processes payment logic for the order
  • Updates payment status (success or failure)
  • Can emit new events such as payment.success or payment.failed

How It Works

  1. Kafka delivers order.created events to the Payment Service
  2. The service processes the payment asynchronously
  3. Payment logic is executed independently of the Order Service
  4. Optionally, a new payment-related event is published

Code

main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Transport } from '@nestjs/microservices';

async function bootstrap() {
  const app = await NestFactory.createMicroservice(AppModule, {
    transport: Transport.KAFKA,
    options: {
      client: {
        brokers: ['localhost:9092'],
      },
      consumer: {
        groupId: 'payment-group',
      },
    },
  });

  await app.listen();
}
bootstrap();

payment.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';

@Controller()
export class PaymentController {

  @MessagePattern('order.created')
  handleOrderCreated(@Payload() order: any) {

    console.log('Payment received order:', order);
    console.log('Payment processed successfully');

    return {
      orderId: order.orderId,
      status: 'PAYMENT_SUCCESS',
    };
  }
}
      
Hire Now!

Hire DevOps Engineers Today!

Ready to enhance your development and operations strategies? Start your project with Zignuts expert DevOps engineers.

**Hire now**Hire Now**Hire Now**Hire now**Hire now

Notification Service (Consumer)

Purpose

The Notification Service handles all user communication related to orders.

Responsibilities

  • Listens for order.created events
  • Sends notifications such as email, SMS, or push messages
  • Handles communication logic only

How It Works

  1. Kafka delivers the order.created event
  2. The Notification Service reacts to the event
  3. A notification is sent to the user
  4. No response or event emission is required

Code

main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Transport } from '@nestjs/microservices';

async function bootstrap() {
  const app = await NestFactory.createMicroservice(AppModule, {
    transport: Transport.KAFKA,
    options: {
      client: {
        brokers: ['localhost:9092'],
      },
      consumer: {
        groupId: 'notification-group',
      },
    },
  });

  await app.listen();
}
bootstrap();

notification.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';

@Controller()
export class NotificationController {

  @MessagePattern('order.created')
  sendOrderNotification(@Payload() order: any) {
    console.log('Sending order notification for:', order.orderId);
  }
}
      

Conclusion

In this implementation, we built a simple event-driven microservices system using NestJS and Apache Kafka, with Kafka running locally using Docker.

We saw how:

  • Kafka acts as a message broker, allowing services to communicate through events
  • NestJS producers publish events when something happens (like an order being created
  • NestJS consumers listen to those events and react independently
  • Services remain loosely coupled, scalable, and resilient
  • Docker makes it easy to run Kafka locally without complex setup

By using events instead of direct service-to-service calls, the system becomes:

  • Easier to scale
  • More fault tolerant
  • Easier to extend with new services

This approach is ideal for growing applications where multiple services need to react to the same business events.
Starting with a simple setup like this helps you understand the core concepts before moving on to advanced topics such as retries, dead-letter queues, schema management, and monitoring.

card user img
Twitter iconLinked icon

A Node.js enthusiast focused on building scalable, high-performance applications that power the next generation of web technologies

card user img
Twitter iconLinked icon

Focused on ensuring product quality, refining user experiences, and delivering reliable, well-tested solutions across platforms.

Frequently Asked Questions

No items found.
Book Your Free Consultation Click Icon

Book a FREE Consultation

No strings attached, just valuable insights for your project

download ready
Thank You
Your submission has been received.
We will be in touch and contact you soon!
View All Blogs