If you’re building any real backend, you’ll eventually need services to talk to each other without waiting around. That’s where message brokers come in. They let one part of your system send a message and move on, while another part picks it up whenever it’s ready.

The problem is there are too many options. Redis, NATS, Kafka - they all do similar things but in very different ways. Picking the wrong one can make your life miserable later. So let me break it down simply.


What Each One Actually Is

Redis

Redis is an in-memory data store that also happens to be a decent message broker. It started as a key-value cache, but over time people started using it for pub/sub and job queues because it’s fast and simple.

Think of Redis as a Swiss army knife. It does a lot of things pretty well, but it’s not specialized for any single thing.

NATS

NATS is built specifically for messaging. It’s lightweight, fast, and designed for microservices. If Redis is a Swiss army knife, NATS is a scalpel - it does one thing really well.

NATS is super popular in IoT and cloud-native systems because it’s tiny, fast, and handles distributed systems really nicely.

Kafka

Kafka is the heavy-duty option. It’s built for streaming massive amounts of data. Think log processing, event sourcing, analytics pipelines - things where you need to keep every message forever and replay it later.

Kafka is like a giant conveyor belt. Stuff goes in one end, gets stored, and can be picked up by multiple consumers whenever they want.


Quick Comparison

Feature Redis NATS Kafka
Speed Very fast Very fast Fast (batch optimized)
Persistence Optional (RDB/AOF) Optional (JetStream) Yes (disk-based)
Message replay No No (unless JetStream) Yes
Complexity Low Low High
Best for Caching, simple queues Microservices, IoT Event sourcing, streaming

When to Use Redis

Redis is great when you want something simple and fast. Here are the cases where I reach for Redis:

Job queues: Using something like BullMQ, you can add jobs to a queue and process them in the background. It’s simple to set up and handles retries, delays, and priority queues out of the box.

// Publishing a job with Redis
rdb.LPush(ctx, "email-queue", "send-welcome-email:user123")

Pub/Sub for real-time features: If you just need to broadcast events to multiple subscribers (like a chat app or live notifications), Redis pub/sub works fine. It’s fire-and-forget, which is exactly what you want for things like “user just came online” or “new comment posted”.

Rate limiting: Since Redis is in-memory and super fast, it’s perfect for counting requests and enforcing rate limits.

Caching: The obvious one. Cache database queries, API responses, anything that’s expensive to compute.

The downside? Redis keeps everything in memory. If you have a lot of data, your RAM bill goes up fast. And if you need message replay or complex streaming patterns, Redis isn’t built for that.


When to Use NATS

NATS shines when you have a bunch of microservices that need to talk to each other. Here’s why I like it:

It’s incredibly lightweight: The NATS server binary is tiny. You can run it in a container with almost no resources. This makes it perfect for edge computing and IoT.

Subject-based messaging: Instead of sending to specific queues, you publish to subjects like orders.created or users.updated. Subscribers listen to patterns, so orders.* gets everything about orders.

// Publishing
nc.Publish("orders.created", []byte(`{"order_id": "123"}`))

// Subscribing
nc.Subscribe("orders.*", func(msg *nats.Msg) {
    fmt.Printf("Got: %s\n", string(msg.Data))
})

Request-reply made easy: NATS has built-in request-reply patterns. One service asks a question, another answers. No need to set up separate queues for this.

It just works: Seriously, NATS is boring in the best way possible. You don’t need to configure a million things. Start the server, connect, publish, subscribe. Done.

The downside? NATS doesn’t persist messages by default (unless you use JetStream). If a subscriber is down when a message is published, that message is gone. So it’s not great for things where you can’t afford to lose data.


When to Use Kafka

Kafka is the nuclear option. It’s complex, heavy, and takes effort to set up. But when you need it, nothing else works. Here’s when to reach for Kafka:

Event sourcing: When you need a complete history of everything that happened in your system. Every order, every click, every state change - all stored forever and searchable.

Log aggregation: Collecting logs from hundreds of services and analyzing them later. Kafka can handle millions of messages per second.

Stream processing: When you need to do real-time analytics on data as it flows through. Things like “count how many orders came in the last 5 minutes” or “detect fraud patterns in real-time.”

// Kafka producer
writer := kafka.NewWriter(kafka.WriterConfig{
    Brokers:  []string{"localhost:9092"},
    Topic:    "orders",
    Balancer: &kafka.LeastBytes{},
})

writer.WriteMessages(ctx,
    kafka.Message{
        Key:   []byte("order-123"),
        Value: []byte(`{"total": 99.99}`),
    },
)

Multiple consumers need the same data: This is Kafka’s killer feature. One producer sends a message, and multiple different services can all read that same message independently, at their own pace. The analytics team and the notification team can both process the same order event without interfering with each other.

The downside? Kafka is a beast to operate. You need ZooKeeper (or KRaft), monitoring, proper partitioning strategy, and enough disk space. It’s overkill for 90% of backend apps.


How They Work Together

The best part is you don’t have to pick just one. A lot of systems use multiple brokers for different things:

For example, in a typical e-commerce app:

They’re not competing - they’re solving different problems.


Key Takeaways

  1. Start simple. If you just need a job queue or pub/sub, Redis is probably enough. Don’t add Kafka because “maybe we’ll need it later.”
  2. Use NATS for microservices. If you have a bunch of services talking to each other, NATS is purpose-built for that. It’s fast, simple, and lightweight.
  3. Reserve Kafka for streaming. Only use Kafka when you need message replay, event sourcing, or massive throughput. It’s powerful but complex.
  4. Mix and match. You can use Redis for caching, NATS for messaging, and Kafka for streaming. They play well together.

References