Think with Enlab

Diving deep into the ocean of technology

Stay Connected. No spam!

How to create real-time chat applications using WebSocket APIs in API Gateway

 

My experience developing real-time messaging applications leveraging AWS services inspires me to share with the community to hope that it helps something learning how to build scalable and reliable real-time messaging applications. The demo project in this article can be deployed using the following AWS services:

1. API Gateway is adaptable to manage the APIs resource.
2. It is the most appropriate procedure when running with multiple EC2 instances behind a load balancer.
3. WebSocket APIs in Amazon API Gateway come in handy to build secure, real-time communication applications without providing or managing servers to handle connections or large-scale data exchanges.

But before diving deeper into the how-to, it’s essential to go over some AWS WebSocket API concepts to facilitate us on the same page.

 

WebSocket API Concepts

What is WebSocket API?

While WebSocket is a computer communication protocol, WebSocket API is defined as advanced technology mainly used to create real-time applications, such as chat apps, collaboration platforms, streaming dashboards. These applications take advantage of two-way/bidirectional communication between a server and users’ browsers.

 

What is WebSocket API in AWS API Gateway?

It is a collection of WebSocket routes and route keys integrated with backend HTTP endpoints, Lambda functions, or other AWS services. Using API Gateway features facilitates all aspects of the API lifecycle, from creation to monitoring your production APIs.

 

What are an AWS WebSocket route and a route key?

A WebSocket route in API Gateway is employed to direct incoming messages to a specific integration. When you elaborate your WebSocket API, a route key and an integration backend are stipulated. The route key is a value of a JSON property in the message body. The integration backend is invoked when the route key is matched in an incoming message.

For example, if your JSON messages embody an action property, and you want to implement various actions based on this property, your route selection expression might be ${request.body.action}. Let’s look at an example of the incoming message from a client:
{"action":"sendMessage","message":"Hello world"}

 

Three predefined routes can be used: $connect, $disconnect, and $default. Also, you are capable of creating custom routes.

  • $default route is a particular routing value that can be used in the fallback route.
  • $connect route is implemented when a persistent connection between a WebSocket API and the client is being initiated.
  • $disconnect route is completed after clients or the server disconnects from API.
  • $custom route is created if you want to invoke a specific integration based on message content.

 

How to create and test a real-time chat application in the development environment

To make it easy to understand, let’s explore a simple chat room application that does the following:

  • Clients take part in the chat room as they connect to the WebSocket API.
  • Users can send messages to the room, and other users can receive a new message in real-time.
  • Disconnected clients are removed from the chat room.

The following diagram illustrated how it works:

Real-Time Chat Architecture

  • Connect to the chat room:
    (1) Open a WebSocket correlation between clients and servers.
    (2) API Gateway calls the $connect route when having new connections.
    (3) Backend received a callback request from AWS WebSocket via Ngrok that exposes local servers to the public internet in this demo.
    (4) The Connect method of the server API is involved.
    (5) Store a WebSocket connection in the memory of the webserver.
  • Send a new message to the chat room:
    (6) The sendMessage function is involved when users enter a new message into the chat room.
  • Transfer message:
    (7) WebSocket server receives a new message after the app server transmits data to the active connections stored in server memory. It transfers the messages to all connected clients using the new API Gateway Management API.
    (8) Clients are listening for incoming data via the WebSocket.onmessage property.
  • Leave chat room:
    (9) The WebSocket method close() is called from the clients. So API Gateway calls $onDisconnect, then the Disconnect method of the server API is involved and removes that connection out of the server memory.

 

Step 1: Create a WebSocket API

Let’s follow AWS guidelines to create a WebSocket API in API Gateway. Below is the preview of the setting for the demo.

 

 

Then, set up a WebSocket API integration request:

(1): Selecting a route key to integrate to the backend.

(2): Specifying HTTP endpoint to invoke. For more information about Integration Type, follow this tutorial to Set up a WebSocket API integration request in API Gateway.

(3): Configuring how to transform the incoming message’s payload includes the necessary data before sending them to the back-end integration.

For step (2), we are still not implementing the backend, so there is no HTTP endpoint to invoke. To have another endpoint instead of testing purposes, we will use https://webhook.site to generate a unique URL that quickly inspects any incoming HTTP request.

Next, we follow the steps for the WebSocket API integration request above; the details of the $connect route’s configuration are shown below.

 

 

For the request template, the ID is stored in the server’s memory when a new connection integrates with the server. For more information about those parameters, you can read API Gateway WebSocket API mapping template references.

Similar to the $connect route configuration, we have the following one for the $disconnect route.

 

 

And $sendMessage will be configured like this:

 

The connection and message property are required before sending it through to the server.

 

Step 2: Set up a WebSocket API integration responses

We must set up at least one integration response because the non-proxy integration is being used at the integration request. You can read more about Setting up WebSocket API integration responses in API Gateway.

Here is the configuration for the $connect route key. Every value is initialized by default.

 

 

Step 3: Deploy the WebSocket API

For the first-time deployment, we need to create a stage, such as “dev,” and give a sample description.

To test a WebSocket API, use wscat to connect to a WebSocket API and send messages to it. You can refer to the article for how to use wscat to connect to a WebSocket API and send messages to it.

 

Implement backend code

After creating a web API with ASP.NET Core, add a new controller WebSocketDemoController that has implemented the three methods Connect, Disconnect, SendMessage that map with 3 WebSocket API route keys $connect, $disconnect, $sendMessage.


[Route("websocket")]
[ApiController]
public class WebSocketDemoController : ControllerBase
	{
		/// <summary>
		/// The memory storage for the list of connectionIds
		/// </summary>
		private static readonly List<string> ConnectionIds = new List<string>();
		
		private readonly IWebSocketService _webSocketService;

		public WebSocketDemoController(IWebSocketService webSocketService)
		{
			_webSocketService = webSocketService;
		}

		/// <summary>
		/// Having a new connection from the client.
		/// </summary>
		[HttpPost("connect")]
		[AllowAnonymous]
		public void Connect([FromBody] ConnectionModel connection)
		{
			// Store connectionId in memory data
			ConnectionIds.Add(connection.Id);

			// The connection.UserId can be used to identify which user using that connectionId
		}

		/// <summary>
		/// There is a disconnected connection from the client.
		/// </summary>
		[HttpPost("disconnect")]
		[AllowAnonymous]
		public void Disconnect([FromBody] ConnectionModel connection)
		{
			// Remove connectionId out of memory data
			ConnectionIds.Remove(connection.Id);
		}

		/// <summary>
		/// The clients send messages to the server
		/// </summary>
		[HttpPost("message")]
		[AllowAnonymous]
		public async Task SendMessage([FromBody] TextMessageModel input)
		{
			// Get the connectionIds of other users in the chat room
			var connectionIds = ConnectionIds.Where(id => id != input.ConnectionId).ToList();

			// Sends the message to all connected clients using the new API Gateway Management API 
		        // that excepts the current connectionId
			await _webSocketService.SendMessage(connectionIds, "newMessage", input.Message);
		}
	}

For the Connect method, the ConnectionModel class will be mapped with the Request Template of $connect from the WebSocket API integration request.


public class ConnectionModel
{
    public string Id { get; set; }
    public string Domain { get; set; }
    public string Stage { get; set; }
}

Similarly, the TextMessageModel class will be mapped with the Request Template of the $sendMessage route.


public class TextMessageModel.
{
    public string ConnectionId { get; set; }
    public string Message { get; set; }
}

Once the backend API works, we will use backend APIs defined on WebSocketDemoController for the HTTP endpoint instead of the URL of WebSite.Hook.

 

Implement frontend code

Create an index.html file with the source code below.


<h1>Chat Room</h1>
<pre id="messages" style="height: 400px; overflow-y: scroll"></pre>
<input type="text" id="messageBox" placeholder="Type your message here" style="display: block; width: 100%; margin-bottom: 10px; padding: 10px;" />
<button id="send" title="Send Message!" style="width: 100%; height: 30px;">Send Message</button>

<script>
  (function() {
    const sendBtn = document.querySelector('#send');
    const messages = document.querySelector('#messages');
    const messageBox = document.querySelector('#messageBox');

    let ws;

    function showMessage(message, isOutgoing) {
      messages.textContent += isOutgoing ? `\n\n You: ${message}` : `\n\n Another User: ${message}`;
      messages.scrollTop = messages.scrollHeight;
      messageBox.value = '';
    }

    function init() {
      if (ws) {
        ws.onerror = ws.onopen = ws.onclose = null;
        ws.close();
      }

      // connect to Websocket server
      ws = new WebSocket("your_websocket_endpoint");
      ws.onopen = (e) => {
        console.log('Connection opened!');
      }
	  
      // An event listener to be called when a message is received from the server
      ws.onmessage = function (e) {
        var data = JSON.parse(e.data);
        showMessage(data.payload, false);
      }
	 
      // An event listener to be called when the connection is closed.
      ws.onclose = function() {
        ws = null;
        console.log('Connection closed!');
        }
      }

      sendBtn.onclick = function() {
        if (!ws) {
          showMessage("No WebSocket connection :(");
          return ;
        }


        var data = JSON.stringify({
        "action": "sendMessage",
        "message": messageBox.value
          });

        // Send a new message to the sendMessage route key
        ws.send(data);

        // Show the outgoing message
        showMessage(messageBox.value, true);
      }

    init();
  })();
</script>

 

Step 4: Demo

To demo the app, we run the index.html files in two browser tabs: a normal tab and an incognito tab as two different users.

 

For a better understanding, you can discover my demo source code here.

 

Essential Notes for WebSocket APIs

API Gateway can handle messages up to 128 KB with a maximum size of 32 KB per frame. If a message is larger than 32 KB, you must divide it into multiple frames, each 32 KB or smaller. If a larger message is attempted to send, the connection will be closed with code 1009.

 

Final thoughts

I recommend using AWS Websocket APIs for real-time applications rather than building your own WebSocket APIs. Because it is stable on production, and you no longer worry about hosting, load balancing, and scalability in the future. You, therefore, can focus on implementing other essential features to serve your users. Another alternative is to use Azure SignalR Service from Microsoft Azure for the same purpose.

I hope this article is helpful to you. Happy coding!

 

 

References

About the author

Trong Pham

I’m a Software Engineer at Enlab Software. I am interested in .NET programming, SQL Server, and system architectures. What motivates me to code and create software products is helping people improve their business efficiency. When not at work, I'm into playing music or football.
Frequently Asked Questions (FAQs)
What is WebSocket API in AWS API Gateway, and why is it important for real-time chat applications?

WebSocket API in AWS API Gateway is a technology used to create real-time applications like chat apps. It enables two-way communication between a server and users  browsers. WebSocket APIs are crucial for real-time chat applications as they provide the infrastructure for secure, efficient, and scalable real-time communication without managing servers.

How does the WebSocket API route messages in Amazon API Gateway, and what are route keys?

WebSocket API routes messages to specific integrations using route keys. Route keys are values in the message body that determine where the message should be directed. For example, you can route messages based on properties like action. Understanding route keys is essential for creating customized real-time chat experiences.

What are the key components of a real-time chat application architecture using WebSocket APIs?

A real-time chat application using WebSocket APIs consists of components like WebSocket correlations between clients and servers, the connect route for new connections, the disconnect route for disconnecting clients, and the custom route for specific integrations. These components ensure seamless real-time communication.

How can I set up and deploy a WebSocket API for a real-time chat application in API Gateway?

Setting up a WebSocket API involves creating routes, integration requests, integration responses, and stages. Deploying the WebSocket API includes creating a stage, such as dev, and testing the WebSocket API using tools like wscat. Properly configuring these elements is essential for a successful deployment.

What is the role of backend and frontend code in building a real-time chat application with WebSocket APIs?

The backend code handles WebSocket connections, message handling, and user management. It includes methods like Connect, Disconnect, and SendMessage. The frontend code is responsible for user interfaces, message input, and WebSocket connection management. Both components work together to create a functional real-time chat application.

Up Next

July 05, 2024 by Dat Le
In the rapidly evolving world of software development, Big Data stands out as a transformative force....
June 27, 2024 by Dat Le
In today's rapidly evolving digital landscape, secure coding practices are paramount to safeguarding applications from a...
June 20, 2024 by Dat Le
In the rapidly evolving digital landscape, the role of User Interface (UI) and User Experience (UX)...
June 17, 2024 by Dat Le
In the dynamic world of software development, one element has emerged as crucial to success: User...

Can we send you our next blog posts? Only the best stuffs.

Subscribe