typescript
45 lines · 8 steps
Manual RabbitMQ acks in a NestJS controller
A NestJS microservice controller that handles RPC messages over RabbitMQ and acknowledges them by hand.
Explained by
highlit
1import { Controller } from '@nestjs/common';
2import {
3 MessagePattern,
4 Payload,
5 Ctx,
6 RmqContext,
7 RpcException,
8} from '@nestjs/microservices';
9import { OrdersService } from './orders.service';
10import { CreateOrderDto } from './dto/create-order.dto';
11
12@Controller()
13export class OrdersController {
14 constructor(private readonly ordersService: OrdersService) {}
15
16 @MessagePattern({ cmd: 'create_order' })
17 async createOrder(
18 @Payload() dto: CreateOrderDto,
19 @Ctx() context: RmqContext,
20 ) {
21 const channel = context.getChannelRef();
22 const originalMsg = context.getMessage();
23
24 try {
25 const order = await this.ordersService.create(dto);
26 channel.ack(originalMsg);
27 return { id: order.id, status: order.status, total: order.total };
28 } catch (err) {
29 channel.nack(originalMsg, false, false);
30 throw new RpcException({
31 code: 'ORDER_CREATION_FAILED',
32 message: err instanceof Error ? err.message : 'Unknown error',
33 });
34 }
35 }
36
37 @MessagePattern({ cmd: 'get_order' })
38 async getOrder(@Payload('id') id: string) {
39 const order = await this.ordersService.findById(id);
40 if (!order) {
41 throw new RpcException({ code: 'NOT_FOUND', message: `Order ${id} not found` });
42 }
43 return order;
44 }
45}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Manual acknowledgement lets you commit or reject a message only after your business logic succeeds or fails.
- 2Nacking with requeue set to false routes poison messages to a dead-letter queue instead of looping forever.
- 3RpcException carries structured error data back to the caller across the microservice transport.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
typescript
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi';
Validating env config at boot in NestJS
configuration
schema-validation
environment-variables
Intermediate
8 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { InjectRepository } from '@nestjs/typeorm';
A cache-aside country lookup in NestJS
cache-aside
dependency-injection
batch-lookup
Intermediate
8 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core'; interface Preferences { theme: 'light' | 'dark';
A signal-based preferences store in Angular
signals
state-management
persistence
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/manual-rabbitmq-acks-in-a-nestjs-controller-explained-typescript-dba8/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.