Building a Pub/Sub Decorator for NestJS with Dapr

This guide walks through installing the Dapr JavaScript SDK, configuring a RabbitMQ pub/sub component, injecting DaprClient and DaprServer into a NestJS project, creating a custom @DaprPubSubSubscribe decorator, and running a complete end‑to‑end demo that publishes and consumes page‑view events.

Hacker Afternoon Tea
Hacker Afternoon Tea
Hacker Afternoon Tea
Building a Pub/Sub Decorator for NestJS with Dapr

Dapr is a portable, event‑driven runtime that enables developers to build resilient stateless or stateful applications for cloud or edge environments without handling distributed‑system complexities.

Install the Dapr JavaScript SDK

Use npm to add the SDK package:

npm install --save @dapr/dapr
⚠️ The dapr-client package is deprecated; see https://github.com/dapr/js-sdk/issues/259 for details.

Project setup

Create a new NestJS application and install the Dapr client:

npm install -g @nestjs/cli
nest new api
mv api nest-dapr
cd nest-dapr
nest generate app page-view
npm install dapr-client
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash

Inject Dapr dependencies

Register DaprClient in app.module.ts and DaprServer in page-view.module.ts:

providers: [
  ...,
  { provide: DaprClient, useValue: new DaprClient() }
]
providers: [
  ...,
  { provide: DaprServer, useValue: new DaprServer() }
]

Configure RabbitMQ as a Dapr pub/sub component

Start RabbitMQ with Docker Compose:

version: '3.9'
services:
  pubsub:
    image: rabbitmq:3-management-alpine
    container_name: 'pubsub'
    ports:
      - 5674:5672
      - 15674:15672 # web port

Create component/pubsub.yml to define the component:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: pubsub
  namespace: default
spec:
  type: pubsub.rabbitmq
  version: v1
  metadata:
    - name: host
      value: 'amqp://guest:guest@localhost:5674'

Each Dapr microservice also needs its own configuration file (e.g., api/dapr/config.yml and page-view/dapr/config.yml).

API/Gateway service

Expose an endpoint that publishes a page‑view event:

@Post('/add-page-view')
async prderAdd(@Body() pageViewDto: PageViewDto): Promise<void> {
  try {
    console.log(pageViewDto);
    await this.daprClient.pubsub.publish('pubsub', 'page-view-add', pageViewDto);
  } catch (e) {
    console.log(e);
  }
}

Internal listener microservice

Add a method decorated with the custom @DaprPubSubSubscribe decorator to handle the published message:

@DaprPubSubSubscribe('pubsub', 'add')
addPageView(data: PageViewDto) {
  console.log(`addPageView executed with data: ${JSON.stringify(data)}`);
  this.data.push(data);
}

Implementing the @DaprPubSubSubscribe decorator

In shared/decorators.ts define a map to store subscription metadata and a factory function that registers each subscription with the Dapr server at runtime:

import { INestApplication } from '@nestjs/common';
import { DaprServer } from 'dapr-client';

export type PubsubMap = {
  [pubSubName: string]: {
    topic: string;
    target: any;
    descriptor: PropertyDescriptor;
  };
};
export const DAPR_PUB_SUB_MAP: PubsubMap = {};
export const DaprPubSubSubscribe = (
  pubSubName: string,
  topic: string,
): MethodDecorator => {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    DAPR_PUB_SUB_MAP[pubSubName] = { topic, target, descriptor };
    return descriptor;
  };
};

export const useDaprPubSubListener = async (app: INestApplication) => {
  const daprServer = app.get(DaprServer);
  for (const pubSubName in DAPR_PUB_SUB_MAP) {
    const item = DAPR_PUB_SUB_MAP[pubSubName];
    console.log(`Listening to the pubsub name - "${pubSubName}" on topic "${item.topic}"`);
    await daprServer.pubsub.subscribe(
      pubSubName,
      item.topic,
      async (data: any) => {
        const targetClassImpl = app.get(item.target.constructor);
        await targetClassImpl[item.descriptor.value.name](data);
      },
    );
  }
};

Run the demo

Start RabbitMQ, the API service, and the page‑view listener:

docker-compose up -d   # start rabbitmq
npm run dapr:api:dev   # start api/gateway
npm run page-view:dev   # start internal microservice listening to the rabbitmq queue

Send a test request:

curl --location --request POST 'http://localhost:7001/v1.0/invoke/api/method/statistics/add-page-view' \
--header 'Content-Type: application/json' \
--data-raw '{
    "pageUrl" : "https://test.com/some-page",
    "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
}'

The request triggers the API endpoint, which publishes a message to the Dapr pub/sub component; the custom decorator registers a listener that receives the message and stores the page‑view data, demonstrating a complete end‑to‑end Pub/Sub workflow in a NestJS application powered by Dapr.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

cloud nativerabbitmqdecoratorjavascript-sdkNestJSdaprpubsub
Hacker Afternoon Tea
Written by

Hacker Afternoon Tea

You might find something interesting here ^_^

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.