JavaScript

LocalStack SQS Node.js Example

Hello. In this tutorial, we will interact with Amazon AWS SQS (simple queue service) to create a Node.js app with the help of a popular emulator known as LocalStack.

1. Introduction

Localstack is an aws cloud service emulator that runs in a single container and allows us to run the aws applications on our local machines without connecting to a remote cloud provider. This tool helps to speed up and simplify the testing and development workflow. To work with localstack we will use docker so that we have the setup ready within minutes and without any dependency. If someone needs to go through the Docker installation, please watch this video.

1.1 Setting up Node.js

To set up Node.js on windows you will need to download the installer from this link. Click on the installer (also include the NPM package manager) for your platform and run the installer to start with the Node.js setup wizard. Follow the wizard steps and click on Finish when it is done. If everything goes well you can navigate to the command prompt to verify if the installation was successful as shown in Fig. 1.

LocalStack SQS Node.js - npm installation
Fig. 1: Verifying node and npm installation

1.2 Setting up LocalStack

To set up the localstack on our machine we will write a simple docker-compose file and execute the docker-compose commands to quickly start/stop the container. Add the below code the yml file to have the container up and running in minutes –

docker-compose.yml

services:
  localstack:
    environment:
      - AWS_DEFAULT_REGION=ap-southeast-1
      - EDGE_PORT=4566
      - SERVICES=sqs
    image: "localstack/localstack:latest"
    ports:
      - "4566:4566"
    container_name: docker-localstack
    volumes:
      - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
version: "3.0"

You’re free to change the details in the above yml file as per your requirements. For this tutorial, I am keeping some of the details are default.

1.3 Start/Stop the container

To start the localstack container we will execute the docker-compose command in the terminal window. Open a new terminal, navigate to the path containing the yml file and execute the below command.

Start the container

-- pull the image, create and start the container --
docker compose up -d

The above command will start the container on port – 4566 in daemon mode. If you will be doing this activity for the first time then the Docker tool will first pull the image from the hub repository. You can hit the health endpoint in the browser to confirm that the localstack container is up and running successfully.

Localstack health check endpoint

-- to check whether the localstack is up and running --
http://localhost:4566/health

Once you are done with the tutorial use the below command to stop and remove the running localstack container.

Stop and remove the container

-- stop and remove the container –
docker compose down

2. LocalStack SQS Node.js Example

To set up the application, we will need to navigate to a path where our project will reside. For programming stuff, I am using Visual Studio Code as my preferred IDE. You’re free to choose the IDE of your choice.

2.1 Setting up the implementation

Let us write the different files which will be required for practical learning.

2.1.1 Setting up dependencies

Navigate to the project directory and run npm init -y to create a package.json file. This file holds the metadata relevant to the project and is used for managing the project dependencies, script, version, etc. Add the following code to the file wherein we will specify the required dependencies.

package.json

{
  "name": "localstack-sqs",
  "version": "1.0.0",
  "description": "localstack sqs",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon index.js"
  },
  "keywords": [
    "localstack sqs",
    "nodejs",
    "expressjs"
  ],
  "author": "javacodegeeks",
  "license": "ISC",
  "dependencies": {
    "aws-sdk": "^2.1043.0",
    "express": "^4.17.1",
    "underscore": "^1.13.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.15"
  }
}

To download the dependencies navigate to the directory path containing the file and use the npm install command. If everything goes well the dependencies will be loaded inside the node_modules folder and you are good to go with the further steps.

2.1.2 Creating an environment config

Once the localstack is up and running we will be creating the environment configuration file required for this tutorial. Add the below code to the file present in the config folder. The file contains the localstack service endpoint and other required information for this tutorial.

env.js

const env = {
  REGION: "ap-southeast-1", // should match the one given in the docker-compose.yml file
  ACCOUNT_ID: "000000000000", // represents the dummy aws account id for localstack
  IAM: {
    ACCESS_KEY_ID: "na",
    SECRET_ACCESS_KEY: "na"
  },
  SERVICE_ENDPOINT: "http://localhost:4566", // represents the service point for localstack
  QUEUE_NAME: "geek" // queue name used in this tutorial for implementation
};

module.exports = env;

2.1.3 Creating a request handler class

Create a request handler class that will be responsible to handle the incoming requests from the client. The file will also contain the information about the sqs service object creation. Add the below code to the file present in the handler folder.

requests.js

const _ = require("underscore");

const env = require("../config/env");

// configuring local-stack aws
const AWS = require("aws-sdk");
AWS.config.update({
  region: env.REGION
});

// create an sqs service object
const sqs = new AWS.SQS({
  endpoint: env.SERVICE_ENDPOINT,
  accessKeyId: env.IAM.ACCESS_KEY_ID,
  secretAccessKey: env.IAM.SECRET_ACCESS_KEY,
  region: env.REGION
});

// handler methods

// method1 - to get sqs config status
const getConfigurationStatus = (req, res) => {
  console.log("fetching configuration status");
  res.status(200).json({
    status: "ok",
    data: sqs
  });
};

// method2 - to list all queues
const listQueues = (req, res) => {
  console.log("fetching all queues");
  sqs.listQueues({}, (err, data) => {
    if (err) {
      res.status(500).json({
        status: "internal server error",
        error: err
      });
    } else {
      res.status(200).json({
        status: "ok",
        urls: data.QueueUrls
      });
    }
  });
};

// method3 - to create a new sqs queue
const createQueue = (req, res) => {
  let createParams = {
    QueueName: env.QUEUE_NAME, // in real example client should provide queue name
    Attributes: {
      DelaySeconds: "60",
      MessageRetentionPeriod: "86400"
    }
  };
  console.log(createParams);

  sqs.createQueue(createParams, (err, data) => {
    if (err) {
      res.status(500).json({
        status: "internal server error",
        error: err
      });
    } else {
      res.status(201).json({
        status: "created",
        name: data.QueueName,
        message: "queue created successfully"
      });
    }
  });
};

// method4 - to purge the given sqs queue
const purgeQueue = (req, res) => {
  let queueName = req.query.name;
  if (_.isEmpty(queueName)) {
    res.status(400).json({
      status: "bad request",
      message: "queue name cannot be empty"
    });
  } else {
    console.log("purging queue = " + queueName);

    let purgeParams = {
      QueueUrl: env.SERVICE_ENDPOINT + "/" + env.ACCOUNT_ID + "/" + queueName
    };
    console.log(purgeParams);

    sqs.purgeQueue(purgeParams, (err, data) => {
      if (err) {
        res.status(500).json({
          status: "500",
          err: err
        });
      } else {
        res.status(200).json({
          status: "ok",
          data: "queue purged"
        });
      }
    });
  }
};

// method5 - to publish the message to the sqs queue
const publishMsg = (req, res) => {
  //todo - add empty queue name validation in the request body

  let msg = {
    id: req.body["id"],
    name: req.body["name"],
    email: req.body["email"],
    phone: req.body["phone"]
  };

  let msgParams = {
    QueueUrl:
      env.SERVICE_ENDPOINT + "/" + env.ACCOUNT_ID + "/" + req.body["queueName"], // queueName will never be empty
    MessageBody: JSON.stringify(msg)
  };
  console.log(msgParams);

  sqs.sendMessage(msgParams, (err, data) => {
    if (err) {
      res.status(500).json({
        status: "internal server error",
        error: err
      });
    } else {
      res.status(202).json({
        status: "accepted",
        messageId: data.MessageId,
        message: "sent to queue"
      });
    }
  });
};

// method6 - to receive the message from the sqs queue
const receiveMsg = (req, res) => {
  let queueName = req.query.name;
  if (_.isEmpty(queueName)) {
    res.status(400).json({
      status: "bad request",
      message: "queue name cannot be empty"
    });
  } else {
    console.log("Fetching messages from queue = " + queueName);

    let receiveParams = {
      QueueUrl: env.SERVICE_ENDPOINT + "/" + env.ACCOUNT_ID + "/" + queueName,
      MessageAttributeNames: ["All"]
    };
    console.log(receiveParams);

    sqs.receiveMessage(receiveParams, (err, data) => {
      if (err) {
        res.status(500).json({
          status: "internal server error",
          error: err
        });
      } else {
        if (!data.Messages) {
          res.status(200).json({
            status: "ok",
            data: "no message in the queue"
          });
        } else {
          let items = [];
          _.each(data.Messages, function (message) {
            let ele = {
              id: message.MessageId,
              receiptHandle: message.ReceiptHandle,
              data: JSON.parse(message.Body)
            };
            items.push(ele);
          });
          res.status(200).json({
            status: "ok",
            messages: items
          });
        }
      }
    });
  }
};

// method7 - to delete the message from the sqs queue
const deleteMsg = (req, res) => {
  let id = req.query.id;
  let queueName = req.query.name;
  if (_.isEmpty(id) || _.isEmpty(queueName)) {
    res.status(400).json({
      status: "bad request",
      message: "receipt handle id or queue name cannot be empty"
    });
  } else {
    console.log("Deleting message id = " + id + " from queue");

    let deleteParams = {
      QueueUrl: env.SERVICE_ENDPOINT + "/" + env.ACCOUNT_ID + "/" + queueName,
      ReceiptHandle: id
    };
    console.log(deleteParams);

    sqs.deleteMessage(deleteParams, (err, data) => {
      if (err) {
        res.status(500).json({
          status: "internal server error",
          error: err
        });
      } else {
        res.status(202).json({
          status: "accepted",
          message: "message deleted from queue"
        });
      }
    });
  }
};

module.exports = {
  getConfigurationStatus,
  listQueues,
  createQueue,
  purgeQueue,
  publishMsg,
  receiveMsg,
  deleteMsg
};

2.1.4 Creating a routing class

Create a routing class that will be responsible to map the incoming requests from the client with the handler method. Add the below code to the file present in the routes folder.

routes.js

const express = require("express");
const router = express.Router();

const {
  getConfigurationStatus,
  listQueues,
  createQueue,
  publishMsg,
  receiveMsg,
  deleteMsg,
  purgeQueue
} = require("../handler/requests");

// get sqs config status
// endpoint - http://localhost:6001/status
router.get("/status", getConfigurationStatus);

// list sqs queues
// endpoint - http://localhost:6001/list
router.get("/list", listQueues);

// create a sqs queue
// endpoint - http://localhost:6001/create
router.post("/create", createQueue);

// purge a sqs queue
// endpoint - http://localhost:6001/purge
router.delete("/purge", purgeQueue);

// send message to sqs queue
// endpoint - http://localhost:6001/send
router.post("/send", publishMsg);

// receive message from sqs queue
// endpoint - http://localhost:6001/receive
router.get("/receive", receiveMsg);

// delete message from sqs queue
// endpoint - http://localhost:6001/delete
router.delete("/delete", deleteMsg);

module.exports = {
  appRoutes: router
};

2.1.5 Creating an index file

Create an index file that will be acting as the entry point for the application.

index.js

// application
const express = require("express");

const app = express();
app.use(express.json());

// application routes
const routes = require("./routes/routes");
app.use("/", routes.appRoutes);

// start application
const port = process.env.PORT || 6001;
app.listen(port, () => {
  console.log(`Service endpoint = http://localhost:${port}`);
});

3. Run the Application

To run the application navigate to the project directory and enter the following command as shown below.

Command

$ nodemon

If everything goes well the application will be started successfully on port – 6001 as shown in Fig. 2.

LocalStack SQS Node.js - app run
Fig. 2: Application run

4. Demo

You are free to use postman or any other tool of your choice to make the HTTP request to the application endpoints. Download the endpoints collection from the downloads section for an easy setup.

Application endpoints

// get sqs config status
// http get endpoint - http://localhost:6001/status

// list sqs queues
// http get endpoint - http://localhost:6001/list

// create a sqs queue
// http post endpoint - http://localhost:6001/create

// purge a sqs queue
// http delete endpoint - http://localhost:6001/purge

// send message to sqs queue
// http post endpoint - http://localhost:6001/send

// receive message from sqs queue
// http get endpoint - http://localhost:6001/receive

// delete message from sqs queue
// http delete endpoint - http://localhost:6001/delete

That is all for this tutorial and I hope the article served you with whatever you were looking for. Happy Learning and do not forget to share!

5. Summary

Localstack is the most popular aws cloud emulator which is used by individuals to run the aws applications on local machines and without connecting to the remote cloud provider. In this tutorial, we learned how to set up the localstack and integrate it with a simple nodejs application. You can download the source code and the postman collection from the Downloads section.

6. Download the Project

This was a tutorial to understand the aws cloud emulator popularly known as the localstack and its simple working in a nodejs application.

Download
You can download the full source code of this example here: LocalStack SQS Node.js Example

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button