NestJS ships with first-class microservice support, but the documentation glosses over the operational complexity of running message-based services at scale. Here are the patterns that actually matter in production.
Choose your transport layer based on delivery semantics, not familiarity. Redis Pub/Sub is fire-and-forget — no persistence, no acknowledgement, no replay. It's appropriate for ephemeral notifications: cache invalidation signals, real-time UI events, health pings. RabbitMQ gives you durable queues, dead-letter exchanges, and acknowledgement-based delivery. Use it for anything that must not be lost: order processing, billing events, email triggers. Mixing the two in the same system is common and correct.
The @MessagePattern decorator works fine for request-reply patterns, but most inter-service communication should be @EventPattern — fire and forget, no response expected. If service A needs a response from service B, that's often a sign the services are too tightly coupled. Consider whether A can subscribe to an event that B emits when work is done, rather than blocking on a synchronous reply.
Dead-letter queues are not optional. Every queue that processes business-critical messages needs a corresponding dead-letter exchange configured. When a message fails processing after N retries, it moves to the DLQ rather than being dropped. Set up an alert on DLQ depth — non-zero means something is failing silently. Pair this with structured logging: every message handler should log the message ID, processing result, and duration so you can trace failures.
Redis is excellent for shared ephemeral state between service instances. Common uses: distributed locks (prevent duplicate processing of the same order), session caching (avoid hitting the database on every authenticated request), and rate limiting (sliding window counters per user/IP). Use the ioredis client with NestJS's CacheModule for caching, and a dedicated lock library like redlock for distributed locking.
For local development, resist the temptation to mock queues. Run RabbitMQ and Redis via Docker Compose. Mocking message transport hides entire categories of bugs: serialization issues, routing key mismatches, DLQ configuration errors. The Docker Compose overhead is minimal and the parity with production is worth it.