Topic Exchange
Routing key: Dot-separated words matched against binding pattern with * (one word) and # (zero or more words) wildcards
A topic exchange routes messages by matching the routing key against binding patterns using wildcards. * matches exactly one dot-separated word. # matches zero or more words. 'order.created.eu.de' matches 'order.created.#', 'order.*.eu.*', and '#'. Topic exchanges enable flexible selective subscriptions without changing publishers.
How it works
Topic exchanges are the most flexible routing type. They combine the directness of direct exchange with wildcard pattern matching.
Routing key format: dot-separated words, e.g., 'stock.usd.nyse', 'order.created.eu.de', 'user.logged_in'. Each dot-separated segment is a 'word'.
Binding pattern wildcards: * (star): matches exactly one word. 'order.*.eu' matches 'order.created.eu' but not 'order.eu' or 'order.created.updated.eu' # (hash): matches zero or more words. 'order.#' matches 'order', 'order.created', 'order.created.eu.de' '#' alone matches everything (equivalent to fanout)
Special cases: '#.*' matches any routing key with at least one word 'a.b.c' (no wildcards) behaves like a direct exchange binding
Pattern priority: if multiple bindings match, the message is delivered to all matching queues (not just the most specific match).
Design tip: define a consistent routing key taxonomy at the start. Changing routing key conventions after deployment requires coordinated queue rebinding. Convention: [noun].[verb].[qualifier...]
Use Cases
- →
Log routing – 'kern.*' for kernel logs, '*.critical' for all critical logs
- →
Multi-region event routing – 'order.created.eu.*' for EU orders
- →
Selective service subscriptions – payment service subscribes to 'payment.#', shipping to 'order.shipped.#'
- →
Dynamic feature flag events – 'feature.flag.enabled.*'
Examples
await ch.assertExchange('logs', 'topic', { durable: true });
// Critical logs queue – matches any facility, critical severity
await ch.bindQueue('logs.critical', 'logs', '*.critical');
// Kernel all-severities queue
await ch.bindQueue('logs.kernel', 'logs', 'kern.*');
// All logs queue
await ch.bindQueue('logs.all', 'logs', '#');
// Publish: routing key = facility.severity
ch.publish('logs', 'kern.critical', Buffer.from('Kernel panic!'));
// → delivered to logs.critical, logs.kernel, logs.all
ch.publish('logs', 'auth.info', Buffer.from('User login'));
// → delivered to logs.all onlyawait ch.assertExchange('orders', 'topic', { durable: true });
// EU payment team subscribes to EU orders
await ch.bindQueue('orders.eu.payments', 'orders', 'order.*.eu.#');
// All new orders for inventory check
await ch.bindQueue('orders.inventory', 'orders', 'order.created.#');
// Global cancellation handler
await ch.bindQueue('orders.cancelled', 'orders', 'order.cancelled.*');
ch.publish('orders', 'order.created.eu.de', payload);