কম্পিউটার টিউটোরিয়াল

Upstash Kafka, Redis এবং Next.js দিয়ে রিয়েল-টাইম চ্যাট অ্যাপ তৈরি করা

প্রজেক্ট বিবরণ

এই ব্লগ পোস্টে, আমরা একটি মেসেজিং অ্যাপ্লিকেশন তৈরি করব যা ব্যবহারকারীদের মেসেজ ক্লায়েন্ট এবং চ্যাট রুম তৈরি করতে দেবে। এছাড়া, ব্যবহারকারীরা পylene মেসেজগুলো অ্যাক্সেস করতে পারবে।

প্রজেক্টটি দুটি পেজ নিয়ে গঠিত। প্রথম পেজটি ক্লায়েন্ট রেজিস্ট্রেশনের জন্য ডেডিকেটেড, যেখানে আপনি একাধিক ক্লায়েন্ট অনন্য নাম দিয়ে তৈরি করতে পারেন।

Upstash Kafka, Redis এবং Next.js দিয়ে রিয়েল-টাইম চ্যাট অ্যাপ তৈরি করা

যখন আপনি একটি ক্লায়েন্টের ইউজারনেমে ক্লিক করবেন, আপনি সেই নির্দিষ্ট ইউজারের সাথে যুক্ত চ্যাটরুম ক্লায়েন্টে নেওয়া হবেন।

Upstash Kafka, Redis এবং Next.js দিয়ে রিয়েল-টাইম চ্যাট অ্যাপ তৈরি করা

চ্যাট অ্যাপ্লিকেশনের লজিক নিম্নরূপ:

ব্যবহারকারীরা ইন্ডেক্স পেজে একাধিক ক্লায়েন্ট তৈরি করতে পারেন, প্রতিটির একটি অনন্য ইউজারনেম সহ। একটি ক্লায়েন্টের ইউজারনেমে ক্লিক করলে সেই ইউজারের জন্য একটি পৃথক পাথ সহ একটি নতুন ট্যাবে আলাদা ক্লায়েন্ট খুলবে।

প্রতিটি ক্লায়েন্ট একটি মেসেজ সার্ভারের সাথে WebSocket সংযোগের মাধ্যমে সংযুক্ত থাকবে। যখন ক্লায়েন্টে একটি নতুন মেসেজ তৈরি হবে, এটি সেই ক্লায়েন্টের সাথে যুক্ত মেসেজ সার্ভারে পাঠানো হবে।

মেসেজ সার্ভারগুলো মেসেজ ট্রাফিক হ্যান্ডেল করবে। যখন একটি ক্লায়েন্ট WebSocket সংযোগের মাধ্যমে একটি মেসেজ পাঠাবে, সার্ভারটি এটি একটি Kafka ব্রোকারে ডাইরেক্ট করবে। প্রতিটি মেসেজ সার্ভার একটি NodeJS থ্রেড চালাবে ইনকামিং মেসেজ হ্যান্ডল করার জন্য। যখন একটি মেসেজ কনসিউম হবে, এটি বিদ্যমান WebSocket সংযোগের মাধ্যমে ক্লায়েন্টদের পাঠানো হবে। ক্লায়েন্ট-সাইডে ইনকামিং মেসেজ কনসিউম করার জন্য, আমরা react-use-websocket লাইব্রেরি ব্যবহার করব।

অ্যাপ্লিকেশন Upstash Redis ব্যবহার করবে মেসেজ ইতিহাস সংরক্ষণ করতে। যখন একটি মেসেজ Kafka-তে প্রোডিউস হবে, এটি Redis ডেটাবেসেও পERSIST করা হবে। একটি নতুন ক্লায়েন্ট তৈরি হলে, পুরনো মেসেজগুলো Upstash Redis থেকে রিট্রিভ করা হবে এবং চ্যাট ডিসপ্লেতে রেন্ডার করা হবে।

অ্যাপ্লিকেশনের সাধারণ ওভারভিউ:

নোট: আমাদের ইমপ্লিমেন্টেশনে, আমরা ডেমো উদ্দেশ্যে একক মেসেজ সার্ভার তৈরি করব, কেউ সার্ভার সংখ্যা বাড়াতে পারে লোড হ্যান্ডল করার জন্য।

Upstash Kafka, Redis এবং Next.js দিয়ে রিয়েল-টাইম চ্যাট অ্যাপ তৈরি করা

ডেমো

অ্যাপটির ডেমো এখানে পাওয়া যাবে। অ্যাপ্লিকেশন বর্তমান সংস্করণ Fly-এ ডিপ্লয়েড।

শুরু করা যাক

চ্যাট অ্যাপ্লিকেশন বানানোর ধাপগুলো:

  1. Upstash Redis ডেটাবেস তৈরি করা
  2. Upstash Kafka ক্লাস্টার তৈরি করা
  3. Next অ্যাপ্লিকেশন তৈরি করা (ফ্রন্টএন্ড)।
  4. WebSocket মেসেজ সার্ভার তৈরি করা।
  5. Fly.io-এ অ্যাপ্লিকেশন ডিপ্লয় করা

Upstash Redis ডেটাবেস তৈরি করা

Upstash Console-এ নেভিগেট করুন এবং লগ ইন করুন, তারপর Redis ট্যাবে, Create Database বাটনে ক্লিক করুন।

Upstash Kafka, Redis এবং Next.js দিয়ে রিয়েল-টাইম চ্যাট অ্যাপ তৈরি করা

বাস! আমাদের Redis ব্যবহার করার জন্য রেডি! আমরা কনসোলে ফিরে যাব শংসাপত্রের জন্য।

Upstash Kafka ক্লাস্টার তৈরি করা

এখন, Kafka ট্যাবে স্যুইচ করুন, এবং Create Cluster বাটনে ক্লিক করুন। ক্লাস্টার নাম টাইপ করুন এবং এগিয়ে যান। তারপর, Kafka টপিক তৈরি করুন এবং কনফার্ম করুন।

Upstash Kafka, Redis এবং Next.js দিয়ে রিয়েল-টাইম চ্যাট অ্যাপ তৈরি করা

Next App তৈরি করা

প্রথমে, আপনার টার্মিনাল থেকে আপনার অ্যাপ্লিকেশনের রুট ফোল্ডার তৈরি করুন এবং সেখানে নেভিগেট করুন। আমরা Next অ্যাপ এবং সার্ভার দুটোই এই ফোল্ডারে রাখব।

mkdir chat-app
cd chat-app

তারপর, আপনার next অ্যাপ তৈরি করুন।

$ npx create-next-app@latest
✔ What is your project named? … next-chat-app
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … No
✔ Would you like to use `src/` directory? … No
✔ Would you like to use App Router? (recommended) … No
✔ Would you like to customize the default import alias? … No

শংসাপত্র হ্যান্ডল করা

আমরা একটি .env ফাইল তৈরি করব শংসাপত্র সংরক্ষণ করার জন্য। আমাদের বারবার শংসাপত্র কপি-পেস্ট করতে হবে না, আমরা শুধু এই ফাইল থেকে ইমপোর্ট করব।

প্রথমে, .env ফাইলটি তৈরি করুন।

তারপর, Redis কনসোলে নেভিগেট করুন, এবং UPSTASH_REDIS_REST_URL এবং UPSTASH_REDIS_REST_TOKEN শংসাপত্র কপি/পেস্ট করুন .env ফাইলে।

Upstash Kafka, Redis এবং Next.js দিয়ে রিয়েল-টাইম চ্যাট অ্যাপ তৈরি করা

অবশেষে, Kafka কনসোলে স্যুইচ করুন, এবং UPSTASH_KAFKA_REST_URL, UPSTASH_KAFKA_REST_USERNAME, UPSTASH_KAFKA_REST_PASSWORD ট্রান্সফার করুন।

Upstash Kafka, Redis এবং Next.js দিয়ে রিয়েল-টাইম চ্যাট অ্যাপ তৈরি করা

এখন, আপনার .env ফাইলটি নিচের মত দেখাবে

UPSTASH_REDIS_REST_URL=...
UPSTASH_REDIS_REST_TOKEN=...
UPSTASH_KAFKA_REST_URL=...
UPSTASH_KAFKA_REST_USERNAME=...
UPSTASH_KAFKA_REST_PASSWORD=...

এখন শংসাপত্র কনফিগার হয়ে গেলে, আমরা অ্যাপ্লিকেশনের সাথে এগিয়ে যেতে পারি।

ক্লায়েন্ট রেজিস্ট্রেশন পেজ

ইন্ডেক্স পেজে ক্লায়েন্ট রেজিস্ট্রেশন/ক্রিয়েশন অপারেশন থাকবে। যখন একটি ইউজারনেম সাবমিট করা হবে, একটি নতুন ক্লায়েন্ট তৈরি হবে এবং Current Clients টেবিলের অধীনে লিস্ট হবে।

import { useState } from "react";
import Link from "next/link";
import { Redis } from "@upstash/redis";
import styles from "@/styles/Home.module.css";
export default function Home() {
  const [usernameInput, setUsernameInput] = useState<string>("");
  const [usernameList, setUsernameList] = useState<string[]>(Array<string>);
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    const inputValue: string = e.target.value;
    setUsernameInput(inputValue);
  };
  const addUsernameClient = (e: React.FormEvent<HTMLFormElement>): void => {
    e.preventDefault();
    setUsernameList([...usernameList, usernameInput]);
    setUsernameInput("");
  };
  return ( <div className={styles.container}> <div className={styles.welcomeSection}> <h1>Welcome to the demo message app!</h1> <p> This application uses Upstash Kafka for message passing, and Upstash Redis for state management. <br /> <br /> To get started, create several clients by typing in unique usernames to the input section below and submitting. <br /> <br /> The usernames will be added to the list of current clients. Click on a username to open a new tab with that client&apos;s message display. <br /> <br /> You can have multiple sessions open at once. </p> </div> <form className={styles.formSection} onSubmit={addUsernameClient}> <input type="text" className={styles.formInput} value={usernameInput} onChange={handleInputChange} ></input> <button className={styles.formSubmit} type="submit"> Create the client! </button> </form> <div className={styles.clientListSection}> <p className={styles.clientListHeader}>Current Clients</p> <div className={styles.clientList}> {usernameList.map((username, i) => { return ( <Link href={`/user/${username}`} key={`${i}`} className={styles.userClient} target={"_blank"} > <p>{username}</p> </Link> ); })} </div> </div> </div> ); }

আপনি যদি চান অ্যাপ রিলোড হলে চ্যাট ইতিহাস রিসেট হয়, তবে নিচের ফাংশনটি ব্যবহার করতে পারেন:

export async function getServerSideProps() {
  const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, });
  await redis.del("messagesList");
  return { props: {}, };
}

এইভাবে, ইন্ডেক্স পেজ চালানোর জন্য রেডি। next-chat-app ফোল্ডারে npm run dev কমান্ড চালিয়ে ইন্ডেক্স পেজ লাইভ দেখুন।

মেসেজ ক্লায়েন্ট পেজ

ক্লায়েন্টের জন্য ডাইনামিক রাউটিং ইমপ্লিমেন্ট করতে, আমরা /pages/user/[username].tsx নামে একটি ফোল্ডার তৈরি করব। এই ফোল্ডার স্ট্রাকচার আমাদের প্রতিটি ক্লায়েন্টের জন্য তাদের ইউজারনেমের উপর ভিত্তি করে ডাইনামিক রুট তৈরি করতে দেবে।

এখানে ক্লায়েন্টের মেইন কম্পোনেন্ট। এই কম্পোনেন্টটি মেসেজ লিস্ট, ইউজারনেম ইত্যাদির জন্য স্টেট ধারণ করবে। আমরা WebSocket থেকে মেসেজ, কানেকশন এবং ডিসকানেকশন ইভেন্ট তৈরি করতে useWebSocket হুক ব্যবহার করছি। যখন একটি মেসেজ ইভেন্ট ইমিট হবে, মেসেজ মেসেজ লিস্টে যোগ হবে এবং MessageDisplay কম্পোনেন্ট রি-রেন্ডার হবে।

import { useState } from "react";
import { useRouter } from "next/router";
import { Redis } from "@upstash/redis";
import useWebSocket from "react-use-websocket";
import styles from "@/styles/Home.module.css";
type Message = { id: number; sender: string; text: string; };
export default function MessageApp(props: { messagesData: Message[] }) {
  const { messagesData } = props;
  const { username } = useRouter().query;
  const [inputText, setInputText] = useState<string>("");
  const [messageList, setMessageList] = useState<Message[]>(messagesData);
  const [messageCounter, setMessageCounter] = useState<number>(0);
  const handleMessage = function (message: Message) {
    const nextMessages = [...messageList, message];
    setMessageList(nextMessages);
  };
  // WebSocket ইভেন্ট হ্যান্ডলিং
  const { sendMessage } = useWebSocket("ws://localhost:8080", { share: true, filter: () => false, onOpen: () => { console.log("WebSocket connection!"); return "connection"; }, onMessage: (message) => { const data = JSON.parse(message.data); const { sender, text }: { sender: string; text: string } = data; const messageData: Message = { id: messageCounter, sender: sender, text: text, }; setMessageCounter(messageCounter + 1); handleMessage(messageData); return message; }, onClose: () => { console.log("WebSocket disconnected!"); return "disconnected"; }, });
  function handleSendMessage(messageText: string) {
    const messageData = { sender: username, text: messageText, };
    sendMessage(JSON.stringify(messageData));
  }
  return ( <div className={styles.Container}> <MessageDisplay messages={messageList} /> <MessageInput inputText={inputText} setInputText={setInputText} handleSendMessage={handleSendMessage} /> </div> ); }

এখানে MessageDisplay এবং MessageInput কম্পোনেন্টগুলো:

const MessageDisplay = function (props: { messages: Message[] }) { const { messages } = props; return ( <div className={styles.messageContainer}> {messages.map((message) => ( <MessageBubble key={message.id} sender={message.sender} text={message.text} /> ))} </div> ); };
const MessageInput = (props: { inputText: string; setInputText: (msg: string) => void; handleSendMessage: (msg: string) => void; }) => { const { inputText, setInputText, handleSendMessage } = props; const handleInputChange = ( e: React.ChangeEvent<HTMLInputElement> ): void => { const inputValue: string = e.target.value; setInputText(inputValue); }; const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => { e.preventDefault(); handleSendMessage(inputText); if (inputText.trim() !== "") { setInputText(" "); } }; return ( <form className={styles.inputSection} onSubmit={handleSubmit}> <input className={styles.inputText} type="text" value={inputText} onChange={handleInputChange} ></input> <button className={styles.inputSendButton} type="submit"> Send </button> </form> ); };
const MessageBubble = (props: { sender: string; text: string; key: number; }) => { const { sender, text } = props; const { username } = useRouter().query; const isSender = sender === username; const senderClass = isSender ? "sender" : "receiver"; return ( <div className={`${styles["messageBubble"]} ${styles[senderClass]}`}> <div className={styles.messageSender}> {isSender ? "You" : sender} </div> <div className={styles.messageText}>{text}</div> </div> ); };

চ্যাট ইতিহাস ক্লায়েন্টকে প্রদান করতে, আমরা getServerSideProps() ফাংশন ব্যবহার করব।

export async function getServerSideProps() {
  const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, });
  const messagesData = (await redis.lrange("messagesList", 0, -1)).reverse();
  return { props: { messagesData, }, }; }

আমাদের Next.js অ্যাপ এখন কাজ করছে। পেজ রিফ্রেশ করুন, ক্লায়েন্ট তৈরি করুন এবং 그중 একটি নেভিগেট করুন। আপনি ক্লায়েন্ট পেজ দেখবেন। কিন্তু এখনও, আমাদের মেসেজ ফ্লো হ্যান্ডল করার জন্য মেসেজ সার্ভার দরকার।

মেসেজ সার্ভার তৈরি করা

সার্ভারের স্ট্রাকচার খুব সহজ। আমরা Node.js, ws লাইব্রেরি, এবং Upstash Kafka ব্যবহার করব। প্রথম, chat-app folder-এর ভিতরে একটি server ফোল্ডার তৈরি করুন।

mkdir server
cd server

server ফোল্ডারের ভিতরে, আমরা রিকোয়ারমেন্ট ইনস্টল করব এবং ফাইল কনফিগার করব।

npm install typescript ws tsc @upstash/kafka @types/ws
tsc --init

তারপর, আমরা /server/message_server.ts ফাইলে WebSocket, Kafka Producer এবং Kafka Consumer ক্লায়েন্ট তৈরি করব:

import * as http from "http";
import { Kafka } from "@upstash/kafka";
import { Redis } from "@upstash/redis";
import { WebSocket } from "ws";
const server = http.createServer();
const wss = new WebSocket.Server({ server });
server.listen(8080, () => { console.log("Server is running on port 8080"); });
const kafka = new Kafka({ url: process.env.UPSTASH_KAFKA_REST_URL, username: process.env.UPSTASH_KAFKA_REST_USERNAME, password: process.env.UPSTASH_KAFKA_REST_PASSWORD, });
const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, });
const consumer = kafka.consumer();
const producer = kafka.producer();
const clients = new Set<WebSocket>();

WebSocket-এর সাথে ইন্টারঅ্যাক্ট করতে, আমরা connection এবং message ইভেন্ট তৈরি করছি।

wss.on("connection", async (connection, req) => {
  clients.add(connection);
  console.log(`New client connected!`);
  connection.on("message", async (message) => {
    const jsonMessage = message.toString();
    console.log("Received message:", JSON.parse(jsonMessage));
    producer.produce("chat", jsonMessage);
  });
  connection.on("close", () => {
    console.log(`Client disconnected:`);
    clients.delete(connection);
  });
});

অবশেষে, আমরা পূর্বনির্ধারিত ইন্টারভালে মেসেজ কনসিউম করার থ্রেড তৈরি ও চালাব:

async function run() {
  while (true) {
    const messages = await consumer.consume({ consumerGroupId: "group_1", instanceId: "instance_1", topics: ["chat"], autoOffsetReset: "earliest", });
    if (messages.length != 0) {
      for (let i = 0; i < messages.length; i++) {
        await redis.lpush("messagesList", messages[i].value);
        console.log(`Message sending: ${messages[i].value}`);
        clients.forEach((connection: WebSocket) => { connection.send(messages[i].value); });
      }
    }
    console.log("Run!");
    await new Promise((resolve) => setTimeout(resolve, 1000));
  }
}

সব কিছু রেডি। আমাদের অ্যাপ এখন চমৎকার কাজ করতে পারে। আপনি লোকালে মেসেজ সার্ভার চালিয়ে ক্লায়েন্ট পেজ রিফ্রেশ করলে, আপনি ক্লায়েন্টগুলোর মধ্যে মেসেজ ট্রান্সমিট होते দেখতে পাবেন। নিচের কমান্ডগুলো TS ফাইল ট্রান্সকম্পাইল করবে এবং সার্ভার localhost:8000-এ চালাবে

tsc message_server.ts
node message_server.js

ডিপ্লয়মেন্ট

আমরা Fly.io ব্যবহার করব ডিপ্লয়মেন্টের জন্য। শুরু করার আগে যদি আপনার অ্যাকাউন্ট না থাকে, তবে তৈরি করুন।

মেসেজ সার্ভার ডিপ্লয় করা

server ফোল্ডারে যান এবং flyctl CLI টুল ইনস্টল করুন, এবং শেলের মাধ্যমে অথোরাইজ করুন

npm install flyctl
flyctl auth login

কনফিগারেশন ফাইল তৈরি করতে flyctl init চালান। এটি একটি fly.toml তৈরি করবে। fly.toml-এ যান এবং WebSocket কানেকশন কনফিগারেশনের জন্য নিচের লাইনগুলো যোগ করুন:

[[services]]
internal_port = 8080
protocol = "tcp"
[services.concurrency]
hard_limit = 25
soft_limit = 20
[[services.ports]]
handlers = ["http"]
port = "80"
[[services.ports]]
handlers = ["tls", "http"]
port = "443"
[[services.tcp_checks]]
interval = 10000
timeout = 2000

এখন, সার্ভারের জন্য চূড়ান্ত ধাপ। flyctl deploy চালান, এবং আমরা রেডি! ডিপ্লয়মেন্ট প্রসেস hoàn료 হলে, flyctl আপনার সার্ভারের জন্য একটি এন্ডপয়েন্ট প্রদান করবে। সেটি কপি করুন। আমাদের কেসে, এন্ডপয়েন্টটি message-server.fly.dev।

Next অ্যাপ্লিকেশন ডিপ্লয় করা

Next.js অ্যাপ্লিকেশন ডিপ্লয় করার আগে, আমাদের মেসেজ সার্ভারের ডিপ্লয়মেন্ট এন্ডপয়েন্ট এম্বেড করতে হবে। অনুগ্রহ করে pages/user/[username].tsx ফাইলে WebSocket URL কে ws://localhost:8080 থেকে flyctl-এর এন্ডপয়েন্ট দিয়ে রিপ্লেস করুন, wss:// প্রিফিক্সের সাথে। আমাদের কেসে, এটি wss://message-server.fly.dev।

তারপর, next-chat-app ফোল্ডারে, server-এর মতো একই কমান্ডগুলো চালান। এইবার, আমাদের fly.toml ফাইল এডিট করার দরকার নেই, তাই আমরা সেটা ছাড়া এগিয়ে যেতে পারি।

flyctl init
flyctl deploy 

আমরা কাজ শেষ! আপনি flyctl open কমান্ড চালালে, আপনাকে আপনার ডিপ্লয়েড প্রজেক্টে নিয়ে যাবে।

উপসংহার এবং পরামর্শ

সাথে থাকার জন্য ধন্যবাদ!

আপনি প্রজেক্টের GitHub রিপোজিটরি এখানে পাবেন।

আপনি যদি প্রজেক্টে কাজ চালিয়ে রাখতে চান, তবে কিছু পরামর্শ:

  • বর্তমানে, পেজ রিলোড হলে Upstash Redis-এ সংরক্ষিত সকল মেসেজ ফ্লাশ হয় যাচ্ছে। এই আচরণ pages/index.tsx ফাইলে কন্ট্রোল করা হয়, স্পেসিফিক্যালি getServerSideProps ফাংশনে। যাইহোক, একটি ক্রিটিক্যাল ইস্যু উঠে যখন একজন ব্যবহারকারী পেজ রিলোড করে, এটি চ্যাটরুমের সব অংশগ্রাহকের চ্যাট ইতিহাস ডিলিট করে দেয়।
    এই সমস্যা সমাধানের জন্য একটি সুপারিশ করা সমাধান হলো প্রতিবার মেসেজ পাঠাওয়ার সময় চ্যাট ইতিহাসের TTL এক্সটেন্ড করা। এই উন্নতিটি নিশ্চিত করবে যে পেজ রিলোড হওয়ার পরও চ্যাট ইতিহাস অ্যাক্সেসযোগ্য এবং সংরক্ষিত থাকে।

  • আপনি একাধিক চ্যাটরুম ফিচার ইমপ্লিমেন্ট করতে পারেন। এটোর জন্য, আপনি প্রতিটি চ্যাটরুমের জন্য ইউনিক নামের একাধিক Kafka টপিক তৈরি করতে পারেন। অন্যথায়, সঠিক ডেটা স্ট্রাকচার ব্যবহার করে মেসেজ সার্ভারটিইতে হ্যান্ডল করা যায়।

  • আপনি একাধিক মেসেজ সার্ভার এবং একটি লোড ব্যালেন্সারও ইমপ্লিমেন্ট করতে পারেন সেরা সিস্টেম ডিজাইন প্র্যাক্টিস অ্যাপ্লাই করতে।

আপনার যদি কোনো প্রশ্ন থাকে, fahreddin@upstash.com-এ আমার সাথে যোগাযোগ করতে পারেন।

  1. ttk.Notebook-এ ট্যাব হেডারের রঙ পরিবর্তন করা (tkinter)

  2. অморটাইজড অ্যানালিসিস

  3. অ্যান্ড্রয়েডে ConcurrentLinkedQueue-এ remove() কীভাবে ব্যবহার করবেন?

  4. পাইথনে সুবিধাজনক ওয়েব ব্রাউজার কন্ট্রোলার