Deploying a Real-Time Chat App on AWS Lightsail Containers | Next.js & WebSocket Backend

Deploying a Real-Time Chat App on AWS Lightsail Containers | Next.js & WebSocket Backend

AWS Lightsail Container Solution

GitHub Repo: https://github.com/prafulpatel16/realtime- chatapp-test.git

Video Link: https://youtu.be/kVH8n5w6Gkw

https://youtu.be/LlCu7K45cBI

Real-Time Use Case Solution: Real-Time Chat Application for Customer Support

Solution Overview:

To solve this problem, the company deploys a real-time chat application that integrates directly into their e-commerce website. The application consists of:

  • Next.js Frontend for the chat interface, allowing customers to engage with support agents or AI-driven chatbots.

  • WebSocket Backend to handle bi-directional, real-time communication between customers and the support team.

  • Dockerized Containers deployed on AWS Lightsail to ensure scalability and performance.

Architecture and Workflow

Components:

  1. Frontend (Next.js): A user-friendly chat widget embedded in the e-commerce site where customers can enter their inquiries. It allows real-time messaging with the support backend.

  2. Backend (WebSocket Server): Handles real-time communication between the frontend chat widget and either support agents or AI bots. All communication is managed through WebSocket connections, ensuring real-time message delivery without delays.

  3. Docker Containers on AWS Lightsail: The frontend and backend are deployed as separate Docker containers on AWS Lightsail, ensuring high availability, security, and cost-effectiveness.

Real-Time Workflow:

  1. Customer Initiates Chat: A customer browsing the e-commerce site initiates a chat session from the embedded chat widget.

  2. Connection Established: The Next.js frontend establishes a WebSocket connection to the backend running on AWS Lightsail.

  3. Real-Time Messaging: The customer’s queries are sent to the backend in real-time and are either directed to a human support agent or handled by an AI-driven chatbot.

  4. Instant Responses: The customer receives an instant response, reducing wait time from hours (via email) to seconds. Support agents can handle multiple conversations simultaneously, further improving efficiency.

  5. Scalability: During peak times, AWS Lightsail scales the container services to handle additional chat sessions without downtime.


Key Features and Benefits

  1. Instant Response Time: Real-time communication ensures that customer queries are answered immediately, leading to higher customer satisfaction.

  2. Scalable Solution: The system can scale to support thousands of concurrent users, especially during peak hours or events, using the elasticity of AWS Lightsail.

  3. Seamless User Experience: Customers can communicate with agents without leaving the website, providing a seamless experience that keeps them engaged and more likely to complete purchases.

  4. Cost Efficiency: By deploying in lightweight Docker containers on AWS Lightsail, the company minimizes infrastructure costs while maintaining high performance.

  5. Operational Efficiency: The chat system enables support agents to handle multiple customers in real time, reducing the need for hiring more agents.


Technical Stack

  • Frontend: Next.js for chat UI

  • Backend: WebSocket server in Node.js

  • Containerization: Docker

  • Cloud Platform: AWS Lightsail for cost-effective container deployment

  • Scalability: Autoscaling support during high-traffic periods

  • Security: SSL/TLS configuration for secure WebSocket communication

The real-time chat allows customers to receive instant answers to their queries, reducing wait times significantly and increasing customer satisfaction. The system is designed to scale dynamically during peak times, ensuring smooth operations during high-traffic periods such as sales.


 Step 1: Initialize the Project
Create a new directory for the chat application and initialize both Next.js and the WebSocket server.


mkdir realtime-chat-app
cd realtime-chat-app

### 1.1. Next.js Frontend Setup
First, let's create the Next.js frontend.

npx create-next-app@latest frontend

You can go through the prompts or use flags to set up the defaults:

npx create-next-app@latest frontend --ts --eslint --src-dir --tailwin

This command sets up a TypeScript Next.js project with ESLint, `src` directory structure, and TailwindCSS for styling (optional, but highly recommended).

### 1.2. WebSocket Backend Setup
Next, let's set up the backend in a `backend` folder, which will host the WebSocket server.


mkdir backend
cd backend
npm init -y
npm install ws express cors dotenv


The `ws` package is for WebSocket implementation, `express` is for running an HTTP server, and `dotenv` is for environment variables.

### Step 2: Directory Structure

Here is the full directory structure for the app:

realtime-chat-app/
│
├── backend/
│   ├── server.js         # WebSocket backend server
│   ├── package.json      # Node.js dependencies
│   └── .env              # Environment variables for WebSocket server
│
├── frontend/
│   ├── public/           # Static files
│   ├── src/              # Application logic
│   │   ├── components/   # React components for chat UI
│   │   ├── pages/        # Next.js pages
│   │   ├── utils/        # WebSocket connection utility
│   ├── package.json      # Next.js dependencies
│   ├── tailwind.config.js # TailwindCSS configuration (optional)
│   └── .env.local        # Frontend environment variables
└── README.md             # Project documentation
```

### Step 3: Backend WebSocket Server Setup

Inside the `backend` folder, create a file `server.js`:

```javascript
// backend/server.js
const express = require('express');
const { Server } = require('ws');
const cors = require('cors');
require('dotenv').config();

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

const PORT = process.env.PORT || 8080;

// HTTP server for serving WebSocket on the same port
const server = app.listen(PORT, () => {
  console.log(`WebSocket server running on port ${PORT}`);
});

// WebSocket server
const wss = new Server({ server });

wss.on('connection', (ws) => {
  console.log('Client connected');

  ws.on('message', (message) => {
    console.log(`Received: ${message}`);

    // Broadcasting the message to all connected clients
    wss.clients.forEach((client) => {
      if (client.readyState === ws.OPEN) {
        client.send(message);
      }
    });
  });

  ws.on('close', () => {
    console.log('Client disconnected');
  });
});
```

Create a `.env` file for the backend:

```plaintext
# backend/.env
PORT=8080
```

### Step 4: Next.js Frontend Setup

Go to the `frontend` directory and update `src/pages/index.tsx` to use WebSocket.

Here’s how the basic chat interface will look:

```tsx
// frontend/src/pages/index.tsx
import { useState, useEffect } from 'react';

const ChatPage = () => {
  const [messages, setMessages] = useState<string[]>([]);
  const [input, setInput] = useState('');
  const [ws, setWs] = useState<WebSocket | null>(null);

  useEffect(() => {
    const socket = new WebSocket(process.env.NEXT_PUBLIC_WS_URL as string);
    setWs(socket);

    socket.onmessage = (event) => {
      const newMessage = event.data;
      setMessages((prevMessages) => [...prevMessages, newMessage]);
    };

    socket.onclose = () => {
      console.log('WebSocket connection closed');
    };

    return () => {
      socket.close();
    };
  }, []);

  const sendMessage = () => {
    if (ws && input.trim()) {
      ws.send(input);
      setInput('');
    }
  };

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-2xl font-bold">Chat Room</h1>
      <div className="border rounded p-2 h-64 overflow-y-scroll">
        {messages.map((message, index) => (
          <div key={index} className="py-1">{message}</div>
        ))}
      </div>
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
        className="border p-2 rounded w-full my-2"
        placeholder="Type a message..."
      />
      <button
        onClick={sendMessage}
        className="bg-blue-500 text-white p-2 rounded"
      >
        Send
      </button>
    </div>
  );
};

export default ChatPage;
```

This `ChatPage` component listens to WebSocket messages and renders them in a chat box. Users can type a message and send it, which will be broadcast to all connected clients via the WebSocket server.

### Step 5: Environment Variables

In the `frontend` folder, create a `.env.local` file:

```plaintext
NEXT_PUBLIC_WS_URL=ws://localhost:8080
```

### Step 6: Tailwind CSS Setup (Optional)

If you chose to use Tailwind, add it to the frontend project:

```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
```

Update `tailwind.config.js`:

```javascript
// frontend/tailwind.config.js
module.exports = {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};
```

Then, add the following to your `src/styles/globals.css`:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

### Step 7: Run the Application

#### 7.1. Start WebSocket Backend

```bash
cd backend
npm start
```

#### 7.2. Start Next.js Frontend

```bash
cd frontend
npm run dev
```

The frontend will be available at `http://localhost:3000` and the WebSocket server at `ws://localhost:8080`.

### Step 8: Testing Routes and Final App
When you visit `http://localhost:3000`, you should see a chat interface. Messages sent will appear for all connected clients, thanks to the WebSocket setup.

### Final Routes
1. **Frontend Route**: 
   - `/` (Chat interface)
2. **WebSocket Backend Route**: 
   - `ws://localhost:8080` (WebSocket connection endpoint)

With these steps, you have a fully working chat application with a real-time WebSocket backend and Next.js frontend.

Step 7: Run the Application
7.1. Start WebSocket Backend

cd backend
npm start

7.2. Start Next.js Frontend
cd frontend
npm run dev

frontend

.env

NEXT_PUBLIC_WS_URL=ws://localhost:8080

backend

.env

# backend/.env
PORT=8080

# docker-compose.yml version: '3' services: websocket-backend: build: ./backend ports: - "8080:8080" environment: - PORT=8080 restart: always nextjs-frontend: build: ./frontend ports: - "3000:3000" environment: - NEXT_PUBLIC_WS_URL=ws://websocket-backend:8080 depends_on: - websocket-backend restart: always

docker-compose up --build

open frontend web app url

localhost:3000

Deploy docker containers on AWS Lightsail containers

Prerequisites:

  1. AWS CLI Installed: Ensure you have the AWS CLI installed. You can verify this by running:

     aws --version
     If it’s not installed, follow the AWS CLI installation guide.
    
  2. AWS CLI Configured: Make sure the AWS CLI is configured with valid credentials. If not, configure it withaws configure

    You’ll need to provide your AWS Access Key ID, Secret Access Key, Region, and Output format (e.g., json).

Steps to Create an ECR Repository:

  1. Create the ECR Repository: You can create an ECR repository using the following AWS CLI command:

     ecr create-repository --repository-name <repository-name> --region <region>
     Replace <repository-name> with the name of your repository and <region> with the AWS region where you want to create it. For example:
    
     ecr create-repository --repository-name my-realtime-chatapp --region us-east-1
    
  2. Example Output: Upon successful creation, you should get a response like this:

     jsonCopy code{
         "repository": {
             "repositoryArn": "arn:aws:ecr:us-east-1:123456789012:repository/my-realtime-chatapp",
             "registryId": "123456789012",
             "repositoryName": "my-realtime-chatapp",
             "repositoryUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-realtime-chatapp",
             "createdAt": 1644870241.0,
             "imageTagMutability": "MUTABLE",
             "imageScanningConfiguration": {
                 "scanOnPush": false
             },
             "encryptionConfiguration": {
                 "encryptionType": "AES256"
             }
         }
     }
    

    Steps to Push Docker Images to ECR:

    1. Authenticate Docker to Your ECR Registry

    Before pushing images to ECR, you need to authenticate Docker to your Amazon ECR registry.

    Run this command to authenticate (replace <your-region> with your AWS region, e.g., us-east-1):

     aws ecr get-login-password --region <your-region> | docker login --username AWS --password-stdin <your-account-id>.dkr.ecr.<your-region>.amazonaws.com
    

    For example, if your AWS region is us-east-1 and your account ID is 123456789012, the command would be:

     aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
    
  3. Amazon ECR repositories use a specific URL format for tagging images, which includes your AWS account ID, region, and repository name.

    You will need to tag both images (realtime-chatapp-test-nextjs-frontend and realtime-chatapp-test-websocket-backend) so they point to the correct ECR repository.

    Tag the Frontend Image:
     docker tag realtime-chatapp-test-nextjs-frontend:latest <your-account-id>.dkr.ecr.<your-region>.amazonaws.com/my-realtime-chatapp:frontend-latest
    

    Example:

     docker tag realtime-chatapp-test-nextjs-frontend:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-realtime-chatapp:frontend-latest
    
    Tag the Backend Imagedocker tag realtime-chatapp-test-websocket-backend:latest <your-account-id>.dkr.ecr.<your-region>.amazonaws.com/my-realtime-chatapp:backend-..

    Example:

     bdocker tag realtime-chatapp-test-websocket-backend:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-realtime-chatapp:backend-latest
    

    3. Push the Docker Images to ECR

    Now that the images are tagged, you can push them to the ECR repository.

    Push the Frontend Image:
     docker push <your-account-id>.dkr.ecr.<your-region>.amazonaws.com/my-realtime-chatapp:frontend-latest
    

    Example:

     docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-realtime-chatapp:frontend-latest
    
    Push the Backend Image:
     docker push <your-account-id>.dkr.ecr.<your-region>.amazonaws.com/my-realtime-chatapp:backend-latest
    

    Example:

     bashCopy codedocker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-realtime-chatapp:backend-latest
    

    Verify Your Images in ECR:

    Once the push is successful, you can verify that your images have been uploaded by going to the Amazon ECR Console and checking the my-realtime-chatapp repository.

When configuring a WebSocket connection on AWS Lightsail or any other environment, WebSockets generally use either:

  • ws:// for unencrypted WebSocket connections

  • wss:// for encrypted WebSocket connections (WebSockets over TLS/SSL, similar to HTTPS).

Which Protocol to Use:

  1. ws:// (Unencrypted WebSocket):

    • This is used for local development or when you don’t have SSL/TLS certificates configured on your server.

    • Example: ws://localhost:8080

  2. wss:// (Secure WebSocket):

    • This is the recommended protocol for production environments, including AWS Lightsail.

    • WebSocket connections secured with SSL/TLS (just like HTTPS).

    • Example: wss://your-domain.com/websocket

Deploy on AWS Lighsail

Create IAM Role:

EC2-Lightsail-ECR-Route53-Role

Create a Policy:

LightSailAccess

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:DeleteServiceLinkedRole",
                "iam:GetServiceLinkedRoleDeletionStatus"
            ],
            "Resource": "arn:aws:iam::*:role/aws-service-role/lightsail.amazonaws.com/AWSServiceRoleForLightsail*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CopySnapshot",
                "ec2:DescribeSnapshots",
                "ec2:CopyImage",
                "ec2:DescribeImages"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetAccountPublicAccessBlock"
            ],
            "Resource": "*"
        }
    ]
}

Setup AWS Lightsail

  1. Log In to AWS Lightsail:

  2. Create a Lightsail Container Service:

Deploy Frontend and Backend Containers

  1. Deploy Backend Container:

    • After creating the container service, select it and click Add container.

    • Under Container Name, enter a name like backend.

    • Set the Container image to your backend image (e.g., <your-username>/realtime-chat-backend:latest).

    • Set Open ports to 8080 (the port your WebSocket server listens on).

    • Click Save.

  2. Deploy Frontend Container:

    • Repeat the above steps for your frontend:

      • Add a container named frontend.

      • Set the Container image to your frontend image (e.g., <your-username>/realtime-chat-frontend:latest).

      • Set Open ports to 3000 (the port your Next.js app listens on).

    • Click Save and Deploy.

Deploy both the containers within one Lightsail Container service

Access the Webapp and verify on the browser


Conclusion

By deploying a real-time chat application using Next.js, WebSockets, and AWS Lightsail containers, the e-commerce company solved their customer support issues, scaled their infrastructure efficiently, and significantly boosted both customer satisfaction and revenue. This real-time business solution is a powerful use case for modern e-commerce platforms looking to optimize user engagement and operational efficiency.