Saeed Ghofrani
For RecruitersArchitectureCase StudiesProjectsExperienceSkillsBlogContact
Discuss a role
For RecruitersArchitectureCase StudiesProjectsExperienceSkillsBlogContact
  1. Home
  2. /Blog
  3. /Building Scalable Microservices with NestJS and Kafka

Hiring for backend ownership?

The recruiter brief has my role fit, strongest production results, resume, and direct contact details.

Open recruiter briefEmail me

Saeed Ghofrani Ivari

I build backend systems, lead delivery, and stay close to production.

Focus

NestJS, data-heavy services, real-time products, and Linux operations.

Contact

sa.ghofraniivari@gmail.comTelegram
GitHubLinkedInStack OverflowDev.toRecruiter briefPlayground
nestjsmicroserviceskafkaarchitecture

Building Scalable Microservices with NestJS and Kafka

How I approach NestJS microservices: service boundaries first, transport second, and operational behavior always visible.

2 min readSystem designSaeed Ghofrani Ivari

From my notebook

Start with ownership, then choose the transport

Where this came from

Most of my production broker work has used RabbitMQ. I wrote this note while comparing those lessons with a smaller public NestJS/Kafka configuration project. The transport changes, but ownership, idempotency, retries, and observability remain the hard parts.

My short checklist

  • - Choose service boundaries from ownership and failure modes.
  • - Treat messages as public contracts with versioning pressure.
  • - Keep NestJS transport adapters thin and test business logic without a broker.
  • - Design retries, dead-letter behavior, and observability before traffic arrives.

Kafka, RabbitMQ, HTTP, and gRPC are implementation details compared with the harder question: where should the boundary exist? A microservice boundary should match ownership, data responsibility, deployment risk, or a clear scaling need. If the boundary only mirrors a folder name, it adds network complexity without buying independence.

Start with ownership

Before introducing a message broker, I define what the service owns. Does it own the database table? Does it own the business rule? Can another team change it safely? What happens if it is down? These questions expose whether the service is a real boundary or just a remote function call.

Treat messages as contracts

A Kafka topic or queue message is a public API. Consumers may deploy at different times, retry old messages, or receive events produced by a previous version. Message names, required fields, optional fields, and versioning strategy matter. I prefer explicit event types and payloads that describe facts, not commands pretending to be facts.

  • Good event: OrderPaid, TicketAssigned, ProfileUpdated.
  • Risky event: DoEverythingForCheckout.
  • Required behavior: idempotent consumers and safe retries.
  • Required visibility: logs with message id, consumer, attempt, and outcome.

Keep NestJS modules honest

NestJS makes it easy to build clean modules, but module boundaries are only useful if dependencies stay directional. A billing module importing support internals is a design smell. In a microservice setup, I keep transport adapters thin and put business rules in services that can be tested without a broker.

Plan for failure before traffic

Distributed systems fail in ordinary ways: duplicate messages, late messages, missing dependencies, partial writes, schema drift, and poison payloads. A scalable system needs retry limits, dead-letter handling, backoff, health checks, and dashboards before it needs clever abstractions.

The best microservice work is usually conservative. Split only where independence is real, keep contracts small, make consumers idempotent, and make operational state visible. NestJS gives a productive structure, but the architecture comes from the boundaries and failure behavior, not from the decorator syntax.

Consumers should be idempotent and explicit about retryable work.
@MessagePattern("invoice.created")
async handleInvoiceCreated(event: InvoiceCreatedEvent) {
  const exists = await this.inbox.seen(event.id);
  if (exists) return;

  await this.billing.applyInvoice(event);
  await this.inbox.markSeen(event.id);
}

Who may find it useful

NestJS teams splitting services or adding Kafka/RabbitMQ to an existing backend

Topics

NestJS microservicesKafka topicsIdempotent consumersDead-letter queues