Project: Creating a Scalable Serverless Chat Application on AWS

Project: Creating a Scalable Serverless Chat Application on AWS

Β·

10 min read

Here I come again with yet another AWS project. As an aside, deciding on what AWS projects to take on at a given point in time is a full-time job on its own as there is a plethora of options to pick from. While cruising through different AWS Project streets, I decided to make a stop on "Upper Serverless Street" (pun intended). Since I stopped on "Upper Serverless Street", today's edition of AWS projects will be completely serverless. Hope you're looking forward to this with excitement as much as I am given that I am a big fan of serverless. Now that your enthusiasm is ascertained (forgive me for assuming your enthusiasm), the serverless project we are going to be working on is............. (Drumroll πŸ₯πŸ₯πŸ₯πŸ₯) a serverless chat application. In today's edition, we'll leverage the capabilities of AWS serverless services like AWS Lambda and API Gateway to build a chat application in the AWS cloud that can effortlessly scale to meet high demand.

To make sure everyone is aligned in their understanding of Serverless computing on AWS, here is a concise overview using an analogy to enhance clarity and retention. Imagine AWS serverless computing as a restaurant where you don't have to worry about the kitchen, staff, or even the dining area. You're the maestro in the kitchen, free to craft your culinary works of art. When a hungry guest arrives (an event), a team of invisible waitstaff (AWS Lambda) swiftly prepares and serves the dish, ensuring the customer leaves satisfied. You're only charged for the ingredients you use, not for keeping the entire restaurant staffed 24/7. It's like orchestrating a symphony without worrying about the musicians, as AWS provides an ensemble ready to perform your composition the moment the curtain rises. The magic of AWS serverless is in letting you be the creative genius without the hassle of running the entire restaurant. I'm confident you grasped the underlying point: Serverless computing means running code or applications without having to manage servers. With that out of the way, let's continue exploring "Upper Serverless Street" starting with the project's prerequisites.

Prerequisites

  • An AWS account with an IAM user that has administrative permissions.

  • A basic understanding of a high-level programming language such as Python is recommended but not required.

  • Basic understanding of databases and APIs.

  • Grit and persistence. I like to include this as a prerequisite in all projects I work on as they are both needed to accomplish anything worthwhile in life including working on cloud projects.

With the prerequisites all outlined, log into your AWS account (the supercar you are going to be cruising around "Upper Serverless Street" in) using the IAM user's credentials and let's begin.

Lap One: Create A Lambda Function

The very first thing we need to do is to create a lambda function. To learn more about Lambda, check out this wonderful article on the topic. If you are ready to go, navigate to the lambda management console window. Once you are there click on the Create a function button as shown below.

In the screen that appears, fill in the details as in the image that follows.

There you have it. Our Lambda function is created successfully. We are going to leave it as it is for now and move on to the next step.

πŸ’‘
I changed the name of this function to SendMessage so make sure you update yours too to avoid unnecessary confusion down the road.

Lap Two: Create an API

This lap is all about creating an API using API Gateway which is a managed service that makes it fun and easy for developers to create, publish, maintain, monitor, and secure APIs to back-end systems running on EC2, AWS Lambda or any publicly addressable web service. If you have a curious mind and a desire to explore API Gateway further, I recommend taking the time to read this article. Feel free to return once you've finished.

If you went to read the article, "Welcome back". Let's press on. Within your management console, navigate to the API Gateway window. The API required for this project is a WebSocket API. If you'd like a clearer understanding of what that entails, here is a simple analogy to illuminate the idea. Think of a WebSocket API as a direct phone line between two people who want to have a conversation. With traditional communication, you'd send a letter (like an HTTP request) and wait for a response in another letter (like an HTTP response). But with WebSocket, it's like having a continuous phone call open. You have the ability to engage in both speaking and listening simultaneously, much like a live, back-and-forth conversation. It's as if you're both on the line, ready to chat, without the need to dial and hang up for every message. This makes it ideal for rapid exchanges when you need to instantly share information, similar to having a real-time chat with a friend. So that's that about that. I am sure you understand it better and hopefully got a clue as to why we are building a WebSocket API. Now let's move on

In the API Gateway console, click on the Build button under WebSocket APIs as shown below.

Fill in the details in the same manner as I've illustrated in the image above. In case you don't understand what the Route Selection expression section means, it is a configuration option used to determine how incoming WebSocket messages are routed to the appropriate Lambda function or integration based on the content of the message. In case that still doesn’t make any sense, here is an example to help you understand. Suppose you have messages with a JSON structure like this:

{
  "action": "sendMessage",
  "message": "Hello, how are you?"
}

You can create a route selection expression like $request.body.action to route messages based on their "action" attribute as we have done in our case. Then, you'd define route actions for different "action" values, such as:

  • Route Key: sendMessage

  • Integration: Lambda function to handle the messages

This way, incoming messages will be routed to the appropriate backend Lambda functions based on their "action" attribute. Now let's go to add routes.

As you can see we have three routes: the $connect, $disconnect and a sendMessage route. Each of these routes will be integrated with a different lambda function so in addition to our SendMessage, create two new lambda functions named: Connect and Disconnect. The SendMessage function will be integrated with the sendMessage route while the Connect and Disconnect functions will be integrated with the $connect and $disconnect routes respectively.

In the Attach integration tab, select Lambda as the integration type for all the routes we have then proceed to locate the appropriate lambda function for each route.

In the Add Stage tab that appears, leave everything as it is and click on Next. Now scroll to the button and click on the Create and deploy button. With that, our websocket API and routes are created and deployed. To see if we can connect to the API we just created, go to the Stages tab, and click on the production stage which is the only stage we have to open it. Copy the WebSocket URL and paste it into this website. Here are the steps in pictures for you to better understand.

Once you have copied and pasted the WebSocket URL, click on Connect. If the connection is successful, you'll see this:

Go back to the Stages tab and copy and paste the Connection URL of the WebSocket API somewhere because we are going to need it soon in our lambda function.

Lap Three: Adding code to our Lambda functions

The time has come to add code to our lambda functions. Let's start with the SendMessage function. I am going to give you the code and then explain what it does in summary.

import json
import urllib3
import boto3

client = boto3.client('apigatewaymanagementapi', endpoint_url="xxxxxxxxxx.com/production")

def lambda_handler(event, context):
    print(event)

    #Extract connectionId from incoming event
    connectionId = event["requestContext"]["connectionId"]

    #Do something interesting... 
    responseMessage = "I am fine"

    #Form response and post back to connectionId
    response = client.post_to_connection(ConnectionId=connectionId, Data=json.dumps(responseMessage).encode('utf-8'))
    return { "statusCode": 200  }

The code essentially defines a Lambda function that handles WebSocket events. It extracts the connectionId from the event, performs some custom logic (represented by the responseMessage), and then sends a response back to the WebSocket client using the API Gateway Management API. The function is designed to be invoked when a client sends a message. Now copy and paste the code into the lambda.py file of the SendMessage lambda function (a better version will be to type out the code line by line for better understanding). Make sure you replace "xxxxxxxxxx.com/production" in the code with the connection URL you copied earlier. Remove /@connections from the end of the URL as it is not needed.

When you add the code to the lambda.py file of the lambda function, be sure to save and deploy it to apply the changes made. There is one thing left to do with our Lambda function. Since the API Gateway management API is what gives our lambda function the ability to access our API Gateway APIs, let's attach permission for it to our lambda function execution role. Here is how to locate the execution role. In the lambda function console, navigate to the appropriate lambda function then click on the Configurations tab and click on Permissions as shown below.

Once in the Permissions tab, click on the role name to open it in the IAM console. When that is done, click on the Add Permissions dropdown as shown below and then click on Attach Policies.

The policy we are going to add is the AmazonAPIGatewayInvokeFullAccess policy so search that up and then add it to the Lambda function execution role by doing these:

You have to make a small code change to your lambda function and then deploy it to apply the added policy to the function's execution role. So go ahead and do a small code change that won't affect the functioning of the code, save the changes and then deploy it.

Now the code for our Connect and Disconnect lambda functions:

import json

def lambda_handler(event, context):
    print(event)
    print("****")
    print(context)
    return { "statusCode": 200 }

If you are confused about what the code does, don't worry I got you with this step-by-step explanation.

  • It prints the content of the event parameter. The event parameter contains data related to the triggering event that caused the Lambda function to execute (the event in our case is either when a client connects to or disconnects from our API).

  • It prints a line of asterisks (****) as a separator.

  • It prints the content of the context parameter. The context parameter provides information about the runtime environment of the Lambda function, such as AWS request ID, function name, and more.

  • Finally, it returns a JSON object with a "statusCode" of 200 to indicate a successful response. This is a common HTTP status code for successful requests.

Let's proceed to the next step.

Lap Four: Testing our Send Message Route

To make sure our Lambda function is working as integration for our API and a client can send messages, we are going to test our send message route. So go back to PieSocket with the WebSocket connection still open and try sending a message in the format:

{
  "action": "sendMessage",
  "message": "Hello, how are you?"
}

As you can see the response we get back is "I am fine" which is the response message we specified in our lambda function. This means our API is working as it should. Hurray we have completed this project!!

Lap Five: Cleanup

Now make sure you go back and delete all the resources you created for this project before calling it a day.

Outro

Congratulations on driving to the end of Upper Serverless Street and completing the project. The project is small but not slight. It represents a significant milestone in your journey towards mastering serverless technologies on AWS. As you contemplate your achievements thus far, remember that this is just the beginning. Serverless architecture has opened doors to scalability, cost-efficiency, and rapid development that were once unimaginable. Continue exploring and building with curiosity, because the serverless world is ever-evolving, and there's always something new to discover. Whether you're developing chat applications, complex backend systems, or anything in between, may your serverless endeavours be efficient, your functions be stateless, and your deployments be seamless. Happy coding!