प्रोजेक्ट विवरण
<पी> इस ब्लॉग पोस्ट में, हम एक मैसेजिंग एप्लिकेशन बनाएंगे जो उपयोगकर्ताओं को मैसेज क्लाइंट और चैट रूम बनाने की अनुमति देता है। इसके अतिरिक्त, उपयोगकर्ता पिछले संदेशों तक पहुंच सकेंगे। <पी> प्रोजेक्ट में दो पृष्ठ हैं। पहला पृष्ठ ग्राहक पंजीकरण के लिए समर्पित है, जहां आप अद्वितीय नामों के साथ कई ग्राहक बना सकते हैं। <पी>
<पी> जब आप किसी ग्राहक के उपयोगकर्ता नाम पर क्लिक करते हैं, तो आपको उस विशिष्ट उपयोगकर्ता से जुड़े चैटरूम क्लाइंट पर निर्देशित किया जाएगा। <पी>
<पी> चैट एप्लिकेशन का तर्क इस प्रकार है: <पी> उपयोगकर्ता इंडेक्स पेज पर कई क्लाइंट बना सकते हैं, जिनमें से प्रत्येक एक अद्वितीय उपयोगकर्ता नाम के साथ हो सकता है। क्लाइंट के उपयोगकर्ता नाम पर क्लिक करने से एक अद्वितीय पथ के साथ एक अलग क्लाइंट के साथ एक नया टैब खुल जाएगा। <पी> प्रत्येक क्लाइंट वेबसॉकेट कनेक्शन के माध्यम से एक संदेश सर्वर से जुड़ा होगा। जब क्लाइंट पर एक नया संदेश बनाया जाता है, तो उसे उस क्लाइंट से जुड़े संदेश सर्वर पर भेजा जाएगा। <पी> संदेश सर्वर संदेश ट्रैफ़िक को संभालेंगे. जब कोई क्लाइंट वेबसॉकेट कनेक्शन के माध्यम से एक संदेश भेजता है, तो सर्वर उसे काफ्का ब्रोकर को निर्देशित करेगा। प्रत्येक संदेश सर्वर आने वाले संदेशों को संभालने के लिए एक NodeJS थ्रेड चलाएगा। जब कोई संदेश उपभोग किया जाता है, तो उसे मौजूदा वेबसॉकेट कनेक्शन के माध्यम से ग्राहकों को भेजा जाएगा। क्लाइंट-साइड पर आने वाले संदेशों का उपभोग करने के लिए, हम react-use-websocket का उपयोग करेंगे पुस्तकालय. <पी> एप्लिकेशन संदेश इतिहास को संग्रहीत करने के लिए अपस्टैश रेडिस का उपयोग करेगा। जब काफ्का को कोई संदेश भेजा जाता है, तो उसे रेडिस डेटाबेस पर भी जारी रखा जाएगा। नया क्लाइंट बनाने पर, पुराने संदेशों को अपस्टैश रेडिस से पुनर्प्राप्त किया जाएगा और चैट डिस्प्ले में प्रस्तुत किया जाएगा। <पी> यहां एप्लिकेशन का सामान्य अवलोकन दिया गया है: <पी> ध्यान दें: हमारे कार्यान्वयन में, हम डेमो उद्देश्यों के लिए एकल संदेश सर्वर बनाएंगे, संदेश लोड को संभालने के लिए सर्वर की संख्या बढ़ा सकते हैं। <पी>
डेमो
<पी> आप यहां ऐप का डेमो देख सकते हैं। एप्लिकेशन का वर्तमान संस्करण फ्लाई पर तैनात किया गया है। आरंभ करना
<पी> चैट एप्लिकेशन बनाने के चरण यहां दिए गए हैं: - अपस्टैश रेडिस डेटाबेस बनाना
- अपस्टैश काफ्का क्लस्टर बनाना
- अगला एप्लिकेशन (फ्रंटएंड) बनाना।
- WebSocket संदेश सर्वर बनाना।
- एप्लिकेशन को Fly.io पर परिनियोजित करना
अपस्टैश रेडिस डेटाबेस बनाना
<पी> अपस्टैश कंसोल पर जाएं और लॉग इन करें, फिर रेडिस पर टैब पर, डेटाबेस बनाएं पर क्लिक करें बटन. <पी>
<पी> ठीक वैसे ही, हमारी Redis उपयोग के लिए तैयार है! हम क्रेडेंशियल्स के लिए Redis कंसोल पर वापस आएंगे। अपस्टैश काफ्का क्लस्टर बनाना
<पी> अब, काफ्का पर स्विच करें टैब, और क्लस्टर बनाएं पर क्लिक करें बटन. क्लस्टर नाम टाइप करें और आगे बढ़ें। फिर, काफ्का विषय बनाएं और पुष्टि करें। <पी>
अगला ऐप बनाना
<पी> सबसे पहले, अपने टर्मिनल से अपने एप्लिकेशन का रूट फ़ोल्डर बनाएं और नेविगेट करें। हम नेक्स्ट ऐप और सर्वर को इस फोल्डर में रखेंगे। mkdir chat-app
cd chat-app
<पी> फिर, अपना अगला ऐप बनाएं। $ 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 बनाएं फ़ाइल. <पी> फिर, रेडिस कंसोल पर नेविगेट करें, और UPSTASH_REDIS_REST_URL को कॉपी/पेस्ट करें और UPSTASH_REDIS_REST_TOKEN .env के क्रेडेंशियल फ़ाइल. <पी>
<पी> अंत में, काफ्का कंसोल पर स्विच करें, और UPSTASH_KAFKA_REST_URL को स्थानांतरित करें , UPSTASH_KAFKA_REST_USERNAME , UPSTASH_KAFKA_REST_PASSWORD पी> <पी>
<पी> अब, आपका .env फ़ाइल समान दिखनी चाहिए .envUPSTASH_REDIS_REST_URL=...
UPSTASH_REDIS_REST_TOKEN=...
UPSTASH_KAFKA_REST_URL=...
UPSTASH_KAFKA_REST_USERNAME=...
UPSTASH_KAFKA_REST_PASSWORD=...
<पी> अब जब हमने क्रेडेंशियल कॉन्फ़िगर कर लिया है, तो हम आवेदन के साथ आगे बढ़ सकते हैं। ग्राहक पंजीकरण पृष्ठ
<पी> इंडेक्स पेज में क्लाइंट पंजीकरण/निर्माण संचालन शामिल होंगे। जब उपयोगकर्ता नाम सबमिट किया जाता है, तो एक नया क्लाइंट बनाया जाएगा और वर्तमान क्लाइंट के अंतर्गत सूचीबद्ध किया जाएगा। तालिका. पेज/index.tsximport { 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'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>
);
}
<पी> यदि आप हर बार ऐप पुनः लोड होने पर चैट इतिहास को रीसेट करना चाहते हैं, तो आप निम्न फ़ंक्शन का उपयोग कर सकते हैं: पेज/index.tsxexport 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: {},
};
}
<पी> इसके साथ ही, इंडेक्स पेज चलने के लिए तैयार है। npm run dev चलाएँ next-chat-app में कमांड इंडेक्स पेज को लाइव देखने के लिए फ़ोल्डर। संदेश क्लाइंट पृष्ठ
<पी> ग्राहकों के लिए डायनामिक रूटिंग लागू करने के लिए, हम /pages/user/[username].tsx नामक एक फ़ोल्डर बनाएंगे यह फ़ोल्डर संरचना हमें प्रत्येक व्यक्तिगत क्लाइंट के लिए उनके उपयोगकर्ता नाम के आधार पर गतिशील मार्ग बनाने की अनुमति देगी। <पी> यहां क्लाइंट का मुख्य घटक है. यह घटक संदेशों, उपयोगकर्ता नाम आदि की सूची के लिए स्थिति रखेगा। हम वेबसॉकेट से संदेश, कनेक्शन और डिस्कनेक्शन ईवेंट बनाने के लिए यूज़वेबसॉकेट हुक का उपयोग कर रहे हैं। जब कोई संदेश ईवेंट उत्सर्जित होता है, तो संदेश को संदेश सूची में जोड़ा जाएगा और MessageDisplay घटक को फिर से प्रस्तुत किया जाएगा। /पेज/उपयोगकर्ता/[उपयोगकर्ता नाम].tsximport { 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);
};
// handling WebSocket events
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 घटक हैं: /पेज/उपयोगकर्ता/[उपयोगकर्ता नाम].tsxconst 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() का उपयोग करेंगे समारोह. /पेज/उपयोगकर्ता/[उपयोगकर्ता नाम].tsxexport 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 लाइब्रेरी और अपस्टैश काफ्का का उपयोग करने जा रहे हैं। सबसे पहले, एक server बनाएं chat-app folder के अंदर फ़ोल्डर . mkdir server
cd server
<पी> server के अंदर फ़ोल्डर, हम आवश्यकताओं को स्थापित करेंगे और फ़ाइलों को कॉन्फ़िगर करेंगे। npm install typescript ws tsc @upstash/kafka @types/ws
tsc --init
<पी> फिर, हम /server/message_server.ts के अंदर वेबसॉकेट, काफ्का प्रोड्यूसर और काफ्का उपभोक्ता क्लाइंट बनाने जा रहे हैं। फ़ाइल: /server/message_server.tsimport * 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 घटनाएँ. /server/message_server.tswss.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);
});
});
<पी> अंत में, हम वह थ्रेड बनाएंगे और चलाएंगे जो पूर्वनिर्धारित अंतराल के साथ संदेशों का उपभोग करता है: /server/message_server.tsasync 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));
}
}
<पी> सब कुछ तैयार है. हमारा ऐप अभी आकर्षण की तरह काम करना चाहिए। यदि आप संदेश सर्वर को स्थानीय पर चलाते हैं और क्लाइंट पेज को रीफ्रेश करते हैं, तो आप क्लाइंट के बीच प्रसारित संदेशों को देख सकते हैं। नीचे दिए गए आदेश टीएस फ़ाइल को ट्रांसकंपाइल करेंगे और सर्वर को localhost:8000 पर चलाएंगे पी> tsc message_server.ts
node message_server.js
तैनाती
<पी> हम परिनियोजन के लिए Fly.io का उपयोग करेंगे। कृपया शुरू करने से पहले एक खाता बनाएं, यदि आपके पास यह पहले से नहीं है। संदेश सर्वर परिनियोजन
<पी> server पर जाएं फ़ोल्डर खोलें और flyctl इंस्टॉल करें सीएलआई उपकरण, और शेलपी> के माध्यम से अधिकृत करें npm install flyctl
flyctl auth login
<पी> कॉन्फ़िगरेशन फ़ाइलें बनाने के लिए, flyctl init चलाएँ . इससे एक fly.toml बन जाएगा . fly.toml पर जाएं और WebSocket कनेक्शन कॉन्फ़िगरेशन के लिए निम्नलिखित पंक्तियाँ डालें: Fly.toml[[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 चलाएँ , और हम जाने के लिए तैयार हैं! जब परिनियोजन प्रक्रिया पूरी हो जाएगी, तो फ्लाईसीटीएल आपके सर्वर के लिए एक समापन बिंदु प्रदान करेगा। कृपया उस समापनबिंदु की प्रतिलिपि बनाएँ। हमारे मामले में, समापन बिंदु message-server.fly.dev है . अगला एप्लिकेशन तैनात करना
<पी> 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 चलाते हैं कमांड, आपको आपके तैनात प्रोजेक्ट पर ले जाया जाएगा। निष्कर्ष और सुझाव
<पी> साथ देने के लिए धन्यवाद! <पी> आप यहां प्रोजेक्ट का जीथब रिपॉजिटरी पा सकते हैं। <पी> यदि आप प्रोजेक्ट पर काम करना जारी रखना चाहते हैं, तो यहां कुछ सुझाव दिए गए हैं: - <पी> वर्तमान में, जब भी पृष्ठ पुनः लोड किया जाता है, तो अपस्टैश रेडिस में संग्रहीत सभी संदेश फ्लश हो जाते हैं। यह व्यवहार
pages/index.tsx में मौजूद कोड द्वारा नियंत्रित होता है फ़ाइल, विशेष रूप से getServerSideProps के भीतर समारोह. हालाँकि, एक गंभीर समस्या तब उत्पन्न होती है जब कोई उपयोगकर्ता पृष्ठ को पुनः लोड करने का निर्णय लेता है, जिसके परिणामस्वरूप चैटरूम में सभी प्रतिभागियों के लिए चैट इतिहास हटा दिया जाता है।
इस समस्या को हल करने के लिए, एक अनुशंसित समाधान में हर बार संदेश भेजे जाने पर चैट इतिहास के लिए टीटीएल का विस्तार लागू करना शामिल है। यह सुधार यह सुनिश्चित करेगा कि पृष्ठ पुनः लोड होने के बाद भी चैट इतिहास पहुंच योग्य और संरक्षित रहे।
- <पी> आप एकाधिक चैटरूम सुविधा लागू कर सकते हैं। इसे प्राप्त करने के लिए, आप प्रत्येक चैट रूम के लिए अद्वितीय नामों के साथ कई काफ्का विषय बना सकते हैं। दूसरा तरीका सही डेटा संरचनाओं का उपयोग करके संदेश सर्वर पर ही इसे संभालना है।
- <पी> सर्वोत्तम सिस्टम डिज़ाइन प्रथाओं को लागू करने के लिए आप एकाधिक संदेश सर्वर और एक लोड बैलेंसर भी लागू कर सकते हैं।
<पी> यदि आपके कोई प्रश्न हैं, तो आप मुझसे fahreddin@upstash.com पर संपर्क कर सकते हैं