DEV Community

Oscar Crespo Sanchez
Oscar Crespo Sanchez

Posted on

SNS Localstack http subscription with Nestjs

SNS Localstack http subscription with Nestjs

Last week, I found myself on a mission to replicate a slice of the AWS infrastructure in my local environment. Acutting-edge microservices architecture powered by Fargate ECS, where seamless communication between services is orchestrated by SNS.

Enter Localstack — your go-to tool for emulating cloud services locally. It’s the perfect match for bringing the AWS cloud experience right to your development machine.

In this tutorial, I’ll guide you through the process of integrating a server in Nest.js that effortlessly communicates with Localstack SNS. Get ready to dive into the world of event-driven microservices and learn how to subscribe to events like a pro.

But before we embark on this exciting journey, let’s set the stage. You’ll need to ensure a few essential resources are installed to make the most of this tutorial.

  • Localstack

    brew install localstack/tap/localstack-cli

  • Awslocal

    pip3 install awscli-local

  • Nestjs

    npm i -g @nestjs/cli

  • Npm >16

Now that our toolbox is fully equipped, it’s time to dive into the exciting realm of server development. For the purpose of this tutorial, I’ve opted for Nest.js, a framework known for its lightning-fast setup and developer-friendly environment.

Let’s kick things off by creating a new Nest.js project. Fire up your terminal and execute the following commands:

$ npm i -g @nestjs/cli
$ nest new localstack-server
Enter fullscreen mode Exit fullscreen mode

In this tutorial, we won’t delve deep into the intricacies of Nest.js structure. Don’t worry, though, because if you’ve previously worked with any other backend framework, you’ll find yourself right at home.

Rather than relying on all the libraries that come bundled with Nest.js by default, our focus will be on a crucial addition — the aws-sdk. This library is instrumental for confirming the SNS subscription. To install it, simply run the following command:

npm i -g @aws-sdk/client-sns
Enter fullscreen mode Exit fullscreen mode

Now, if you’re already familiar with the magic of HTTP SNS, feel free to skip this part — you’re ahead of the game! But for those who are diving into this exciting world for the first time, let me break it down in a way that’s as friendly as a conversation over coffee.

Image description

Take a look at the image to get a glimpse of what we’re about to build. Our server is gearing up to be a subscriber to an SNS topic, ready to catch messages as they flow through. Imagine it as setting up a personal hotline between your server and the SNS — whenever there’s a message, it rings our server’s bell!

Now, here’s the scoop: before our server can start handling these incoming messages, it needs to confirm its subscription to SNS. Enter the /_sns/onEvent endpoint, a special spot where this confirmation magic happens.

Let’s break down how you can configure your controller to make this seamless connection a reality.

import { Body, Controller, Get, Post, Req } from '@nestjs/common';
import { AppService } from './app.service';
import { Request } from 'express';

import {
  ConfirmSubscriptionCommand,
  ConfirmSubscriptionCommandOutput,
  SNSClient,
} from '@aws-sdk/client-sns';


@Controller()
export class AppController {
  private readonly snsClient: SNSClient;

  constructor(private readonly appService: AppService) {
    this.snsClient = new SNSClient({
      endpoint: process.env.SNS_ENDPOINT || 'http://127.0.0.1:4566',
      region: process.env.AWS_REGION || 'eu-west-1',
    });
  }

  @Post('_sns/onEvent')
  onEvent(@Body() message: string, @Req() req: Request): Promise<ConfirmSubscriptionCommandOutput> | boolean {
    if (req.headers['x-amz-sns-message-type'] === 'SubscriptionConfirmation') {
      const subscribeEvent: { Token: string; TopicArn: string } =
        JSON.parse(message);
      const { Token: token, TopicArn: topicArn } = subscribeEvent;
      const command = new ConfirmSubscriptionCommand({
        Token: token,
        TopicArn: topicArn,
      });

      return this.snsClient.send(command)
    } else {
      return this.appService.doSomethingOnEvent()
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Certainly! Here’s a revised version of your text:

In the constructor of our controller, I’ve declared the SNS client that will be our trusty companion in communicating with the Localstack service. Below, you’ll find the endpoint I’ve crafted to handle the SNS interactions.

The first line of this endpoint is like a gatekeeper, performing validations to discern whether the incoming request is for a new subscription or if it carries a vital message. If it’s a subscription request, our mission is to let Localstack know that our endpoint is not only aware but also eager to receive messages.

Once subscribed, our endpoint stands ready to gracefully handle events cascading in from the topic we’ve engaged with. I’ll guide you through this event-handling process in just a bit.

However, before we proceed, let’s make a quick pitstop in our main.ts file. By default, Nest.js isn't configured to receive text-format bodies. To remedy this, we need to make a small tweak.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule, {
    rawBody: true,
    cors: true,
    bufferLogs: true,
  });
  app.useBodyParser('text');
  await app.listen(3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

With the majority of the code in place, we’re almost there! The final step involves adding a new, empty service in the app.service.ts file. Think of it as giving our application that last missing puzzle piece.

Let’s keep the momentum going and complete the puzzle by creating this essential service.

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {

  public doSomethingOnEvent(){
    return true
  }
}
Enter fullscreen mode Exit fullscreen mode

Finish!! Our service is done and you can execute it with the following command.

npm start
Enter fullscreen mode Exit fullscreen mode

Before we dive into setting up all the Localstack magic, let me highlight a crucial detail.

By default, our Localstack service is operating on port 4566 locally. You might be wondering why, in our app.controller.ts file, we're connecting to the SNS server at http://127.0.0.1:4566. Well, the reason lies in the fact that Localstack is running in Docker, and this is the gateway we use to access it.

Understanding this connection point will ensure smooth communication between our application and the Localstack service.

Now, let’s transition to the “infrastructure” part — and trust me, it’s super easy.

For a seamless experience, I highly recommend opening an iTerm split into three sections. Dedicate one section to your Nest.js server, another to the Localstack server, and the third for executing Localstack commands. This way, you’ll have a clear view of each component as we bring our infrastructure to life.

Image description

We start localstack (left section)

localstack start
Enter fullscreen mode Exit fullscreen mode

Create sns resources:

  • Create sns topic (where we send the messages and subscribe our endpoint)

    awslocal sns create-topic --name app_events

  • Create a subscription to our nestjs endpoint server

    awslocal sns subscribe --topic-arn arn:aws:sns:eu-west-1:000000000000:app_events --protocol http --notification-endpoint http://host.docker.internal:3000/_sns/onEvent

Let’s pause for a moment here. Notice how we’re directing our server to http://host.docker.internal:3000 It’s the key to seamlessly reaching our server through the Docker machine. While instinctively, you might consider pointing directly to localhost, bear in mind that it won’t work in this setup.

Now, if your server is successfully subscribed, keep an eye on your Localstack window — you should catch a glimpse of something like this.

Image description

We’re almost there! The last piece of the puzzle involves sending messages and having your server gracefully catch them.

awslocal sns publish --topic-arn arn:aws:sns:eu-west-1:000000000000:app_events --message "hello world"
Enter fullscreen mode Exit fullscreen mode

Voilà! If everything went smoothly, you’ll find a reassuring message waiting for you in your Nest.js console.

Image description

Recreating an entire infrastructure might sound daunting, but trust me, it’s a necessary journey for your team to have a playground without relying on a dedicated DevOps team. Just last week, I found myself grappling with this challenge, and the struggle inspired me to put together this free tutorial. Most tutorials out there come with a hefty price tag, but I believe everyone should have access to this knowledge.

This is my first attempt at a tutorial, and I really hope it proves helpful, or at the very least, sparks some curiosity! Feel free to reach out if you need anything — I’m here to help and learn together.

Happy coding! 🚀

Code repository: https://github.com/imOscarCrespo/localstack-server

Instagram: oscarcrespo.xyz

Top comments (0)