Skip to content

The Consumer API

A Kafka consumer subscribes to topics and then runs a consume loop — KafkaJS calls your eachMessage handler with records from the partitions assigned to it, and that running loop is what keeps the consumer alive in its group.

You create a consumer from a Kafka client with a groupId, connect(), subscribe() to one or more topics, and start run(). KafkaJS hands each record to your eachMessage callback. The key and value arrive as raw Buffers — you deserialize them yourself (e.g. .toString() or JSON.parse), the mirror of the producer serializing.

import { Kafka } from 'kafkajs';
const kafka = new Kafka({ clientId: 'order-processor', brokers: ['localhost:9092'] });
const consumer = kafka.consumer({ groupId: 'order-processor' }); // consumer group membership
async function main() {
await consumer.connect();
await consumer.subscribe({ topic: 'orders', fromBeginning: false }); // false = only new records (latest)
// run() drives the poll loop for you and calls eachMessage per record
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
// key/value are Buffers — deserialize them yourself
console.log(
`p=${partition} off=${message.offset} key=${message.key?.toString()} val=${message.value?.toString()}`,
);
},
});
}
main();

run() does far more than fetch data. Under the hood it polls Kafka, sends heartbeats, participates in group coordination, and fetches partition assignments. If your eachMessage blocks too long — processing a record takes longer than the session timeout — the group coordinator assumes the consumer is dead and triggers a rebalance, reassigning its partitions elsewhere. The rule: keep per-message processing bounded.

flowchart LR
  sub["subscribe(topics)"] --> run["consumer.run(...)"]
  run --> each["eachMessage per record"]
  each --> proc["process the record"]
  proc --> run
  run -->|also| hb["send heartbeats + coordinate group"]
The consumer run loop

Every message exposes its topic, partition, offset, key, value, timestamp, and headers. Those coordinates are what you use to commit progress and to reason about ordering — the consumer sees records in offset order within each partition, though records from different partitions may interleave.

What does consumer.run() (the poll loop) do besides returning records?
What happens if a consumer stops consuming (its handler blocks) for too long?
How do you get a typed key or value from a KafkaJS message?
In what order does a consumer see records?