Direct Exchange
Exact-match routing
Section titled “Exact-match routing”A direct exchange routes a message to the queues whose binding key exactly equals the message’s routing key. No patterns, no wildcards — a plain string equality check. It is the simplest exchange type and the one you reach for when a message has a single, well-defined destination category.
You already used a direct exchange without naming it: the default exchange is a nameless direct exchange that every queue is automatically bound to by its own name. That’s why publishing to routing key hello with an empty exchange string lands in the queue named hello.
A worked example: logs by severity
Section titled “A worked example: logs by severity”Say you emit log messages and want error logs in one queue (for alerting) and info logs in another (for archiving). Declare a direct exchange, bind each queue with its severity as the binding key, and publish with the severity as the routing key.
flowchart LR p["publish key: error"] --> x["direct exchange 'logs'"] x -->|"bind key: error"| qe["queue: errors"] x -->|"bind key: info"| qi["queue: info"] x -. "no match for 'error'" .-> qi
A message published with routing key error matches only the binding whose key is error, so it goes to the errors queue and not the info queue.
const ex = 'logs';await channel.assertExchange(ex, 'direct', { durable: true });
// consumer side: bind a queue for errors onlyconst q = await channel.assertQueue('errors', { durable: true });await channel.bindQueue(q.queue, ex, 'error');
// producer side: publish with the severity as the routing keychannel.publish(ex, 'error', Buffer.from('disk full on node-3'));channel.publish(ex, 'info', Buffer.from('user logged in'));ex = "logs"channel.exchange_declare(exchange=ex, exchange_type="direct", durable=True)
# consumer side: bind a queue for errors onlychannel.queue_declare(queue="errors", durable=True)channel.queue_bind(queue="errors", exchange=ex, routing_key="error")
# producer side: publish with the severity as the routing keychannel.basic_publish(exchange=ex, routing_key="error", body="disk full on node-3")channel.basic_publish(exchange=ex, routing_key="info", body="user logged in")ex := "logs"ch.ExchangeDeclare(ex, "direct", true, false, false, false, nil)
// consumer side: bind a queue for errors onlych.QueueDeclare("errors", true, false, false, false, nil)ch.QueueBind("errors", "error", ex, false, nil)
// producer side: publish with the severity as the routing keych.PublishWithContext(ctx, ex, "error", false, false, amqp.Publishing{Body: []byte("disk full on node-3")})ch.PublishWithContext(ctx, ex, "info", false, false, amqp.Publishing{Body: []byte("user logged in")})Many bindings, same key
Section titled “Many bindings, same key”A direct exchange isn’t limited to one queue per key. If two queues both bind with key error, a single error message is copied to both — so you can combine exact routing with a bit of fan-out. Likewise, one queue can bind multiple keys (bind errors to both error and critical) to collect several categories.