<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eduardo Conti</title>
    <description>The latest articles on DEV Community by Eduardo Conti (@eduardoconti).</description>
    <link>https://dev.to/eduardoconti</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1246805%2F2a655bed-9c86-431b-8e8e-5a48cb9da618.jpeg</url>
      <title>DEV Community: Eduardo Conti</title>
      <link>https://dev.to/eduardoconti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eduardoconti"/>
    <language>en</language>
    <item>
      <title>Ensuring Idempotence: A Guide to Implementing Idempotent Endpoints with NestJS Interceptors</title>
      <dc:creator>Eduardo Conti</dc:creator>
      <pubDate>Thu, 11 Jan 2024 04:30:51 +0000</pubDate>
      <link>https://dev.to/eduardoconti/ensuring-idempotence-a-guide-to-implementing-idempotent-endpoints-with-nestjs-interceptors-lgb</link>
      <guid>https://dev.to/eduardoconti/ensuring-idempotence-a-guide-to-implementing-idempotent-endpoints-with-nestjs-interceptors-lgb</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Idempotence refers to the property of an operation where applying it multiple times has the same result as applying it once. In the context of APIs, an idempotent operation ensures that making the same request multiple times produces the same outcome as making it once.&lt;br&gt;
Idempotence is related to the effect that an operation has when executed multiple times, and this varies depending on the HTTP method used. Let's analyze the GET and POST methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GET:&lt;/strong&gt;&lt;br&gt;
Idempotent: The read operation does not change the server state. Requesting the same data multiple times does not cause changes to the server. Therefore, the GET method is naturally idempotent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;POST:&lt;/strong&gt;&lt;br&gt;
Non-idempotent: The operation of creating or sending data via POST usually changes the server state, creating a new resource. Each POST request can create a new resource or have different effects, so it is not idempotent.&lt;/p&gt;

&lt;p&gt;The main distinction is that idempotent operations can be repeated without causing different side effects, while non-idempotent operations can lead to different results with each execution. This is crucial to ensure consistency and predictability in API design and server-side data manipulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Do I Need Idempotency?
&lt;/h2&gt;

&lt;p&gt;Imagine you're managing an online booking system for concert tickets, and your application allows users to reserve tickets for their favorite band's upcoming show. Here's the situation:&lt;/p&gt;

&lt;p&gt;A user initiates a ticket reservation by sending an HTTP POST request to the server, specifying the number of tickets and other relevant details.&lt;/p&gt;

&lt;p&gt;The server receives the request and successfully reserves the tickets for the user, updating the database accordingly.&lt;/p&gt;

&lt;p&gt;However, due to a temporary network issue or a sudden app crash, the client does not receive the successful response from the server.&lt;/p&gt;

&lt;p&gt;The user, unaware that their reservation was successful, assumes it failed and decides to retry the reservation by sending another identical HTTP POST request.&lt;/p&gt;

&lt;p&gt;The server processes the second request, not recognizing that it's a duplicate, and reserves the same set of tickets again.&lt;/p&gt;

&lt;p&gt;Now, when the user checks their reservation history, they are surprised to see two identical reservations, and they have been charged for both sets of tickets.&lt;/p&gt;

&lt;p&gt;This undesirable situation occurred because the system lacked idempotence. Ideally, an idempotent system would recognize that the second reservation request is a duplicate and would respond with a status indicating that the request has already been processed or the same response of first request. This way, the user wouldn't inadvertently end up with multiple reservations and duplicate charges.&lt;/p&gt;

&lt;p&gt;Implementing idempotence in this scenario would ensure that even if the client retries the reservation due to a lack of response, the server would handle it safely, preventing unintended consequences such as duplicate reservations and charges.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwytz5i58meif98ol3sa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwytz5i58meif98ol3sa.png" alt="Scenario"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;To implement an idempotent endpoint, I will use an API that I recently developed for this article: &lt;a href="https://dev.to/eduardoconti/nestjs-with-rabbitmq-in-a-monorepo-building-a-scalable-credit-card-payment-system-with-decoupled-api-and-consumers-58bb"&gt;NestJS with RabbitMQ in a Monorepo: Building a Scalable Credit Card Payment System with Decoupled API and Consumers&lt;/a&gt;. As we are dealing with credit card charges, we don't want to risk duplicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Go To The Implementation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why Am I Using an NestJS Interceptor?&lt;/strong&gt;&lt;br&gt;
Because they make it possible to bind extra logic before / after method execution.&lt;br&gt;
In this approach, I will initially pre-save the idempotence entity. Subsequently, after the controller execution, I will update it with the response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1fs1ccyyx0jwz86lmvt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1fs1ccyyx0jwz86lmvt.png" alt="Sequence"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create class IdempotencyKeyInterceptor&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  BadRequestException,
  Inject,
} from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { IdempotencyRepository } from '../repositories';
import { IIdempotencyRepository } from '@api/idempotency/domain/repositories';

@Injectable()
export class IdempotencyKeyInterceptor implements NestInterceptor {
  constructor(
    @Inject(IdempotencyRepository)
    private readonly idempotencyRepository: IIdempotencyRepository,
  ) {}

  async intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Promise&amp;lt;Observable&amp;lt;any&amp;gt;&amp;gt; {
    const ctx = context.switchToHttp();
    const request = ctx.getRequest&amp;lt;Request&amp;gt;();
    const idempotencyKey = request.headers['x-idempotency-key'];

    if (!idempotencyKey) {
      throw new BadRequestException(
        "Header 'x-idempotency-key' is required for this request.",
      );
    }

    if (!this.isValidUUID(idempotencyKey)) {
      throw new BadRequestException(
        "Header 'x-idempotency-key' must be a UUID.",
      );
    }

    const idempotencyModel = await this.idempotencyRepository.find(
      idempotencyKey,
    );

    if (idempotencyModel) {
      return of(idempotencyModel.response);
    }

    await this.idempotencyRepository.preSave(idempotencyKey);

    return next.handle().pipe(
      tap(async (data) =&amp;gt; {
        await this.idempotencyRepository.update(idempotencyKey, data);
        return data;
      }),
    );
  }

  private isValidUUID(uuid: string) {
    const uuidRegex =
      /(?:^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}$)|(?:^0{8}-0{4}-0{4}-0{4}-0{12}$)/u;
    return uuidRegex.test(uuid);
  }
}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dependencies Injection:&lt;/strong&gt;&lt;br&gt;
The class constructor injects an implementation of the IIdempotencyRepository interface through the IdempotencyRepository. This is a clear example of dependency injection, allowing for the flexibility to swap out different implementations of the repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interception Logic:&lt;/strong&gt;&lt;br&gt;
The intercept method is the core logic of the interceptor. It checks for the presence of an 'x-idempotency-key' header in the incoming request. If not present, it throws a BadRequestException. It also ensures that the idempotency key is a valid UUID.&lt;br&gt;
It then queries the IdempotencyRepository to check if there's an existing entry for the idempotency key. If found, it short-circuits the request by returning the stored response.&lt;br&gt;
If no existing entry is found, it pre-saves the idempotency key using the repository before allowing the request to proceed to the controller.&lt;br&gt;
After the controller execution, it updates the idempotency entry with the actual response from the controller.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Idempotency Repository Usage:&lt;/strong&gt;&lt;br&gt;
The class utilizes methods from the injected IdempotencyRepository to interact with the underlying storage. This repository handles tasks such as finding, pre-saving, and updating idempotency entries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dependency Inversion:&lt;/strong&gt;&lt;br&gt;
By injecting the IdempotencyRepository interface, the class adheres to the dependency inversion principle. This design pattern allows for a more modular and testable codebase, where the specific implementation details of the repository can be swapped out without affecting the functionality of the interceptor.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;you can see the creation and implementation of this interceptor and the repository (in memory) in this &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/99b18ccf1619e26df95ffd0f754b7761c98e14cf" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Be Careful
&lt;/h2&gt;

&lt;p&gt;Please exercise caution, as I haven't applied this approach in a production environment. If you identify any issues with this implementation or have suggestions for enhancement, I welcome and appreciate your valuable insights. Feel free to share your opinions and recommendations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In the pursuit of ensuring idempotence in API endpoints, this article navigated through the fundamental concepts, explored the distinctions between idempotent and non-idempotent operations, and delved into the importance of maintaining consistency and predictability in API design. Drawing attention to the potential pitfalls of lacking idempotence, a real-world scenario illuminated the need for robust solutions.&lt;/p&gt;

&lt;p&gt;The journey continued with a practical implementation using NestJS Interceptors, showcasing the development of an IdempotencyKeyInterceptor. Leveraging the principles of dependency inversion, the interceptor seamlessly integrated with an Idempotency Repository, providing a modular and testable solution.&lt;/p&gt;

&lt;p&gt;The detailed breakdown of the interceptor's logic, from dependency injection to interception strategy, highlighted its role in handling idempotency gracefully. The careful design allowed for pre-saving idempotent entities and updating them post-controller execution, introducing an effective approach to address the challenges posed by non-idempotent scenarios.&lt;/p&gt;

&lt;p&gt;As we embark on implementing idempotent endpoints, the cautionary note underscores the importance of prudence. The class, though well-architected and designed, has not been battle-tested in a production setting. It beckons the community to scrutinize and contribute, welcoming opinions, identifying potential pitfalls, and offering suggestions for refinement.&lt;/p&gt;

&lt;p&gt;In the spirit of collaborative improvement, this article encourages readers to share their experiences and insights, fostering a collective endeavor towards more robust, reliable, and idempotent API solutions. With a foundation laid in understanding, implementation, and community collaboration, the journey towards ensuring idempotence is well underway.&lt;/p&gt;

&lt;p&gt;repo: &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example" rel="noopener noreferrer"&gt;nestjs-rabitmq-example&lt;/a&gt;&lt;br&gt;
links: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html#name-idempotent-methods" rel="noopener noreferrer"&gt;rfc9110-idempotent-methods&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.nestjs.com/interceptors" rel="noopener noreferrer"&gt;NestJS Interceptors&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://rxjs.dev/api/operators/tap" rel="noopener noreferrer"&gt;Rxjs: tap&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://rxjs.dev/api/index/function/of" rel="noopener noreferrer"&gt;Rxjs: of&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>NestJS with RabbitMQ in a Monorepo: Building a Scalable Credit Card Payment System with Decoupled API and Consumers</title>
      <dc:creator>Eduardo Conti</dc:creator>
      <pubDate>Sun, 07 Jan 2024 18:17:37 +0000</pubDate>
      <link>https://dev.to/eduardoconti/nestjs-with-rabbitmq-in-a-monorepo-building-a-scalable-credit-card-payment-system-with-decoupled-api-and-consumers-58bb</link>
      <guid>https://dev.to/eduardoconti/nestjs-with-rabbitmq-in-a-monorepo-building-a-scalable-credit-card-payment-system-with-decoupled-api-and-consumers-58bb</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this article, we will explore the creation of a credit card payment application using NestJS and RabbitMQ to handle billing generation with a Payment Service Provider (PSP). Additionally, we will incorporate Docker and Docker Compose to streamline container management.&lt;br&gt;
Initially, we will create an endpoint simulating the billing creation process, performing a database insert and an HTTP request. We will observe that the response time for this endpoint is unacceptably high, around 1100 ms. Next, we will introduce a message broker service to asynchronously process the HTTP request for billing generation, resulting in a significant reduction in the endpoint's response time.&lt;br&gt;
To conclude, we will address the separation of the consumer from the API in a monorepo, enabling both to scale independently. I want to highlight that I developed this project using the Clean Architecture, but you have the flexibility to choose the architecture that best suits your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should I use asynchronous processing and RabbitMQ?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asynchronous Processing for Improved Performance&lt;/strong&gt;:&lt;br&gt;
    The introduction reveals that the initial response time for the billing creation process is unacceptably high, around 1100 ms. Introducing a message broker allows you to shift to asynchronous processing, significantly reducing the endpoint's response time. RabbitMQ excels in handling asynchronous communication, enabling your application to scale more efficiently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Scalability and Responsiveness&lt;/strong&gt;:&lt;br&gt;
    By leveraging RabbitMQ, you can decouple the billing generation process from the API endpoint. This decoupling facilitates improved scalability, as both the API and the billing generation service can scale independently. This flexibility ensures that your application remains responsive, even under increased load.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reliable Message Delivery&lt;/strong&gt;:&lt;br&gt;
    RabbitMQ provides reliable message delivery mechanisms, ensuring that messages are successfully delivered even in the event of system failures or network issues. This reliability is crucial in financial applications like credit card payment processing, where data integrity and consistency are paramount.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary, utilizing RabbitMQ in conjunction with NestJS for a credit card payment application brings tangible benefits such as improved performance, scalability, reliability, and flexibility, making it a well-rounded choice for handling asynchronous communication and optimizing your application's overall architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start new nestjs app
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;$ nest new nestjs-rabbitmq-example&lt;/code&gt; &lt;br&gt;
&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/197cdfda14b31a34b8a1b9d87e6da00a35d52e6e" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After installation, I removed app.controller.ts and app.service.ts &lt;br&gt;
&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/616ff73936f7f2415a7529c91d62dac85ca9c015" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Include entity, controller, and services to simulate the creation of a charge using a payment service provider such as Pagarme
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;$ nest g module credit-card&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can see the classes created in this&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/6233d66380eb6153a12181ebf0f4adaf06101d8b" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mn3lqbmp9xenvpphg5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mn3lqbmp9xenvpphg5z.png" alt="folder structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have an endpoint that &lt;strong&gt;simulates&lt;/strong&gt; the creation of a credit card charge, as if making an insertion in the database (100ms) and then an http request to &lt;em&gt;pagarme&lt;/em&gt; (1000ms).&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80ur3weg042oegvdxfxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80ur3weg042oegvdxfxa.png" alt="postman"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this scenario, the client is not required to linger on the HTTP request, awaiting a response from Pagarme; we can efficiently handle this process asynchronously. To achieve this, we will leverage &lt;strong&gt;RabbitMQ&lt;/strong&gt; to queue the creation requests for charges.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Add the necessary libs to implement rabbitmq
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;$ yarn add @nestjs/microservices amqplib amqp-connection-manager&lt;/code&gt; &lt;br&gt;
&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/6ac0d9b411a9b2505f0fb4dfffcfb23cd5142855" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Dockerize application
&lt;/h2&gt;

&lt;p&gt;Dockerfile&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:16

WORKDIR /usr/src/app
COPY package*.json ./

RUN yarn
COPY . .

RUN yarn build

EXPOSE 3000
CMD [ "yarn", "start:prod" ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;docker-compose.yml&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.7'

services:
  credit-card-api:
    container_name: credit-card-api
    restart: on-failure
    build:
      context: .
    volumes:
      - .:/usr/src/app
    ports:
      - 3000:3000
    command: yarn start:dev
    depends_on:
      - rabbitmq

  rabbitmq:
    image: rabbitmq:3.9-management
    container_name: rabbitmq
    restart: always
    hostname: rabbitmq
    ports:
      - 5672:5672  
      - 15672:15672  
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq  

volumes:
  rabbitmq_data:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/0d3b5bddf812e3cd36194585554cadbae8bb2ee2" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Connect application with rmq
&lt;/h2&gt;

&lt;p&gt;update file main.ts to use  &lt;em&gt;app.connectMicroservice()&lt;/em&gt; and &lt;em&gt;app.startAllMicroservices()&lt;/em&gt;&lt;br&gt;
I'm not going to delve into rabbitmq settings.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { RmqOptions, Transport } from '@nestjs/microservices';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.connectMicroservice&amp;lt;RmqOptions&amp;gt;({
    transport: Transport.RMQ,
    options: {
      urls: [`amqp://rabbitmq:5672`],
      queue: 'create_charge_psp',
      prefetchCount: 1,
      persistent: true,
      noAck: false,
      queueOptions: {
        durable: true,
      },
      socketOptions: {
        heartbeatIntervalInSeconds: 60,
        reconnectTimeInSeconds: 5,
      },
    },
  });

  await app.startAllMicroservices();

  await app.listen(3000);
}
bootstrap();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/6f0b6796042cbd34cdfdc3b1db6d087540246cf4" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Start application
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;$ docker-compose up --build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5jkkc7fmzpwnz0lptbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5jkkc7fmzpwnz0lptbf.png" alt="app running console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Access rabbitmq panel in &lt;a href="http://localhost:15672" rel="noopener noreferrer"&gt;http://localhost:15672&lt;/a&gt; with default crendentials login: &lt;strong&gt;guest&lt;/strong&gt; and pass: &lt;strong&gt;guest&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnnjnmn17ys6urzertl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnnjnmn17ys6urzertl8.png" alt="rmq panel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Create publisher and consumer
&lt;/h2&gt;

&lt;p&gt;create-charge.publisher.ts&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { catchError, firstValueFrom, throwError } from 'rxjs';
import { CreateChargeInputProps } from 'src/credit-card/domain/contracts/psp-service.interface';

export class CreateChargePublisher {
  constructor(
    @Inject('create_charge_publisher')
    private readonly clientProxy: ClientProxy,
  ) {}

  async publish(data: CreateChargeInputProps): Promise&amp;lt;void&amp;gt; {
    await firstValueFrom(
      this.clientProxy.emit('CREATE_CHARGE_PSP', data).pipe(
        catchError((exception: Error) =&amp;gt; {
          return throwError(() =&amp;gt; new Error(exception.message));
        }),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;create-charge-on-psp.event-handler.ts&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller, Inject } from '@nestjs/common';
import { Ctx, EventPattern, Payload, RmqContext } from '@nestjs/microservices';
import {
  CreateChargeInputProps,
  ICreateCharge,
} from 'src/credit-card/domain/contracts/psp-service.interface';
import { Pagarme } from 'src/credit-card/infra/psp/pagarme/pagarme.service';

@Controller()
export class CreateChargeOnPSPEventHandler {
  constructor(
    @Inject(Pagarme)
    private readonly pspService: ICreateCharge,
  ) {}
  @EventPattern('CREATE_CHARGE_PSP')
  async handle(
    @Payload() payload: CreateChargeInputProps,
    @Ctx() context: RmqContext,
  ): Promise&amp;lt;void&amp;gt; {
    console.log(payload);
    const channel = context.getChannelRef();
    const originalMsg = context.getMessage();
    try {
      await this.pspService.createCharge(payload);
    } catch (error) {
      console.log(error);
    }
    channel.ack(originalMsg);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;credit-card.module.ts&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import { Pagarme } from './infra/psp/pagarme/pagarme.service';
import { CreateChargeUseCase } from './app/use-cases/create-charge.use-case';
import { CreateChargeController } from './presentation/controllers/create-charge.controller';
import { CreateChargeOnPSPEventHandler } from './presentation/event-handler/create-charge-on-psp.event-handler';
import { CreateChargePublisher } from './infra/rmq/publisher/create-charge.publisher';
import {
  ClientProxy,
  ClientProxyFactory,
  Transport,
} from '@nestjs/microservices';
import { ICreateCharge } from './domain/contracts/psp-service.interface';

@Module({
  providers: [
    Pagarme,
    {
      provide: CreateChargeUseCase,
      useFactory: (pspService: ICreateCharge) =&amp;gt; {
        return new CreateChargeUseCase(pspService);
      },
      inject: [Pagarme],
    },
    CreateChargePublisher,
    {
      provide: 'create_charge_publisher',
      useFactory: (): ClientProxy =&amp;gt; {
        return ClientProxyFactory.create({
          transport: Transport.RMQ,
          options: {
            urls: [`amqp://rabbitmq:5672`],
            queue: 'create_charge_psp',
            prefetchCount: 1,
            persistent: true,
            noAck: true,
            queueOptions: {
              durable: true,
            },
            socketOptions: {
              heartbeatIntervalInSeconds: 60,
              reconnectTimeInSeconds: 5,
            },
          },
        });
      },
    },
  ],
  controllers: [CreateChargeController, CreateChargeOnPSPEventHandler],
})
export class CreditCardModule {}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/558df94b3b896f7728f71fbde440597b0e893b1b" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Change the use case to send charge data to the rabbitmq queue instead of making the call to pagarme directly
&lt;/h2&gt;

&lt;p&gt;create-charge.use-case.ts&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  CreateChargeInputProps,
  CreateChargeOutputProps,
} from 'src/credit-card/domain/contracts/psp-service.interface';
import { CreditCardChargeEntity } from 'src/credit-card/domain/entities/credit-card-charge.entity';
import { ICreateChargeUseCase } from 'src/credit-card/domain/use-cases/create-charge.use-case';
import { IPublisherCreateCharge } from 'src/credit-card/infra/rmq/publisher/create-charge.publisher';

export class CreateChargeUseCase implements ICreateChargeUseCase {
  constructor(private readonly publisher: IPublisherCreateCharge) {}

  async execute(
    props: CreateChargeInputProps,
  ): Promise&amp;lt;Omit&amp;lt;CreateChargeOutputProps, 'pspId' | 'value'&amp;gt;&amp;gt; {
    const entity = CreditCardChargeEntity.newCharge(props);
    console.log(entity);
    await new Promise((resolve) =&amp;gt; setTimeout(resolve, 100)); //simulate database access
    await this.publisher.publish(props);
    return { ...props, status: 'PENDING' };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/51b862d9d44dc0cace531567f75378e24a3c5531" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now our endpoint for creating a credit card charge has an acceptable response time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9bnuqcz62vlj3mgo1u4q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9bnuqcz62vlj3mgo1u4q.png" alt="postman"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Several might halt their progress at this juncture; I've witnessed products in production following this approach. While it's not inherently incorrect, it hinders our ability to scale both the consumer and API horizontally and vertically independently. Additionally, the processing load on the consumer can adversely affect API response times. To address this, we aim to decouple startAllMicroservices() from listen(), instigating a shift towards a monorepo structure. Simultaneously, we'll develop a dedicated app for the consumer, fostering improved scalability and performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Switch from standard mode to monorepo mode
&lt;/h2&gt;

&lt;p&gt;Let's change the project structure to monorepo.&lt;br&gt;
&lt;code&gt;$ nest generate app credit-card-consumer&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/142c3f2027d721d0d72bbf23c47427044ef6d46a" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is imperative to modify the scripts responsible for building and launching the application. Additionally, we must craft a Dockerfile tailored for the consumer, configuring it to spawn three replicas, and subsequently, fine-tune the docker-compose.yml configuration accordingly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/add7851d733339ccbc781efe35e7db1bfd5be813" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Start application again
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;$ docker-compose up --build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, running the &lt;code&gt;$ docker ps&lt;/code&gt; command we can see 3 instances of the consumer and 1 of the api running&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eluf984eazwqxsw8611.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eluf984eazwqxsw8611.png" alt="containers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Decouple the consumer from the api
&lt;/h2&gt;

&lt;p&gt;Currently, the consumer remains tightly coupled to the API, as we've only created the new credit-card-consumer app without implementing any modifications. The next step involves decoupling these components by transferring the responsibility of initiating the  consumer  to the credit-card-consumer application.&lt;/p&gt;

&lt;p&gt;credit-card-consumer/src/main.ts&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NestFactory } from '@nestjs/core';
import { CreditCardConsumerModule } from './credit-card-consumer.module';
import { RmqOptions, Transport } from '@nestjs/microservices';

async function bootstrap() {
  const app = await NestFactory.create(CreditCardConsumerModule);
  app.connectMicroservice&amp;lt;RmqOptions&amp;gt;({
    transport: Transport.RMQ,
    options: {
      urls: [`amqp://rabbitmq:5672`],
      queue: 'create_charge_psp',
      prefetchCount: 1,
      persistent: true,
      noAck: false,
      queueOptions: {
        durable: true,
      },
      socketOptions: {
        heartbeatIntervalInSeconds: 60,
        reconnectTimeInSeconds: 5,
      },
    },
  });

  await app.startAllMicroservices();
}
bootstrap();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;See that here we do not need to invoke the app.listen() method.&lt;/p&gt;

&lt;p&gt;nestjs-rabbitmq-example/src/main.ts&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;See that here we only keep the app.listen() method.&lt;br&gt;
Moreover, I have eliminated the services and controllers produced by the &lt;code&gt;$ nest generate app credit-card-consumer&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;You can see that changes in this &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/bed1ea5cc07d77616b3b1f87889f6f094ed54560" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To finalize the decoupling process, it's essential to relocate the event handler to the credit-card-consumer, as it's responsible for both removing and processing messages from the queue. However, a challenge arises as the event handler relies on the Pagarme class within the nestjs-rabbitmq-example structure. To resolve this issue, let's transfer all shared components between the API and the consumer to the 'libs' folder, utilizing the 'nest generate library' resource. Importantly, we must refrain from importing any class from the API into the consumer under any circumstances.&lt;br&gt;
I moved all the shared code to the credit-card lib, basically i kept only the &lt;em&gt;presentation&lt;/em&gt; layer for API and consumer.&lt;br&gt;
Now our API is completely decoupled from the consumer, allowing us to scale each component independently.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/51d89b624a1c4c37959057db8e76e3166a95177b" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Limit cpu and memory for containers
&lt;/h2&gt;

&lt;p&gt;Establishing a controlled environment serves to restrict the RAM and CPU usage of containers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;See these changes in the&lt;/em&gt; &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example/commit/0214f4c86066394f391f736e413dfc0c80ebfe3e" rel="noopener noreferrer"&gt;commit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, this article outlined the journey of creating a credit card payment application, emphasizing the efficient integration of technologies such as NestJS, RabbitMQ, Docker, Docker Compose, and the use of a monorepo. When addressing the initial challenge of inadequate response time in our endpoint, we implemented a message broker service to enable asynchronous processing of HTTP requests, resulting in significant improvements in system efficiency and responsiveness.&lt;/p&gt;

&lt;p&gt;Additionally, we explored separating the consumer from the API within a monorepo, providing flexibility and individual scalability for both parts of the system. It is worth noting that, while I opted for the Clean Architecture, the choice of architecture and implementation within a monorepo remains at the developer's discretion.&lt;/p&gt;

&lt;p&gt;This approach not only enhances application performance but also provides a solid foundation for future adaptations and customizations as needs evolve. By seamlessly integrating these technologies within a monorepo environment, we hope this article serves as a valuable guide for those seeking to optimize efficiency and scalability in their payment applications.&lt;/p&gt;

&lt;p&gt;repo: &lt;a href="https://github.com/eduardoconti/nestjs-rabitmq-example" rel="noopener noreferrer"&gt;nestjs-rabitmq-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.nestjs.com/cli/monorepo" rel="noopener noreferrer"&gt;nestjs monorepo docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.nestjs.com/microservices/rabbitmq" rel="noopener noreferrer"&gt;nestjs rabbitmq docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rabbitmq.com/#getstarted" rel="noopener noreferrer"&gt;rabbitmq docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Automating Tag and Release Generation with Semantic Release and GitHub Actions for Node.js Applications</title>
      <dc:creator>Eduardo Conti</dc:creator>
      <pubDate>Sat, 06 Jan 2024 20:15:35 +0000</pubDate>
      <link>https://dev.to/eduardoconti/automating-tag-and-release-generation-with-semantic-release-and-github-actions-for-nodejs-applications-2d4i</link>
      <guid>https://dev.to/eduardoconti/automating-tag-and-release-generation-with-semantic-release-and-github-actions-for-nodejs-applications-2d4i</guid>
      <description>&lt;p&gt;In an increasingly automation-focused software development landscape, streamlining repetitive processes is essential. A common practice involves creating tags and releases to mark different versions of an application. However, this process can become tedious and prone to human errors. It is in this context that tools like Semantic Release and GitHub Actions come into play, offering an automated and robust approach to managing your project's versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Semantic Release?
&lt;/h2&gt;

&lt;p&gt;Semantic Release is a tool that automates semantic versioning and release creation based on the content of changes made to the source code. By adopting this approach, the tool analyzes commits since the last version, automatically determines the version type (major, minor, or patch) based on the changes made, and then generates a new tag and release. This not only streamlines the process but also ensures consistency and accuracy in your software versions. See &lt;a href="https://github.com/semantic-release/commit-analyzer/blob/HEAD/lib/default-release-rules.js" rel="noopener noreferrer"&gt;default release rules&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Importance of Conventional Commits
&lt;/h2&gt;

&lt;p&gt;Central to this process is the use of &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/#specification" rel="noopener noreferrer"&gt;Conventional Commits&lt;/a&gt;, a standardized commit message convention. Conventional Commits enable Semantic Release to accurately determine the type of version change based on commit messages. By following this convention, you ensure that your commits provide clear and consistent information for automated versioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Let's install the libs with the necessary plugins to use semantic release with github.&lt;br&gt;
&lt;code&gt;npm i @semantic-release/commit-analyzer @semantic-release/git @semantic-release/github @semantic-release/release-notes-generator @semantic-release/npm --save-dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Update &lt;strong&gt;package.json&lt;/strong&gt; with &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

"release": {
    "branches": [
      "main"
    ],
    "repositoryUrl": "https://github.com/your_user/your_repository.git",
    "plugins": [
      "@semantic-release/commit-analyzer",
      "@semantic-release/release-notes-generator",
      "@semantic-release/npm",
      [
        "@semantic-release/git",
        {
          "message": "chore(release): ${nextRelease.version} \n\n${nextRelease.notes}"
        }
      ],
      "@semantic-release/github"
    ]
  }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Update your permissions for GITHUB_TOKEN to &lt;strong&gt;Read and write&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3dt8zbusau854cgl5if.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3dt8zbusau854cgl5if.png" alt="Github permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the repository secrets NPM_TOKEN; it is essential for the @semantic-release/npm plugin to update your package.json version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9kgy9nh1zf0fto9keum.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9kgy9nh1zf0fto9keum.png" alt="NPM_TOKEN"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create file &lt;strong&gt;release.yml&lt;/strong&gt; in folder .github/workflows&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

name: Release
on:
  push:
    branches:
      - main
jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "lts/*"
      - name: Install dependencies
        run: npm install
      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: npx semantic-release



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now I'm going to add a feature to my application that implements an endpoint for health check.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ git commit -m "feat: add health check endpoint"&lt;/code&gt;&lt;br&gt;
&lt;code&gt;$ git push origin main&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgm5z6tz7xdpzdrjj360v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgm5z6tz7xdpzdrjj360v.png" alt="pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fay8ckxff53j4bmue8u7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fay8ckxff53j4bmue8u7x.png" alt="V1.0.0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that the first version of the application was generated.&lt;br&gt;
Now, I'm going to add one more feature, an endpoint to return a list of users.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ git commit -m "feat: add endpoint to list all users"&lt;/code&gt;&lt;br&gt;
&lt;code&gt;$ git push origin main&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jodtfa3nl4fxfcjw5c4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jodtfa3nl4fxfcjw5c4.png" alt="pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that a new tag was generated with a minor change.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjunxko54vtko7ugai0lh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjunxko54vtko7ugai0lh.png" alt="v1.1.0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I'm going to do a bug fix.&lt;br&gt;
&lt;code&gt;$ git commit -m "fix: bug fix example"&lt;/code&gt;&lt;br&gt;
&lt;code&gt;$ git push origin main&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can see that a new tag was generated with a patch change.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmj0jmid288ukw0haas8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmj0jmid288ukw0haas8.png" alt="v1.1.1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To conclude, I will implement a feature that includes breaking changes, resulting in the generation of a tag reflecting a major version update.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ git commit -m "feat: add authentication for get users route

BREAKING CHANGE: route /users needs authentication"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g0m0o6mqv0xzm8w88j7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g0m0o6mqv0xzm8w88j7.png" alt="v2.0.0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;repository: &lt;a href="https://github.com/eduardoconti/semantic-release-example" rel="noopener noreferrer"&gt;semantic-release-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;links: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://semantic-release.gitbook.io/semantic-release/" rel="noopener noreferrer"&gt;semantic-release.gitbook&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.conventionalcommits.org/en/v1.0.0/#specification" rel="noopener noreferrer"&gt;conventional commits&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.npmjs.com/creating-and-viewing-access-tokens" rel="noopener noreferrer"&gt;NPM creating-and-viewing-access-tokens&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>NestJS APM with Elastic and Docker</title>
      <dc:creator>Eduardo Conti</dc:creator>
      <pubDate>Thu, 04 Jan 2024 00:12:41 +0000</pubDate>
      <link>https://dev.to/eduardoconti/nestjs-apm-with-elastic-and-docker-2nb0</link>
      <guid>https://dev.to/eduardoconti/nestjs-apm-with-elastic-and-docker-2nb0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Observability is a crucial aspect of modern software development, offering a comprehensive view into the performance and behavior of applications. As applications become more complex and distributed, understanding how they function in real-time becomes paramount. Application Performance Monitoring (APM) plays a pivotal role in observability, providing developers with the tools to track, analyze, and optimize their applications.&lt;/p&gt;

&lt;p&gt;In this article, we'll delve into the implementation of APM with NestJS and Elasticsearch. By harnessing the power of these technologies, developers can gain valuable insights into their application's health, identify bottlenecks, and enhance overall performance. &lt;/p&gt;

&lt;h2&gt;
  
  
  Pre requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NestJs CLI&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Init NestJS app
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;$ npm i -g @nestjs/cli&lt;/code&gt;&lt;br&gt;
&lt;code&gt;$ nest new apm-example&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add elastic-apm-node lib
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;$ yarn add elastic-apm-node&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create env file
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;.env&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;ELASTIC_APM_SERVICE_NAME=apm-example&lt;br&gt;
 ELASTIC_APM_SERVER_URL=http://apm-server:8200&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Add @nestjs/config
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;$ yarn add @nestjs/config&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Import ConfigModule
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;app.module.ts&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApmModule } from './apm/apm.module';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ApmModule,
    ConfigModule.forRoot({
      envFilePath: '.env',
      isGlobal: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  6. Compiler options
&lt;/h3&gt;

&lt;p&gt;Add compiler options configurations for correct functioning of the agent&lt;br&gt;
&lt;a href="https://www.elastic.co/guide/en/apm/agent/nodejs/current/typescript.html" rel="noopener noreferrer"&gt;Elastic config agent with ts&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;tsconfig.json&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;"esModuleInterop": true,&lt;br&gt;
 "moduleResolution": "node"&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Create apm module and service
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;$ nest g module apm&lt;/code&gt;&lt;br&gt;
&lt;code&gt;$ nest g service apm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;apm.module.ts&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import { Module } from '@nestjs/common';
import { ApmService } from './apm.service';

@Module({
  providers: [ApmService],
})
export class ApmModule {}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;apm.service.ts&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import { Injectable } from '@nestjs/common';
import 'elastic-apm-node/start';

@Injectable()
export class ApmService {}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  8. Create files Dockerfile and docker-compose
&lt;/h3&gt;

&lt;p&gt;Create Dockerfile and docker-compose.yml in src folder:&lt;br&gt;
&lt;strong&gt;Dockerfile&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

FROM node:18.12.0-alpine AS build

WORKDIR /app

COPY package.json yarn.lock ./

RUN yarn 

COPY . .

RUN yarn build

FROM node:18.12.0-alpine

WORKDIR /app

COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/yarn.lock ./yarn.lock

EXPOSE ${PORT}

CMD ["yarn", "start:prod"]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;docker-compose.yml&lt;/strong&gt;&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;version: '3.7'&lt;/p&gt;

&lt;p&gt;services:&lt;br&gt;
  apm-example:&lt;br&gt;
    hostname: apm-example&lt;br&gt;
    restart: on-failure&lt;br&gt;
    build:&lt;br&gt;
      context: .&lt;br&gt;
      dockerfile: ./Dockerfile&lt;br&gt;
    volumes:&lt;br&gt;
      - .:/app&lt;br&gt;
    env_file:&lt;br&gt;
      - .env&lt;br&gt;
    ports:&lt;br&gt;
      - "3000:3000"&lt;br&gt;
    command: npm run start:dev&lt;br&gt;
    depends_on:&lt;br&gt;
      - apm-server&lt;br&gt;
    cpus: 1&lt;br&gt;
    mem_limit: 1024M&lt;br&gt;
  elasticsearch:&lt;br&gt;
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1&lt;br&gt;
    container_name: elasticsearch&lt;br&gt;
    environment:&lt;br&gt;
      - discovery.type=single-node&lt;br&gt;
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"&lt;br&gt;
    ulimits:&lt;br&gt;
      memlock:&lt;br&gt;
        soft: -1&lt;br&gt;
        hard: -1&lt;br&gt;
    volumes:&lt;br&gt;
      - es-data:/usr/share/elasticsearch/data&lt;br&gt;
    ports:&lt;br&gt;
      - 9200:9200&lt;br&gt;
    healthcheck:&lt;br&gt;
      interval: 30s&lt;br&gt;
      retries: 10&lt;br&gt;
      test: curl -s &lt;a href="http://localhost:9200/_cluster/health" rel="noopener noreferrer"&gt;http://localhost:9200/_cluster/health&lt;/a&gt; | grep -vq '"status":"red"'&lt;br&gt;
  kibana:&lt;br&gt;
    image: docker.elastic.co/kibana/kibana:7.12.1&lt;br&gt;
    container_name: kibana&lt;br&gt;
    ports:&lt;br&gt;
      - 5601:5601&lt;br&gt;
    environment:&lt;br&gt;
      ELASTICSEARCH_URL: &lt;a href="http://elasticsearch:9200" rel="noopener noreferrer"&gt;http://elasticsearch:9200&lt;/a&gt;&lt;br&gt;
    cpus: 0.1&lt;br&gt;
    mem_limit: 256M &lt;br&gt;
    depends_on:&lt;br&gt;
      elasticsearch:&lt;br&gt;
        condition: service_healthy&lt;br&gt;
    healthcheck:&lt;br&gt;
      interval: 40s&lt;br&gt;
      retries: 20&lt;br&gt;
      test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null &lt;a href="http://localhost:5601/api/status" rel="noopener noreferrer"&gt;http://localhost:5601/api/status&lt;/a&gt;&lt;br&gt;
  apm-server:&lt;br&gt;
    image: docker.elastic.co/apm/apm-server:7.17.16&lt;br&gt;
    depends_on:&lt;br&gt;
      elasticsearch:&lt;br&gt;
        condition: service_healthy&lt;br&gt;
      kibana:&lt;br&gt;
        condition: service_healthy&lt;br&gt;
    cap_add: ["CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"]&lt;br&gt;
    cap_drop: ["ALL"]&lt;br&gt;
    ports:&lt;br&gt;
    - 8200:8200&lt;br&gt;
    command: &amp;gt;&lt;br&gt;
       apm-server -e&lt;br&gt;
         -E apm-server.rum.enabled=true&lt;br&gt;
         -E setup.kibana.host=kibana:5601&lt;br&gt;
         -E setup.template.settings.index.number_of_replicas=0&lt;br&gt;
         -E apm-server.kibana.enabled=true&lt;br&gt;
         -E apm-server.kibana.host=kibana:5601&lt;br&gt;
         -E output.elasticsearch.hosts=["elasticsearch:9200"]&lt;br&gt;
    healthcheck:&lt;br&gt;
      interval: 10s&lt;br&gt;
      retries: 12&lt;br&gt;
      test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null &lt;a href="http://localhost:8200/" rel="noopener noreferrer"&gt;http://localhost:8200/&lt;/a&gt;&lt;br&gt;
    cpus: 0.1&lt;br&gt;
    mem_limit: 256M &lt;br&gt;
volumes:&lt;br&gt;
  es-data:&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  

&lt;ol&gt;
&lt;li&gt;Run docker containers
&lt;/li&gt;
&lt;/ol&gt;
&lt;/h3&gt;


&lt;p&gt;&lt;code&gt;$ docker-compose up --build -d --timeout 180&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  10. GET /
&lt;/h3&gt;

&lt;p&gt;access the application endpoint to collect the first metrics&lt;br&gt;
&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Open elastic app
&lt;/h3&gt;

&lt;p&gt;&lt;a href="http://localhost:5601/app/apm/services?rangeFrom=now-5m&amp;amp;rangeTo=now" rel="noopener noreferrer"&gt;http://localhost:5601&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;you should see something similar to this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7vu8b0kda6f9k2y33yu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7vu8b0kda6f9k2y33yu.png" alt="Elastic APM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fss4jmaaam53zn5p44cvx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fss4jmaaam53zn5p44cvx.png" alt="Elastic APM Trace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Collect docker metrics with Metricbeat
&lt;/h3&gt;

&lt;p&gt;&lt;a href="http://localhost:5601/app/home#/tutorial/dockerMetrics" rel="noopener noreferrer"&gt;http://localhost:5601/app/home#/tutorial/dockerMetrics&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After install and configure Metricbeat, you should run&lt;br&gt;
&lt;code&gt;$ sudo metricbeat setup&lt;br&gt;
 $ sudo service metricbeat start&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;you should see something similar to this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqppfyj6qvz5udo6xqbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqppfyj6qvz5udo6xqbq.png" alt="Docker metrics received data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click in &lt;strong&gt;Docker metrics dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;you should see something similar to this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijeid5mil0lyagqiiadz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijeid5mil0lyagqiiadz.png" alt="Docker metrics dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can explore Elastic metrics, happy learning!&lt;/p&gt;

&lt;p&gt;repo: &lt;a href="https://github.com/eduardoconti/apm-example" rel="noopener noreferrer"&gt;apm-example&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>elasticsearch</category>
      <category>docker</category>
      <category>apm</category>
    </item>
  </channel>
</rss>
