Guide to Using AWS SNS and SQS
Introduction
AWS SNS
AWS SNS (Amazon Simple Notification Service) is a fully managed messaging service by AWS that operates on a Pub/Sub (Publish/Subscribe) model.
Simply put, SNS acts as a "coordination hub" for messages. A system (Publisher) sends a message to SNS, and SNS automatically pushes that message to all destinations that have registered to receive it (Subscribers).
Core Concepts
To understand SNS, you need to grasp three concepts:
- Topic: A logical communication channel. You send messages to this Topic.
- Publisher: The system that sends messages to the Topic (e.g., a web app, CloudWatch).
- Subscriber: The parties that receive messages from the Topic (e.g., Email, SMS, Lambda functions).
Key Advantages
- Serverless: No server management required; AWS scales automatically.
- High Reliability: Messages are stored across multiple Availability Zones to ensure they are not lost.
- Low Cost: Pay-as-you-go based on the actual number of messages sent.
AWS SQS
AWS SQS (Simple Queue Service) is a fully managed message queue service that allows you to decouple and scale microservices, distributed systems, and serverless applications. Think of SQS as an intermediate "buffer": the sender (Producer) puts a message into the queue, and the receiver (Consumer) pulls the message out for processing when it has enough resources.
Key Advantages
- Decoupling: System components do not need to communicate directly. If a service fails or pauses, messages remain safe in the queue.
- Scalability: SQS can handle a massive volume of messages (nearly unlimited throughput) without requiring manual infrastructure management.
- High Reliability and Security: Messages are stored redundantly across multiple servers and Availability Zones (AZs). You can encrypt message content using AWS KMS.
- Cost-Effective: Operates on a pay-as-you-go model with a Free Tier of up to 1 million requests per month.
- Smart Error Handling (Dead Letter Queue): If a message fails to process after several attempts, SQS moves it to a separate queue (DLQ) for debugging.
- Flexible Choices:
- Standard Queue: Ultra-high throughput, guarantees at-least-once delivery.
- FIFO Queue: Guarantees messages are processed in the exact order they were sent and exactly-once processing (no duplicates).
The Perfect Hybrid Model: SNS + SQS (Fan-out)
In practice, these two are often used together in a very popular AWS architecture:
- An event occurs and is pushed to an SNS Topic.
- This SNS Topic has multiple Subscribers which are different SQS Queues.
- Each Queue serves a distinct service.
Benefit: You can broadcast messages to multiple places (via SNS) while ensuring each destination stores and processes the message safely without loss if the system is busy (via SQS).
Detailed Implementation
In this guide, I will show you how to use SNS to send messages to a topic and use SQS to subscribe and receive those messages.
First, create the file lib/sns-sqs-stack.ts:
import * as cdk from "aws-cdk-lib"
import * as sns from "aws-cdk-lib/aws-sns"
import * as subs from "aws-cdk-lib/aws-sns-subscriptions"
import * as sqs from "aws-cdk-lib/aws-sqs"
import { Construct } from "constructs"
export class SnsSqsStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
const myTopic = new sns.Topic(this, "MyOrderTopic", {
topicName: "order-events-topic",
})
const myQueue = new sqs.Queue(this, "MyOrderQueue")
myTopic.addSubscription(new subs.SqsSubscription(myQueue))
new cdk.CfnOutput(this, "TopicArn", {
value: myTopic.topicArn,
})
new cdk.CfnOutput(this, "QueueUrl", {
value: myQueue.queueUrl,
})
}
}
The TopicArn and QueueUrl values will be used in the NestJS project.
Update file bin/aws-cdk.ts:
#!/usr/bin/env node
import * as cdk from "aws-cdk-lib/core"
import { SnsSqsStack } from "../lib/sns-sqs-stack"
const app = new cdk.App()
new SnsSqsStack(app, "SnsSqsStack")
After deployment, the results will appear as follows:
✅ SnsSqsStack
✨ Deployment time: 50.82s
Outputs:
SnsSqsStack.QueueUrl = https://sqs.ap-southeast-1.amazonaws.com/347116125752/SnsSqsStack-MyOrderQueue223C541D-DHWn3V5yBCuh
SnsSqsStack.TopicArn = arn:aws:sns:ap-southeast-1:347116125752:order-events-topic
✨ Total time: 155.66s
Next, create the file sns.service.ts in the NestJS project:
import {PublishCommand, SNSClient} from '@aws-sdk/client-sns'
import {fromIni} from '@aws-sdk/credential-providers'
import {Injectable} from '@nestjs/common'
import {ConfigService} from '@nestjs/config'
@Injectable()
export class SnsService {
private snsClient: SNSClient
private topicArn: string
constructor(private configService: ConfigService) {
const region = this.configService.get<string>('REGION') || ''
const profile = this.configService.get<string>('PROFILE') || ''
this.topicArn = this.configService.get<string>('TOPIC_ARN') || ''
this.snsClient = new SNSClient({
region,
credentials: fromIni({profile}),
})
}
async publishInfo(info: Record<string, any>) {
const command = new PublishCommand({
TopicArn: this.topicArn,
Message: JSON.stringify(info),
})
try {
const response = await this.snsClient.send(command)
return {success: true, messageId: response.MessageId}
} catch (error) {
console.error('SNS Publish Error', error)
throw error
}
}
}
- Note: The profile you use must have permission to send messages to SNS.
- TOPIC_ARN is the value you receive after deploying the CDK stack.
Next is the file sns.controller.ts:
import {Body, Controller, Post} from '@nestjs/common'
import {SnsService} from 'src/service/sns.service'
@Controller('sns')
export class SnsController {
constructor(private readonly snsService: SnsService) {}
@Post('publish-info')
publishInfo(@Body() data: Record<string, any>) {
return this.snsService.publishInfo(data)
}
}
Then create the file sqs-consumer.service.ts:
import {Injectable, OnModuleInit, OnModuleDestroy} from '@nestjs/common'
import {Consumer} from 'sqs-consumer'
import {SQSClient} from '@aws-sdk/client-sqs'
import {ConfigService} from '@nestjs/config'
@Injectable()
export class SqsConsumerService implements OnModuleInit, OnModuleDestroy {
private consumer: Consumer
constructor(private readonly configService: ConfigService) {}
onModuleInit() {
this.consumer = Consumer.create({
queueUrl: this.configService.get<string>('SQS_QUEUE_URL') || '',
sqs: new SQSClient({region: this.configService.get<string>('REGION')}),
handleMessage: async message => {
if (message.Body) {
await this.handleProcessing(message.Body)
}
},
})
this.consumer.on('error', err => console.error('SQS Error:', err.message))
this.consumer.on('processing_error', err =>
console.error('Processing Error:', err.message)
)
this.consumer.start()
console.log('🚀 SQS Consumer is listening...')
}
async handleProcessing(rawBody: string) {
try {
const snsEnvelope = JSON.parse(rawBody)
const actualData =
typeof snsEnvelope.Message === 'string'
? JSON.parse(snsEnvelope.Message)
: snsEnvelope.Message
console.log('--- Logic handling ---')
console.log('Data received:', actualData)
} catch (error) {
console.error('Error message:', error.message)
throw error
}
}
onModuleDestroy() {
this.consumer.stop()
console.log('🛑 SQS Consumer stopped.')
}
}
- This functionality is used to receive messages that were sent into the topic.
- SQS_QUEUE_URL is the value you receive after deploying CDK.
Import the required services and controllers into app.module.ts:
import {Module} from '@nestjs/common'
import {AppController} from './app.controller'
import {AppService} from './app.service'
import {SnsController} from './controller/sns.controller'
import {SqsConsumerService} from './service/sqs-consumer.service'
import {SnsService} from './service/sns.service'
@Module({
controllers: [AppController, SnsController],
providers: [AppService, SnsService, SqsConsumerService],
})
export class AppModule {}
REGION = ap-southeast-1
PROFILE = roles-anywhere
TOPIC_ARN = arn:aws:sns:ap-southeast-1:347116125752:order-events-topic
SQS_QUEUE_NAME = ORDER_QUEUE
SQS_QUEUE_URL = https://sqs.ap-southeast-1.amazonaws.com/347116125752/SnsSqsStack-MyOrderQueue223C541D-DHWn3V5yBCuh
🚀 SQS Consumer is listening...
2026-02-09T10:51:57.432Z info [HTTP] {"url":"/sns/publish-info","method":"POST","payload":{"data1":"data 1"},"response":{"success":true,"messageId":"b99a34fd-371f-5f2b-ab34-56fe8992c3e5"},"duration":"346ms"} +39m
--- Logic handling ---
Data received: { data1: 'data 1' }
This is just an example, so I'm using both SNS and SQS within the same project. In a real-world microservices architecture, when SNS sends a message to a topic, other services will subscribe to that topic to process the data accordingly.
Happy coding!
Comments
Post a Comment