<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Chat - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Chat - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 22 May 2026 17:39:52 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/chat/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Custom AI Chat Application with Next.js: Fine-Tune GPT Using Your Data ]]>
                </title>
                <description>
                    <![CDATA[ In 2025, AI-powered applications have advanced from generic chatbots to highly specialised assistants that understand your specific field, communicate in your style, and give contextually relevant answers. While Large Language Models (LLMs) like GPT-... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-custom-ai-chat-application-with-nextjs/</link>
                <guid isPermaLink="false">68e4f54b40f8bf1b48305e00</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sharvin Shah ]]>
                </dc:creator>
                <pubDate>Tue, 07 Oct 2025 11:11:07 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759834368425/fc26010c-077e-4af9-86cd-e16f1c218560.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In 2025, AI-powered applications have advanced from generic chatbots to highly specialised assistants that understand your specific field, communicate in your style, and give contextually relevant answers. While Large Language Models (LLMs) like GPT-5 have impressive general abilities, there is an increasing demand for AI that deeply understands particular businesses, personal brands, or specialised areas of knowledge.</p>
<p>Imagine having an AI assistant that not only knows about web development in general but also understands your specific coding style, knows your project history, and can answer questions in your voice. This isn't science fiction - it's what fine-tuning makes possible today.</p>
<p>In this tutorial, you'll learn how to fine-tune OpenAI's latest GPT-4.1 models and create a production-ready chat application using Next.js 15. I will guide you through the entire process: from preparing your dataset and submitting it for fine-tuning, to building a sleek chat interface that uses your custom model.</p>
<p>I'll show you this process using content from my agency website, mtechzilla.com, as example data. You'll learn how to scrape, clean, and format real-world content for training. Naturally, you'll want to use your own data - whether it's documentation, blog posts, customer support transcripts, or any other text that reflects the knowledge and style you want your AI to have.</p>
<p>This tutorial is for developers who are familiar with React and Node.js but are new to fine-tuning AI models. By the end, you'll have a fully functional, custom AI chat application ready to deploy.</p>
<p>As of September 2025, fine-tuning GPT-5 is not supported yet. This tutorial uses GPT-4.1. When GPT-5 fine-tuning becomes available, you'll mainly need to change the base model name.</p>
<h2 id="heading-table-of-contents">Table of Contents:</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-understanding-fine-tuning">Understanding Fine-Tuning</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-dataset-preparation">Step 1: Dataset Preparation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-fine-tuning-submission">Step 2: Fine-Tuning Submission</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-nextjs-application-setup">Step 3: Next.js Application Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-building-the-chat-interface">Step 4: Building the Chat Interface</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-api-route-integration">Step 5: API Route Integration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-testing-your-application">Step 6: Testing Your Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-understanding-fine-tuning">Understanding Fine-Tuning</h2>
<p>Before we jump into the code, let's explain what fine-tuning really means and when it's the best option for your project.</p>
<p>Fine-tuning means taking a pre-trained language model and training it further with your specific dataset. It's like teaching a smart student about your particular area of expertise and communication style. The model retains its general knowledge but becomes specialised in your field.</p>
<p>This is quite different from other methods of customising AI behavior. Retrieval-Augmented Generation (RAG) involves providing relevant context to the model when a query is made, similar to giving someone reference materials to use while answering questions. Prompt engineering, on the other hand, involves creating smart instructions to direct the model's behavior without any additional training. Fine-tuning, however, results in a model that has deeply learned and internalised your data.</p>
<p>The trade-offs in 2025 are clearer than ever. Fine-tuning requires an upfront investment in data preparation and training costs, but it leads to faster inference, no need for context injection, and a more consistent personality. RAG systems are cheaper to set up and easier to update, but they need vector databases and can have trouble matching nuanced styles. Prompt engineering is free and immediate, but it limits how much customisation you can achieve.</p>
<p>As of September 2025, OpenAI supports supervised fine-tuning for three new models: GPT-4.1, GPT-4.1-mini, and GPT-4.1-nano. Each model has different capabilities and costs. GPT-4.1-nano is the most affordable option, ideal for simpler tasks. GPT-4.1-mini balances performance and cost, while GPT-4.1 offers the highest intelligence for complex, domain-specific applications.</p>
<p>Fine-tuning is best when you need a consistent voice and style, have specialised knowledge not well-covered by the base model, want to reduce delay by removing context injection, or need to ensure specific behaviours without complicated prompts. If your needs involve frequently changing information, simple factual lookups, or only occasional customisation, consider using RAG or prompt engineering instead.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we begin, make sure you have the following tools and accounts set up:</p>
<ol>
<li><p>Node.js v22+ and npm (check with <code>node --version</code>)</p>
</li>
<li><p>Basic JavaScript knowledge for the scripts</p>
</li>
<li><p>Familiarity with React and TypeScript for the web app</p>
</li>
<li><p>An OpenAI API key with billing enabled (get one at <a target="_blank" href="https://platform.openai.com/docs/overview">https://platform.openai.com/docs/overview</a>)</p>
</li>
<li><p>A code editor</p>
</li>
</ol>
<p>You'll also need some content to train your model on. This could be blog posts, documentation, transcripts, or any text that represents the knowledge and style you want to impart to your AI.</p>
<p>If you get stuck, you can check the GitHub repository for assistance or contact me (I'm happy to help fellow developers).</p>
<p><strong>Repository link:</strong> <a target="_blank" href="https://github.com/Sharvin26/ai-fine-tuning-project">https://github.com/Sharvin26/ai-fine-tuning-project</a></p>
<h2 id="heading-step-1-dataset-preparation">Step 1: Dataset Preparation</h2>
<p>The key to a successful fine-tuning project is having a well-prepared dataset. OpenAI needs training data in JSONL (JSON Lines) format, where each line is a complete JSON object representing a conversation. JSONL is a format where each line is a separate JSON object, which is great for handling large datasets efficiently. It allows for easy streaming and handling of data, making it ideal for machine learning tasks.</p>
<p>The JSONL structure that OpenAI requires for fine-tuning is as follows:</p>
<pre><code class="lang-json">{
   <span class="hljs-attr">"messages"</span>:[
      {
         <span class="hljs-attr">"role"</span>:<span class="hljs-string">"system"</span>,
         <span class="hljs-attr">"content"</span>:<span class="hljs-string">"You are a helpful assistant."</span>
      },
      {
         <span class="hljs-attr">"role"</span>:<span class="hljs-string">"user"</span>,
         <span class="hljs-attr">"content"</span>:<span class="hljs-string">"What is React?"</span>
      },
      {
         <span class="hljs-attr">"role"</span>:<span class="hljs-string">"assistant"</span>,
         <span class="hljs-attr">"content"</span>:<span class="hljs-string">"React is a JavaScript library..."</span>
      }
   ]
}
</code></pre>
<p>Each line represents a complete conversation. For fine-tuning to be effective, you need at least 10 examples, though 50-100 typically yields better results.</p>
<p>Let's build a Node.js scraper that extracts content from a website and converts it into the proper format.</p>
<p>First, let's set up our scripts folder structure:</p>
<pre><code class="lang-bash">mkdir ai-fine-tuning-project
<span class="hljs-built_in">cd</span> ai-fine-tuning-project
mkdir scripts
<span class="hljs-built_in">cd</span> scripts
npm init -y
npm install cheerio axios dotenv openai
touch scraper.js fine-tune.js .gitignore .env
</code></pre>
<p>Open the <code>ai-fine-tuning-project</code> in a code editor and copy the following values into a <code>.env</code> file in the scripts folder.</p>
<pre><code class="lang-bash">OPENAI_API_KEY=sk-...your-api-key-here...
OPENAI_ORG_ID=org-...your-org-id...
</code></pre>
<p>Update the ENV variables with a valid API Key and Org ID from OpenAI.</p>
<ol>
<li><p>Generate an API Key using this guide: <a target="_blank" href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key">Where do I find my OpenAI API Key?</a>. Here are the best practices to keep your API Key safe: <a target="_blank" href="https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety">Best Practices for API Key Safety</a>.</p>
</li>
<li><p>You can find your OpenAI Org ID here: <a target="_blank" href="https://platform.openai.com/settings/organization/general">OpenAI Organization Settings</a>.</p>
</li>
</ol>
<p>Add the following code to the <code>.gitignore</code> file:</p>
<pre><code class="lang-plaintext"># Environment variables
.env
.env.local
.env.*.local

# Dependencies
node_modules/

# Logs
*.log
logs/

# Cache and temporary files
.cache/
temp/
tmp/

# OS files
.DS_Store
Thumbs.db

# IDE files
.vscode/
.idea/
*.swp
*.swo
</code></pre>
<p>Update the scraper script (<code>scraper.js</code>) with the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> cheerio = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cheerio"</span>);
<span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">"axios"</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);
<span class="hljs-keyword">const</span> OpenAI = <span class="hljs-built_in">require</span>(<span class="hljs-string">"openai"</span>);
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();

<span class="hljs-keyword">const</span> config = {
    <span class="hljs-attr">urls</span>: [
        {
            <span class="hljs-attr">url</span>: <span class="hljs-string">"https://www.mtechzilla.com/"</span>,
            <span class="hljs-attr">contentType</span>: <span class="hljs-string">"general"</span>,
        },
        {
            <span class="hljs-attr">url</span>: <span class="hljs-string">"https://www.mtechzilla.com/company/about-us"</span>,
            <span class="hljs-attr">contentType</span>: <span class="hljs-string">"about"</span>,
        },
        {
            <span class="hljs-attr">url</span>: <span class="hljs-string">"https://www.mtechzilla.com/services"</span>,
            <span class="hljs-attr">contentType</span>: <span class="hljs-string">"services"</span>,
        },
    ],
    <span class="hljs-attr">openai</span>: {
        <span class="hljs-attr">apiKey</span>: process.env.OPENAI_API_KEY,
        <span class="hljs-attr">model</span>: <span class="hljs-string">"gpt-5"</span>,
        <span class="hljs-attr">trainingExamples</span>: <span class="hljs-number">50</span>,
    },
    <span class="hljs-attr">outputFile</span>: <span class="hljs-string">"training_data.jsonl"</span>,
};

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AIScraper</span> </span>{
    <span class="hljs-keyword">constructor</span>(config) {
        <span class="hljs-built_in">this</span>.urls = config.urls;
        <span class="hljs-built_in">this</span>.openaiConfig = config.openai;
        <span class="hljs-built_in">this</span>.outputFile = config.outputFile;
        <span class="hljs-built_in">this</span>.scrapedContent = [];
        <span class="hljs-built_in">this</span>.trainingData = [];
        <span class="hljs-built_in">this</span>.openai = <span class="hljs-keyword">new</span> OpenAI({
            <span class="hljs-attr">apiKey</span>: <span class="hljs-built_in">this</span>.openaiConfig.apiKey,
        });
        <span class="hljs-built_in">this</span>.totalCost = <span class="hljs-number">0</span>;
    }

    <span class="hljs-keyword">async</span> fetchPage(url) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.get(url, {
                <span class="hljs-attr">timeout</span>: <span class="hljs-number">30000</span>,
                <span class="hljs-attr">headers</span>: {
                    <span class="hljs-string">'User-Agent'</span>: <span class="hljs-string">'Mozilla/5.0 (compatible; AI-Training-Data-Scraper/1.0)'</span>
                }
            });
            <span class="hljs-keyword">return</span> response.data;
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`Failed to fetch <span class="hljs-subst">${url}</span>: <span class="hljs-subst">${error.message}</span>`</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
        }
    }

    extractContent(html, urlConfig) {
        <span class="hljs-keyword">const</span> $ = cheerio.load(html);

        $(<span class="hljs-string">'script, style, nav, header, footer'</span>).remove();
        $(<span class="hljs-string">'[class*="cookie"], [class*="popup"], [class*="ad"]'</span>).remove();
        $(<span class="hljs-string">'button, .btn'</span>).remove();

        <span class="hljs-keyword">const</span> headings = [];
        $(<span class="hljs-string">'h1, h2, h3, h4'</span>).each(<span class="hljs-function">(<span class="hljs-params">_, elem</span>) =&gt;</span> {
            <span class="hljs-keyword">const</span> text = $(elem).text().trim();
            <span class="hljs-keyword">if</span> (text.length &gt; <span class="hljs-number">3</span> &amp;&amp; text.length &lt; <span class="hljs-number">200</span>) {
                headings.push(text);
            }
        });

        <span class="hljs-keyword">const</span> paragraphs = [];
        $(<span class="hljs-string">'p'</span>).each(<span class="hljs-function">(<span class="hljs-params">_, elem</span>) =&gt;</span> {
            <span class="hljs-keyword">const</span> text = $(elem).text().trim();
            <span class="hljs-keyword">if</span> (text.length &gt; <span class="hljs-number">20</span>) {
                paragraphs.push(text);
            }
        });

        <span class="hljs-keyword">const</span> listItems = [];
        $(<span class="hljs-string">'ul li, ol li'</span>).each(<span class="hljs-function">(<span class="hljs-params">_, elem</span>) =&gt;</span> {
            <span class="hljs-keyword">const</span> text = $(elem).text().trim();
            <span class="hljs-keyword">if</span> (text.length &gt; <span class="hljs-number">5</span> &amp;&amp; text.length &lt; <span class="hljs-number">200</span>) {
                listItems.push(text);
            }
        });

        <span class="hljs-keyword">return</span> {
            <span class="hljs-attr">url</span>: urlConfig.url,
            <span class="hljs-attr">contentType</span>: urlConfig.contentType,
            <span class="hljs-attr">title</span>: $(<span class="hljs-string">'title'</span>).text().trim(),
            <span class="hljs-attr">metaDescription</span>: $(<span class="hljs-string">'meta[name="description"]'</span>).attr(<span class="hljs-string">'content'</span>) || <span class="hljs-string">''</span>,
            <span class="hljs-attr">headings</span>: headings.slice(<span class="hljs-number">0</span>, <span class="hljs-number">10</span>),
            <span class="hljs-attr">paragraphs</span>: paragraphs.slice(<span class="hljs-number">0</span>, <span class="hljs-number">15</span>),
            <span class="hljs-attr">listItems</span>: listItems.slice(<span class="hljs-number">0</span>, <span class="hljs-number">20</span>)
        };
    }

    formatContentForPrompt(content) {
        <span class="hljs-keyword">let</span> formattedContent = <span class="hljs-string">`URL: <span class="hljs-subst">${content.url}</span>\n`</span>;
        formattedContent += <span class="hljs-string">`Content Type: <span class="hljs-subst">${content.contentType}</span>\n`</span>;
        formattedContent += <span class="hljs-string">`Title: <span class="hljs-subst">${content.title}</span>\n\n`</span>;

        <span class="hljs-keyword">if</span> (content.metaDescription) {
            formattedContent += <span class="hljs-string">`Description: <span class="hljs-subst">${content.metaDescription}</span>\n\n`</span>;
        }

        <span class="hljs-keyword">if</span> (content.headings.length &gt; <span class="hljs-number">0</span>) {
            formattedContent += <span class="hljs-string">`Headings:\n<span class="hljs-subst">${content.headings.map(h =&gt; <span class="hljs-string">`- <span class="hljs-subst">${h}</span>`</span>).join(<span class="hljs-string">'\n'</span>)}</span>\n\n`</span>;
        }

        <span class="hljs-keyword">if</span> (content.paragraphs.length &gt; <span class="hljs-number">0</span>) {
            formattedContent += <span class="hljs-string">`Content:\n<span class="hljs-subst">${content.paragraphs.join(<span class="hljs-string">'\n\n'</span>)}</span>\n\n`</span>;
        }

        <span class="hljs-keyword">if</span> (content.listItems.length &gt; <span class="hljs-number">0</span>) {
            formattedContent += <span class="hljs-string">`Features/Services:\n<span class="hljs-subst">${content.listItems.map(item =&gt; <span class="hljs-string">`- <span class="hljs-subst">${item}</span>`</span>).join(<span class="hljs-string">'\n'</span>)}</span>\n\n`</span>;
        }

        <span class="hljs-keyword">return</span> formattedContent;
    }

    <span class="hljs-keyword">async</span> generateTrainingDataWithAI() {
        <span class="hljs-keyword">const</span> allContent = <span class="hljs-built_in">this</span>.scrapedContent.map(<span class="hljs-function"><span class="hljs-params">content</span> =&gt;</span> 
            <span class="hljs-built_in">this</span>.formatContentForPrompt(content)
        ).join(<span class="hljs-string">'\n'</span> + <span class="hljs-string">'='</span>.repeat(<span class="hljs-number">50</span>) + <span class="hljs-string">'\n'</span>);

        <span class="hljs-keyword">const</span> prompt = <span class="hljs-string">`Based on the website content below, generate <span class="hljs-subst">${<span class="hljs-built_in">this</span>.openaiConfig.trainingExamples}</span> diverse, natural Q&amp;A pairs for training a customer service chatbot.

Website Content:
<span class="hljs-subst">${allContent}</span>

Create varied questions a real customer might ask, including:
- Company/business information
- Services or products offered  
- Contact and support questions
- General greetings and conversational questions
- FAQ-style questions

Make questions natural and human-like. Generate accurate answers based ONLY on the provided website content. Keep answers concise but informative.

Return a JSON object with a "training_data" array containing the Q&amp;A pairs.`</span>;

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.openai.chat.completions.create({
                <span class="hljs-attr">model</span>: <span class="hljs-built_in">this</span>.openaiConfig.model,
                <span class="hljs-attr">messages</span>: [
                    {
                        <span class="hljs-attr">role</span>: <span class="hljs-string">"system"</span>,
                        <span class="hljs-attr">content</span>: <span class="hljs-string">"You are an expert at creating training data for AI chatbots. Always return valid JSON. Output your final JSON response directly without any reasoning or explanation."</span>
                    },
                    {
                        <span class="hljs-attr">role</span>: <span class="hljs-string">"user"</span>,
                        <span class="hljs-attr">content</span>: prompt,
                    },
                ],
                <span class="hljs-attr">response_format</span>: { 
                    <span class="hljs-attr">type</span>: <span class="hljs-string">"json_schema"</span>,
                    <span class="hljs-attr">json_schema</span>: {
                        <span class="hljs-attr">name</span>: <span class="hljs-string">"training_data_generation"</span>,
                        <span class="hljs-attr">schema</span>: {
                            <span class="hljs-attr">type</span>: <span class="hljs-string">"object"</span>,
                            <span class="hljs-attr">properties</span>: {
                                <span class="hljs-attr">training_data</span>: {
                                    <span class="hljs-attr">type</span>: <span class="hljs-string">"array"</span>,
                                    <span class="hljs-attr">items</span>: {
                                        <span class="hljs-attr">type</span>: <span class="hljs-string">"object"</span>,
                                        <span class="hljs-attr">properties</span>: {
                                            <span class="hljs-attr">question</span>: {
                                                <span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span>,
                                                <span class="hljs-attr">description</span>: <span class="hljs-string">"A natural question a customer might ask"</span>
                                            },
                                            <span class="hljs-attr">answer</span>: {
                                                <span class="hljs-attr">type</span>: <span class="hljs-string">"string"</span>, 
                                                <span class="hljs-attr">description</span>: <span class="hljs-string">"An accurate answer based on the website content"</span>
                                            }
                                        },
                                        <span class="hljs-attr">required</span>: [<span class="hljs-string">"question"</span>, <span class="hljs-string">"answer"</span>]
                                    }
                                }
                            },
                            <span class="hljs-attr">required</span>: [<span class="hljs-string">"training_data"</span>]
                        }
                    }
                }
            });

            <span class="hljs-keyword">const</span> generatedContent = response.choices[<span class="hljs-number">0</span>].message.content?.trim();

            <span class="hljs-keyword">const</span> actualInputTokens = response.usage.prompt_tokens;
            <span class="hljs-keyword">const</span> actualOutputTokens = response.usage.completion_tokens;
            <span class="hljs-keyword">const</span> actualCost = (actualInputTokens * <span class="hljs-number">1.25</span> / <span class="hljs-number">1000000</span>) + (actualOutputTokens * <span class="hljs-number">10</span> / <span class="hljs-number">1000000</span>);
            <span class="hljs-built_in">this</span>.totalCost += actualCost;

            <span class="hljs-keyword">if</span> (!generatedContent) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"No content generated in response"</span>);
            }

            <span class="hljs-keyword">const</span> structuredData = <span class="hljs-built_in">JSON</span>.parse(generatedContent);
            <span class="hljs-keyword">const</span> validTrainingData = [];

            <span class="hljs-keyword">if</span> (structuredData.training_data &amp;&amp; <span class="hljs-built_in">Array</span>.isArray(structuredData.training_data)) {
                structuredData.training_data.forEach(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
                    <span class="hljs-keyword">if</span> (item.question &amp;&amp; item.answer) {
                        validTrainingData.push({
                            <span class="hljs-attr">messages</span>: [
                                {
                                    <span class="hljs-attr">role</span>: <span class="hljs-string">"system"</span>,
                                    <span class="hljs-attr">content</span>: <span class="hljs-string">"You are a helpful assistant. Answer questions accurately based on the website content."</span>
                                },
                                {
                                    <span class="hljs-attr">role</span>: <span class="hljs-string">"user"</span>, 
                                    <span class="hljs-attr">content</span>: item.question
                                },
                                {
                                    <span class="hljs-attr">role</span>: <span class="hljs-string">"assistant"</span>,
                                    <span class="hljs-attr">content</span>: item.answer
                                }
                            ]
                        });
                    }
                });
            }

            <span class="hljs-built_in">this</span>.trainingData = validTrainingData;
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Generated <span class="hljs-subst">${validTrainingData.length}</span> training examples`</span>);

        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`OpenAI API error: <span class="hljs-subst">${error.message}</span>`</span>);
            <span class="hljs-keyword">throw</span> error;
        }
    }

    <span class="hljs-keyword">async</span> scrape() {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Starting scraper for <span class="hljs-subst">${<span class="hljs-built_in">this</span>.urls.length}</span> URLs`</span>);

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> urlConfig <span class="hljs-keyword">of</span> <span class="hljs-built_in">this</span>.urls) {
            <span class="hljs-keyword">const</span> html = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.fetchPage(urlConfig.url);

            <span class="hljs-keyword">if</span> (html) {
                <span class="hljs-keyword">const</span> content = <span class="hljs-built_in">this</span>.extractContent(html, urlConfig);
                <span class="hljs-built_in">this</span>.scrapedContent.push(content);
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Scraped: <span class="hljs-subst">${content.title || urlConfig.url}</span>`</span>);
            }

            <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">1000</span>));
        }

        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.scrapedContent.length === <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"No content scraped successfully"</span>);
        }

        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.generateTrainingDataWithAI();

        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Scraped <span class="hljs-subst">${<span class="hljs-built_in">this</span>.scrapedContent.length}</span> pages, generated <span class="hljs-subst">${<span class="hljs-built_in">this</span>.trainingData.length}</span> examples`</span>);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Total cost: $<span class="hljs-subst">${<span class="hljs-built_in">this</span>.totalCost.toFixed(<span class="hljs-number">4</span>)}</span>`</span>);
    }

    saveToFile() {
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.trainingData.length === <span class="hljs-number">0</span>) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"No training data to save!"</span>);
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">const</span> jsonl = <span class="hljs-built_in">this</span>.trainingData
            .map(<span class="hljs-function"><span class="hljs-params">example</span> =&gt;</span> <span class="hljs-built_in">JSON</span>.stringify(example))
            .join(<span class="hljs-string">'\n'</span>);

        fs.writeFileSync(<span class="hljs-built_in">this</span>.outputFile, jsonl);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Saved <span class="hljs-subst">${<span class="hljs-built_in">this</span>.trainingData.length}</span> examples to <span class="hljs-subst">${<span class="hljs-built_in">this</span>.outputFile}</span>`</span>);
    }
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">if</span> (!config.openai.apiKey) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Please set your OpenAI API key in .env file"</span>);
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">const</span> scraper = <span class="hljs-keyword">new</span> AIScraper(config);
        <span class="hljs-keyword">await</span> scraper.scrape();
        scraper.saveToFile();

        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Scraping complete!"</span>);

    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error:"</span>, error.message);
    }
}

main();
</code></pre>
<p>If you used a version of Node.js before 22, you might encounter issues when running the script. The recommended version is <code>v22.18.0</code>.</p>
<p>This code sets up an automated system for web scraping and creating AI-powered training data. It generates training datasets from website content. The script uses Cheerio to parse HTML from given URLs, extracting useful information like headings, paragraphs, and list items while ignoring unnecessary parts like scripts, navigation menus, and ads. After gathering the content, it uses OpenAI's API (configured to use GPT-4.1 with structured JSON output) to intelligently create natural question-and-answer pairs for fine-tuning purposes.</p>
<p>The generated Q&amp;A pairs are formatted into JSONL files according to OpenAI's fine-tuning format. Each entry includes a system message, a user question, and an assistant response. The scraper also has useful features like rate limiting between requests, error handling, and cost tracking based on token usage ($1.25 per million input tokens and $10 per million output tokens). This allows you to keep track of expenses while generating training data.</p>
<p>However, this is a basic script that can be improved based on your website's design, audience, and goals. While it extracts various content sections and creates diverse question-and-answer pairs, you'll need to manually check the output to ensure quality and correct formatting, as OpenAI will reject improperly formatted data. In production environments, this verification process can be automated by updating the script with additional validation logic and quality checks.</p>
<p>Now, let's create our <code>training_data.jsonl</code> file by running our scraper:</p>
<pre><code class="lang-bash">node scraper.js
</code></pre>
<p>You should see the following output:</p>
<pre><code class="lang-bash">Starting scraper <span class="hljs-keyword">for</span> 3 URLs
Scraped: MTechZilla: Custom Software and App Development Company
Scraped: About MTechZilla | Custom Software Development Agency
Scraped: Expert App &amp; Web Development Services | MTechZilla
Generated 50 training examples
Scraped 3 pages, generated 50 examples
Total cost: <span class="hljs-variable">$0</span>.0632
Saved 50 examples to training_data.jsonl
Scraping complete!
</code></pre>
<p><strong>Pro tip</strong>: Quality is more important than quantity. Review the generated <code>training_data.jsonl</code> file and refine any examples that don't accurately reflect the content or tone you want your AI to adopt.</p>
<h2 id="heading-step-2-fine-tuning-submission">Step 2: Fine-Tuning Submission</h2>
<p>With our dataset prepared, let's create a script to submit it to OpenAI for fine-tuning. We'll use the GPT-4.1 models and handle the entire process from upload to completion.</p>
<p>Update the fine-tuning script (<code>fine-tune.js</code>):</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> OpenAI = <span class="hljs-built_in">require</span>(<span class="hljs-string">"openai"</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();

<span class="hljs-keyword">const</span> CONFIG = {
    <span class="hljs-comment">// Choose your base model for fine-tuning</span>
    <span class="hljs-attr">MODEL</span>: <span class="hljs-string">"gpt-4.1-nano-2025-04-14"</span>, <span class="hljs-comment">// Options: gpt-4.1-nano-2025-04-14, gpt-4.1-mini-2025-04-14, gpt-4.1-2025-04-14</span>

    <span class="hljs-comment">// Training file path</span>
    <span class="hljs-attr">TRAINING_FILE</span>: <span class="hljs-string">"training_data.jsonl"</span>,

    <span class="hljs-comment">// Polling interval for job status (in milliseconds)</span>
    <span class="hljs-attr">POLL_INTERVAL</span>: <span class="hljs-number">30000</span>, <span class="hljs-comment">// 30 seconds</span>
};

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FineTuningManager</span> </span>{
    <span class="hljs-keyword">constructor</span>() {
        <span class="hljs-keyword">if</span> (!process.env.OPENAI_API_KEY) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"OPENAI_API_KEY environment variable is required"</span>);
        }

        <span class="hljs-built_in">this</span>.openai = <span class="hljs-keyword">new</span> OpenAI({
            <span class="hljs-attr">apiKey</span>: process.env.OPENAI_API_KEY,
            <span class="hljs-attr">organization</span>: process.env.OPENAI_ORG_ID,
        });
    }

    <span class="hljs-comment">// Step 1: Validate training data format</span>
    validateTrainingData() {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"🔍 Validating training data format..."</span>);

        <span class="hljs-keyword">if</span> (!fs.existsSync(CONFIG.TRAINING_FILE)) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Training file not found: <span class="hljs-subst">${CONFIG.TRAINING_FILE}</span>`</span>);
        }

        <span class="hljs-keyword">const</span> content = fs.readFileSync(CONFIG.TRAINING_FILE, <span class="hljs-string">"utf-8"</span>);
        <span class="hljs-keyword">const</span> lines = content.split(<span class="hljs-string">"\n"</span>).filter(<span class="hljs-function"><span class="hljs-params">line</span> =&gt;</span> line.trim());

        <span class="hljs-keyword">if</span> (lines.length &lt; <span class="hljs-number">10</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Need at least 10 examples. Found: <span class="hljs-subst">${lines.length}</span>`</span>);
        }

        <span class="hljs-keyword">let</span> validExamples = <span class="hljs-number">0</span>;
        lines.forEach(<span class="hljs-function">(<span class="hljs-params">line, index</span>) =&gt;</span> {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">const</span> data = <span class="hljs-built_in">JSON</span>.parse(line);

                <span class="hljs-comment">// Validate JSONL structure as per OpenAI documentation</span>
                <span class="hljs-keyword">if</span> (!data.messages || !<span class="hljs-built_in">Array</span>.isArray(data.messages) || data.messages.length &lt; <span class="hljs-number">2</span>) {
                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Invalid structure at line <span class="hljs-subst">${index + <span class="hljs-number">1</span>}</span>`</span>);
                }

                <span class="hljs-comment">// Check for required roles</span>
                <span class="hljs-keyword">const</span> hasUser = data.messages.some(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.role === <span class="hljs-string">'user'</span>);
                <span class="hljs-keyword">const</span> hasAssistant = data.messages.some(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.role === <span class="hljs-string">'assistant'</span>);

                <span class="hljs-keyword">if</span> (!hasUser || !hasAssistant) {
                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Missing user or assistant message at line <span class="hljs-subst">${index + <span class="hljs-number">1</span>}</span>`</span>);
                }

                validExamples++;
            } <span class="hljs-keyword">catch</span> (e) {
                <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">`⚠️ Skipping line <span class="hljs-subst">${index + <span class="hljs-number">1</span>}</span>: <span class="hljs-subst">${e.message}</span>`</span>);
            }
        });

        <span class="hljs-keyword">if</span> (validExamples &lt; <span class="hljs-number">10</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Need at least 10 valid examples. Found: <span class="hljs-subst">${validExamples}</span>`</span>);
        }

        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ Validation passed: <span class="hljs-subst">${validExamples}</span> valid examples`</span>);
        <span class="hljs-keyword">return</span> validExamples;
    }

    <span class="hljs-comment">// Step 2: Upload training file to OpenAI</span>
    <span class="hljs-keyword">async</span> uploadTrainingFile() {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"📤 Uploading training file..."</span>);

        <span class="hljs-keyword">const</span> file = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.openai.files.create({
            <span class="hljs-attr">file</span>: fs.createReadStream(CONFIG.TRAINING_FILE),
            <span class="hljs-attr">purpose</span>: <span class="hljs-string">"fine-tune"</span>,
        });

        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ File uploaded: <span class="hljs-subst">${file.id}</span>`</span>);
        <span class="hljs-keyword">return</span> file.id;
    }

    <span class="hljs-comment">// Step 3: Create fine-tuning job</span>
    <span class="hljs-keyword">async</span> createFineTuningJob(fileId) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🚀 Creating fine-tuning job with model: <span class="hljs-subst">${CONFIG.MODEL}</span>`</span>);

        <span class="hljs-keyword">const</span> job = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.openai.fineTuning.jobs.create({
            <span class="hljs-attr">training_file</span>: fileId,
            <span class="hljs-attr">model</span>: CONFIG.MODEL,
            <span class="hljs-attr">method</span>: {
                <span class="hljs-attr">type</span>: <span class="hljs-string">"supervised"</span>
            }
        });

        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ Fine-tuning job created: <span class="hljs-subst">${job.id}</span>`</span>);
        <span class="hljs-keyword">return</span> job.id;
    }

    <span class="hljs-comment">// Step 4: Monitor job until completion</span>
    <span class="hljs-keyword">async</span> monitorJob(jobId) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"⏳ Monitoring fine-tuning job..."</span>);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"This typically takes 10-30 minutes...\n"</span>);

        <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
            <span class="hljs-keyword">const</span> job = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.openai.fineTuning.jobs.retrieve(jobId);

            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Status: <span class="hljs-subst">${job.status}</span>`</span>);

            <span class="hljs-keyword">if</span> (job.status === <span class="hljs-string">"succeeded"</span>) {
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"\n🎉 Fine-tuning completed successfully!"</span>);
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🎆 Your fine-tuned model ID: <span class="hljs-subst">${job.fine_tuned_model}</span>`</span>);
                <span class="hljs-keyword">return</span> job.fine_tuned_model;
            } 

            <span class="hljs-keyword">if</span> (job.status === <span class="hljs-string">"failed"</span>) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Fine-tuning failed: <span class="hljs-subst">${job.error?.message || <span class="hljs-string">'Unknown error'</span>}</span>`</span>);
            }

            <span class="hljs-keyword">if</span> (job.status === <span class="hljs-string">"cancelled"</span>) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Fine-tuning was cancelled"</span>);
            }

            <span class="hljs-comment">// Wait before checking again</span>
            <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, CONFIG.POLL_INTERVAL));
        }
    }

    <span class="hljs-comment">// Complete supervised fine-tuning workflow</span>
    <span class="hljs-keyword">async</span> runFineTuning() {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"🤖 Starting OpenAI Supervised Fine-Tuning\n"</span>);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`📋 Using model: <span class="hljs-subst">${CONFIG.MODEL}</span>`</span>);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`📄 Training file: <span class="hljs-subst">${CONFIG.TRAINING_FILE}</span>\n`</span>);

            <span class="hljs-comment">// Step 1: Validate data</span>
            <span class="hljs-keyword">const</span> validExamples = <span class="hljs-built_in">this</span>.validateTrainingData();

            <span class="hljs-comment">// Step 2: Upload file</span>
            <span class="hljs-keyword">const</span> fileId = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.uploadTrainingFile();

            <span class="hljs-comment">// Step 3: Create job</span>
            <span class="hljs-keyword">const</span> jobId = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.createFineTuningJob(fileId);

            <span class="hljs-comment">// Step 4: Monitor completion</span>
            <span class="hljs-keyword">const</span> modelId = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.monitorJob(jobId);

            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"\n"</span> + <span class="hljs-string">"="</span>.repeat(<span class="hljs-number">60</span>));
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"SUCCESS! Your fine-tuned model is ready!"</span>);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"="</span>.repeat(<span class="hljs-number">60</span>));
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`\n Model ID: <span class="hljs-subst">${modelId}</span>`</span>);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Trained on <span class="hljs-subst">${validExamples}</span> examples`</span>);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"\n Next steps:"</span>);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"1. Copy the Model ID above"</span>);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"2. Use it in your application to access your custom model"</span>);

            <span class="hljs-keyword">return</span> modelId;

        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`\n❌ Fine-tuning failed: <span class="hljs-subst">${error.message}</span>`</span>);

            <span class="hljs-keyword">if</span> (error.message.includes(<span class="hljs-string">"not found"</span>)) {
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"💡 Tip: Make sure training_data.jsonl exists in the current directory"</span>);
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (error.message.includes(<span class="hljs-string">"API_KEY"</span>)) {
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"💡 Tip: Set OPENAI_API_KEY in your .env file"</span>);
            }

            <span class="hljs-keyword">throw</span> error;
        }
    }
}

<span class="hljs-comment">// Main execution</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> manager = <span class="hljs-keyword">new</span> FineTuningManager();
    <span class="hljs-keyword">await</span> manager.runFineTuning();
}

<span class="hljs-keyword">if</span> (<span class="hljs-built_in">require</span>.main === <span class="hljs-built_in">module</span>) {
    main().catch(<span class="hljs-built_in">console</span>.error);
}

<span class="hljs-built_in">module</span>.exports = FineTuningManager;
</code></pre>
<p>This script manages the entire fine-tuning process once your training data is ready from the scraper. It works as an automated manager, taking your JSONL training file and turning it into a custom OpenAI model designed for your specific needs. The process begins with thorough validation, ensuring your training file exists, has at least 10 valid examples, and follows OpenAI's required format with correct message roles (user and assistant). This validation step is important because OpenAI will reject data that isn't formatted correctly, so identifying issues early saves you time and API costs.</p>
<p>This script provides a complete fine-tuning workflow with the new GPT-4.1 models. You can choose from nano (cheapest), mini (balanced), or full (most capable) models, depending on your needs and budget. Once validation is successful, the script uploads your training file to OpenAI's servers and starts a fine-tuning job using your selected base model. The script uses supervised fine-tuning, which means your model learns directly from the question-answer pairs you provided, adjusting its responses to fit your website's information and tone.</p>
<p>The easiest part is the monitoring phase, where the script automatically checks the fine-tuning job status every 30 seconds until it's done. Fine-tuning usually takes 10-30 minutes, depending on your dataset size and the base model you chose. During the process, you'll get clear status updates in the console. Once it's finished, the script gives you your custom model ID, which you can use right away in your applications. It also provides helpful error messages and tips if something goes wrong, like reminding you to check your API key or verify that your training file exists.</p>
<p>Now let's submit our <code>training_data.jsonl</code> file for fine-tuning with the following command:</p>
<pre><code class="lang-bash">node fine-tune.js
</code></pre>
<p>You should see the following output:</p>
<pre><code class="lang-bash">🎉 Fine-tuning completed successfully!
🎆 Your fine-tuned model ID: ft:gpt-4.1-nano-2025-04-14:...

============================================================
SUCCESS! Your fine-tuned model is ready!
============================================================

 Model ID: ft:ft:gpt-4.1-nano-2025-04-14:...
Trained on 50 examples

 Next steps:
1. Copy the Model ID above
2. Use it <span class="hljs-keyword">in</span> your application to access your custom model
</code></pre>
<p><strong>Pro tip</strong>: Start with the nano model to test your dataset and workflow. It's the most cost-effective option and often enough for domain-specific knowledge. You can always train with a larger model later by updating configuration in <code>fine-tune.js</code>.</p>
<h2 id="heading-step-3-nextjs-application-setup">Step 3: Next.js Application Setup</h2>
<p>Now that our model is trained, let's build a modern chat application. We'll set up a separate web folder with a Next.js app using TypeScript and shadcn/ui for components.</p>
<p>First, navigate back to the project root:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ..
</code></pre>
<p>Then create the web application:</p>
<pre><code class="lang-bash">npx create-next-app@latest web
</code></pre>
<p>Choose the following options during the setup:</p>
<pre><code class="lang-bash">✔ Would you like to use TypeScript? › Yes
✔ Which linter would you like to use? › ESLint
✔ Would you like to use Tailwind CSS? › Yes
✔ Would you like your code inside a `src/` directory? › Yes
✔ Would you like to use App Router? (recommended) › Yes
✔ Would you like to use Turbopack? (recommended) › No
✔ Would you like to customize the import <span class="hljs-built_in">alias</span> (`@/*` by default)? › No
</code></pre>
<p>Navigate to the web directory:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> web
</code></pre>
<p>Install the required packages using the following command:</p>
<pre><code class="lang-bash">npm install ai @ai-sdk/openai @ai-sdk/react openai lucide-react
</code></pre>
<p>Now let's set up shadcn/ui for beautiful components:</p>
<pre><code class="lang-bash">npx shadcn@latest init
</code></pre>
<p>Choose the following option during the setup:</p>
<pre><code class="lang-bash">✔ Which color would you like to use as the base color? › Slate
</code></pre>
<p>And add the following shadcn components:</p>
<pre><code class="lang-bash">npx shadcn@latest add button
npx shadcn@latest add card
npx shadcn@latest add scroll-area
npx shadcn@latest add avatar
</code></pre>
<p>Create the <code>.env.local</code> file using the following command:</p>
<pre><code class="lang-json">touch .env.local
</code></pre>
<p>Add your environment variables in <code>web/.env.local</code>:</p>
<pre><code class="lang-bash">OPENAI_API_KEY=sk-...your-api-key...
OPENAI_ORG_ID=org-...your-org-id...
FINE_TUNED_MODEL=ft:gpt-4.1-nano-2025-04-14:... <span class="hljs-comment"># Your model ID from fine-tuning</span>
</code></pre>
<p>In the <code>src</code> directory, create a new folder named <code>types</code>. Inside this folder, create a file called <code>chat.ts</code> and copy and paste the following code:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// web/src/types/chat.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Message {
    id: <span class="hljs-built_in">string</span>;
    role: <span class="hljs-string">"user"</span> | <span class="hljs-string">"assistant"</span> | <span class="hljs-string">"system"</span>;
    content: <span class="hljs-built_in">string</span>;
    createdAt?: <span class="hljs-built_in">Date</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ChatRequest {
    messages: Message[];
    model?: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ChatResponse {
    message: Message;
    usage?: {
        prompt_tokens: <span class="hljs-built_in">number</span>;
        completion_tokens: <span class="hljs-built_in">number</span>;
        total_tokens: <span class="hljs-built_in">number</span>;
    };
}
</code></pre>
<p>This TypeScript code defines the data structures (interfaces) for a chat application, setting a standard for how messages and API interactions should be formatted in your app. The <code>Message</code> interface specifies what each chat message must include: a unique ID, a role indicating if it's from the user, assistant, or system, the message content, and an optional timestamp. The <code>ChatRequest</code> interface organizes the data you send to your fine-tuned model, including an array of messages (the conversation history) and an optional model parameter to specify which fine-tuned model to use.</p>
<p>Finally, the <code>ChatResponse</code> interface defines what you'll receive from the API: the assistant's reply message and optional usage statistics that show how many tokens were used for prompts and completions. This helps you track costs. By defining these interfaces, TypeScript ensures type safety throughout your application, catching errors during development and providing autocomplete suggestions in your code editor. This makes your chat application more robust and easier to maintain.</p>
<p>Update the <code>app/layout.tsx</code> with meta information and chat layout:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// web/app/layout.tsx</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Metadata } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> { Inter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/font/google"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span>;

<span class="hljs-keyword">const</span> inter = Inter({ subsets: [<span class="hljs-string">"latin"</span>] });

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata: Metadata = {
    title: <span class="hljs-string">"AI Chat - Powered by Custom Fine-Tuned Model"</span>,
    description: <span class="hljs-string">"Chat with an AI trained on custom content"</span>,
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
    children,
}: {
    children: React.ReactNode;
}</span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;html lang=<span class="hljs-string">"en"</span> className=<span class="hljs-string">"h-full"</span>&gt;
            &lt;body
                className={<span class="hljs-string">`<span class="hljs-subst">${inter.className}</span> h-full bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-950 dark:to-slate-900`</span>}
                suppressHydrationWarning={<span class="hljs-literal">true</span>}
            &gt;
                {children}
            &lt;/body&gt;
        &lt;/html&gt;
    );
}
</code></pre>
<p>This code sets up the root layout for a Next.js application, acting as a wrapper around every page in your chat app. It starts by importing the Inter font from Google Fonts and configures it to use Latin characters, giving your application a clean, modern appearance.</p>
<p>The <code>metadata</code> object sets the page title and description that appear in browser tabs and search engine results, which is important for SEO and user experience. The <code>RootLayout</code> component brings everything together: it wraps all your app's pages (using the <code>children</code> prop) in a consistent HTML structure with full-height styling (<code>h-full</code>) and a pleasant gradient background. This background changes from light slate tones in light mode to dark slate in dark mode, automatically matching the user's system settings. The <code>suppressHydrationWarning</code> attribute addresses a common Next.js issue where server-rendered HTML might slightly differ from client-rendered HTML (often due to things like timestamps or theme detection), preventing console warnings.</p>
<p>This layout ensures that every page in your chat application shares the same basic styling, typography, and metadata, so you don't need to repeat the code on each page. It gives us a solid foundation with TypeScript for type safety, shadcn/ui for standard components, effective management of environment variables, and a clean project structure that is easy to maintain and expand.</p>
<h2 id="heading-step-4-building-the-chat-interface">Step 4: Building the Chat Interface</h2>
<p>Let's build a beautiful, responsive chat interface using shadcn components and the Vercel AI SDK's powerful streaming features.</p>
<p>First, create the main chat component in <code>src/components/chat.tsx</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// web/src/components/chat.tsx</span>
<span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { useChat } <span class="hljs-keyword">from</span> <span class="hljs-string">"@ai-sdk/react"</span>;
<span class="hljs-keyword">import</span> { useRef, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Send, Bot, User, Loader2, AlertCircle } <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>;
<span class="hljs-keyword">import</span> { Button } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/button"</span>;
<span class="hljs-keyword">import</span> {
    Card,
    CardContent,
    CardHeader,
    CardTitle,
    CardDescription,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/card"</span>;
<span class="hljs-keyword">import</span> { ScrollArea } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/scroll-area"</span>;
<span class="hljs-keyword">import</span> { Avatar, AvatarFallback } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/avatar"</span>;
<span class="hljs-keyword">import</span> { cn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/utils"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Chat</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> scrollAreaRef = useRef&lt;HTMLDivElement&gt;(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">const</span> inputRef = useRef&lt;HTMLInputElement&gt;(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">const</span> [input, setInput] = useState(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> {
        messages,
        sendMessage,
        status,
        error,
        regenerate,
        stop,
        setMessages,
    } = useChat({
        onError: <span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Chat error:"</span>, error);
        },
        onFinish: <span class="hljs-function">() =&gt;</span> {
            inputRef.current?.focus();
        },
    });

    <span class="hljs-keyword">const</span> isLoading = status === <span class="hljs-string">"streaming"</span> || status === <span class="hljs-string">"submitted"</span>;

    <span class="hljs-comment">// Add welcome message on mount</span>
    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (messages.length === <span class="hljs-number">0</span>) {
            setMessages(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> [
                ...prev,
                {
                    id: <span class="hljs-string">"welcome"</span>,
                    role: <span class="hljs-string">"assistant"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
                    parts: [
                        {
                            <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
                            text: <span class="hljs-string">"Hello! I'm your custom AI assistant, trained on specific content. How can I help you today?"</span>,
                        },
                    ],
                },
            ]);
        }
    }, [messages.length, setMessages]);

    <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-function">(<span class="hljs-params">e: React.FormEvent</span>) =&gt;</span> {
        e.preventDefault();
        <span class="hljs-keyword">if</span> (!input.trim() || isLoading) <span class="hljs-keyword">return</span>;

        sendMessage({
            role: <span class="hljs-string">"user"</span>,
            parts: [{ <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>, text: input }],
        });
        setInput(<span class="hljs-string">""</span>);
    };

    <span class="hljs-keyword">const</span> handleInputChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
        setInput(e.target.value);
    };

    <span class="hljs-comment">// Auto-scroll to bottom when new messages arrive</span>
    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (scrollAreaRef.current) {
            <span class="hljs-keyword">const</span> scrollContainer = scrollAreaRef.current.querySelector(
                <span class="hljs-string">"[data-radix-scroll-area-viewport]"</span>
            );
            <span class="hljs-keyword">if</span> (scrollContainer) {
                scrollContainer.scrollTop = scrollContainer.scrollHeight;
            }
        }
    }, [messages]);


    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"flex h-screen max-w-5xl mx-auto p-4"</span>&gt;
            &lt;Card className=<span class="hljs-string">"flex-1 flex flex-col shadow-xl overflow-hidden"</span>&gt;
                {<span class="hljs-comment">/* Header */</span>}
                &lt;CardHeader className=<span class="hljs-string">"border-b flex-shrink-0"</span>&gt;
                    &lt;div className=<span class="hljs-string">"flex items-center space-x-4"</span>&gt;
                        &lt;div className=<span class="hljs-string">"relative"</span>&gt;
                            &lt;Avatar className=<span class="hljs-string">"h-10 w-10"</span>&gt;
                                &lt;AvatarFallback className=<span class="hljs-string">"bg-primary text-primary-foreground"</span>&gt;
                                    &lt;Bot className=<span class="hljs-string">"h-6 w-6"</span> /&gt;
                                &lt;/AvatarFallback&gt;
                            &lt;/Avatar&gt;
                            &lt;div className=<span class="hljs-string">"absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-green-500 rounded-full border-2 border-background animate-pulse"</span> /&gt;
                        &lt;/div&gt;
                        &lt;div className=<span class="hljs-string">"flex-1"</span>&gt;
                            &lt;CardTitle&gt;Custom AI Assistant&lt;/CardTitle&gt;
                            &lt;CardDescription&gt;
                                Powered by your fine-tuned model
                            &lt;/CardDescription&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/CardHeader&gt;

                {<span class="hljs-comment">/* Messages Area */</span>}
                &lt;ScrollArea ref={scrollAreaRef} className=<span class="hljs-string">"flex-1 min-h-0"</span>&gt;
                    &lt;div className=<span class="hljs-string">"p-4 space-y-4 pb-4"</span>&gt;
                        {messages.map(<span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
                            <span class="hljs-keyword">const</span> isUser = (message.role <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>) === <span class="hljs-string">"user"</span>;
                            <span class="hljs-keyword">return</span> (
                                &lt;div
                                    key={message.id}
                                    className={cn(
                                        <span class="hljs-string">"flex"</span>,
                                        isUser ? <span class="hljs-string">"justify-end"</span> : <span class="hljs-string">"justify-start"</span>
                                    )}
                                &gt;
                                    &lt;div
                                        className={cn(
                                            <span class="hljs-string">"flex items-start gap-3 max-w-[85%] min-w-0"</span>,
                                            isUser &amp;&amp; <span class="hljs-string">"flex-row-reverse"</span>
                                        )}
                                    &gt;
                                        {<span class="hljs-comment">/* Avatar */</span>}
                                        &lt;Avatar className=<span class="hljs-string">"h-8 w-8 shrink-0"</span>&gt;
                                            &lt;AvatarFallback
                                                className={cn(
                                                    isUser
                                                        ? <span class="hljs-string">"bg-primary text-primary-foreground"</span>
                                                        : <span class="hljs-string">"bg-muted"</span>
                                                )}
                                            &gt;
                                                {isUser ? (
                                                    &lt;User className=<span class="hljs-string">"h-4 w-4"</span> /&gt;
                                                ) : (
                                                    &lt;Bot className=<span class="hljs-string">"h-4 w-4"</span> /&gt;
                                                )}
                                            &lt;/AvatarFallback&gt;
                                        &lt;/Avatar&gt;

                                        {<span class="hljs-comment">/* Message Content */</span>}
                                        &lt;div className=<span class="hljs-string">"space-y-1 min-w-0 flex-1"</span>&gt;
                                            &lt;div
                                                className={cn(
                                                    <span class="hljs-string">"rounded-lg px-4 py-2.5 text-sm max-w-full"</span>,
                                                    isUser
                                                        ? <span class="hljs-string">"bg-primary text-primary-foreground"</span>
                                                        : <span class="hljs-string">"bg-muted"</span>
                                                )}
                                            &gt;
                                                &lt;div className=<span class="hljs-string">"whitespace-pre-wrap break-words leading-relaxed overflow-wrap-anywhere"</span>&gt;
                                                    {message.parts?.map(<span class="hljs-function">(<span class="hljs-params">part, index</span>) =&gt;</span> {
                                                        <span class="hljs-keyword">if</span> (part.type === <span class="hljs-string">"text"</span>) {
                                                            <span class="hljs-keyword">return</span> &lt;p key={index}&gt;{part.text}&lt;/p&gt;;
                                                        }
                                                        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
                                                    })}
                                                &lt;/div&gt;
                                            &lt;/div&gt;
                                        &lt;/div&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            );
                        })}

                        {<span class="hljs-comment">/* Loading Indicator */</span>}
                        {isLoading &amp;&amp; (
                            &lt;div className=<span class="hljs-string">"flex justify-start"</span>&gt;
                                &lt;div className=<span class="hljs-string">"flex items-center gap-3 max-w-[85%]"</span>&gt;
                                    &lt;Avatar className=<span class="hljs-string">"h-8 w-8"</span>&gt;
                                        &lt;AvatarFallback className=<span class="hljs-string">"bg-muted"</span>&gt;
                                            &lt;Bot className=<span class="hljs-string">"h-4 w-4"</span> /&gt;
                                        &lt;/AvatarFallback&gt;
                                    &lt;/Avatar&gt;
                                    &lt;div className=<span class="hljs-string">"bg-muted rounded-lg px-4 py-2.5"</span>&gt;
                                        &lt;div className=<span class="hljs-string">"flex items-center gap-2"</span>&gt;
                                            &lt;Loader2 className=<span class="hljs-string">"h-3 w-3 animate-spin"</span> /&gt;
                                            &lt;span className=<span class="hljs-string">"text-sm text-muted-foreground"</span>&gt;
                                                Thinking...
                                            &lt;/span&gt;
                                        &lt;/div&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        )}

                        {<span class="hljs-comment">/* Error Message */</span>}
                        {error &amp;&amp; (
                            &lt;div className=<span class="hljs-string">"flex justify-center px-4"</span>&gt;
                                &lt;div className=<span class="hljs-string">"bg-destructive/10 border border-destructive/20 rounded-lg p-3 max-w-md"</span>&gt;
                                    &lt;div className=<span class="hljs-string">"flex items-start gap-2"</span>&gt;
                                        &lt;AlertCircle className=<span class="hljs-string">"h-4 w-4 text-destructive mt-0.5"</span> /&gt;
                                        &lt;div className=<span class="hljs-string">"space-y-1"</span>&gt;
                                            &lt;p className=<span class="hljs-string">"text-sm text-destructive"</span>&gt;
                                                {error.message ||
                                                    <span class="hljs-string">"Something went wrong. Please try again."</span>}
                                            &lt;/p&gt;
                                            &lt;Button
                                                onClick={<span class="hljs-function">() =&gt;</span> regenerate()}
                                                variant=<span class="hljs-string">"ghost"</span>
                                                size=<span class="hljs-string">"sm"</span>
                                                className=<span class="hljs-string">"h-7 px-2 text-xs"</span>
                                            &gt;
                                                Retry last message
                                            &lt;/Button&gt;
                                        &lt;/div&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        )}
                    &lt;/div&gt;
                &lt;/ScrollArea&gt;

                {<span class="hljs-comment">/* Input Area */</span>}
                &lt;CardContent className=<span class="hljs-string">"border-t p-4 flex-shrink-0"</span>&gt;
                    &lt;form onSubmit={handleSubmit} className=<span class="hljs-string">"flex gap-2"</span>&gt;
                        &lt;input
                            ref={inputRef}
                            <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                            value={input}
                            onChange={handleInputChange}
                            placeholder=<span class="hljs-string">"Type your message..."</span>
                            disabled={isLoading}
                            className={cn(
                                <span class="hljs-string">"flex-1 px-3 py-2 text-sm rounded-md border border-input bg-background"</span>,
                                <span class="hljs-string">"placeholder:text-muted-foreground"</span>,
                                <span class="hljs-string">"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"</span>,
                                <span class="hljs-string">"disabled:cursor-not-allowed disabled:opacity-50"</span>
                            )}
                            autoFocus
                        /&gt;

                        {isLoading ? (
                            &lt;Button
                                <span class="hljs-keyword">type</span>=<span class="hljs-string">"button"</span>
                                onClick={stop}
                                variant=<span class="hljs-string">"destructive"</span>
                                size=<span class="hljs-string">"sm"</span>
                            &gt;
                                Stop
                            &lt;/Button&gt;
                        ) : (
                            &lt;Button
                                <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
                                disabled={!input.trim()}
                                size=<span class="hljs-string">"sm"</span>
                            &gt;
                                &lt;Send className=<span class="hljs-string">"h-4 w-4"</span> /&gt;
                                &lt;span className=<span class="hljs-string">"ml-2 hidden sm:inline"</span>&gt;
                                    Send
                                &lt;/span&gt;
                            &lt;/Button&gt;
                        )}
                    &lt;/form&gt;

                    {<span class="hljs-comment">/* Character Counter */</span>}
                    {input.length &gt; <span class="hljs-number">0</span> &amp;&amp; (
                        &lt;div className=<span class="hljs-string">"mt-2 text-xs text-muted-foreground text-right"</span>&gt;
                            {input.length} / <span class="hljs-number">4000</span>
                        &lt;/div&gt;
                    )}
                &lt;/CardContent&gt;
            &lt;/Card&gt;
        &lt;/div&gt;
    );
}
</code></pre>
<p>This code creates the main chat interface component where users interact with your fine-tuned AI model. It primarily uses the <code>useChat</code> hook from Vercel's AI SDK, which manages all the complex messaging tasks, such as sending messages, receiving streaming responses, managing conversation state, and handling errors. The component sets up several React hooks: <code>useRef</code> for managing DOM elements like the scroll area and input field, <code>useState</code> for the input text, and <code>useEffect</code> for side effects like auto-scrolling and showing a welcome message when the chat first loads.</p>
<p>The UI is built using shadcn/ui components to create a polished, professional look with minimal effort. The layout has three main sections: a header showing the AI assistant's status (with a pulsing green dot to indicate it's online), a scrollable message area in the middle, and an input form at the bottom. Each message is displayed with an avatar (a user icon for human messages and a bot icon for AI responses) and styled differently based on who sent it. User messages appear on the right with a primary color background, while assistant messages appear on the left with a muted background. The component includes thoughtful UX details like automatic scrolling to the latest message, focus management that returns to the input field after sending, and a character counter showing how close you are to the 4000-character limit.</p>
<p>The component also manages different states smoothly: it shows a "Thinking..." animation with a spinning loader while the AI creates a response, displays error messages with a retry button if something goes wrong, and even lets users stop the response mid-stream if it takes too long. When loading, the send button changes into a "Stop" button, giving users full control over the conversation. Everything is wrapped in responsive styling that adjusts to different screen sizes, ensuring your chat interface looks great whether users are on desktop or mobile devices.</p>
<p>Now, update the main page to use the chat component:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// web/app/page.tsx</span>
<span class="hljs-keyword">import</span> Chat <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/chat"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">"h-screen"</span>&gt;
            &lt;Chat /&gt;
        &lt;/main&gt;
    );
}
</code></pre>
<p>This chat interface uses shadcn/ui components to create a polished, professional look. It includes features like real-time message streaming, animated loading states, error handling with retry options, automatic scrolling to the latest messages, a responsive design for all devices, keyboard shortcuts, accessibility features, and a character counter to keep users informed.</p>
<h2 id="heading-step-5-api-route-integration">Step 5: API Route Integration</h2>
<p>Now let's create the backend API route that connects our chat interface to the fine-tuned OpenAI model with proper streaming support.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// web/app/api/chat/route.ts</span>
<span class="hljs-keyword">import</span> { openai } <span class="hljs-keyword">from</span> <span class="hljs-string">"@ai-sdk/openai"</span>;
<span class="hljs-keyword">import</span> { streamText, convertToModelMessages } <span class="hljs-keyword">from</span> <span class="hljs-string">"ai"</span>;
<span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: NextRequest</span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Parse request body</span>
        <span class="hljs-keyword">const</span> body = <span class="hljs-keyword">await</span> req.json();
        <span class="hljs-keyword">const</span> { messages } = body;

        <span class="hljs-comment">// Validate messages</span>
        <span class="hljs-keyword">if</span> (!messages || !<span class="hljs-built_in">Array</span>.isArray(messages)) {
            <span class="hljs-keyword">return</span> NextResponse.json(
                { error: <span class="hljs-string">"Invalid request format"</span> },
                { status: <span class="hljs-number">400</span> }
            );
        }

        <span class="hljs-comment">// Convert UI messages to model messages using AI SDK utility</span>
        <span class="hljs-keyword">const</span> modelMessages = convertToModelMessages(messages);

        <span class="hljs-comment">// Check if we have any valid messages</span>
        <span class="hljs-keyword">if</span> (modelMessages.length === <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> NextResponse.json(
                { error: <span class="hljs-string">"No valid messages provided"</span> },
                { status: <span class="hljs-number">400</span> }
            );
        }

        <span class="hljs-comment">// Add system prompt to prevent hallucination and guide the model</span>
        <span class="hljs-keyword">const</span> systemPrompt = {
            role: <span class="hljs-string">"system"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
            content: <span class="hljs-string">`You are a helpful assistant answering questions about MTechZilla, a software development company. 

IMPORTANT INSTRUCTIONS:
- Only answer questions based on information you were specifically trained on about MTechZilla
- If you don't know something or weren't trained on specific information, say "I don't have that specific information in my training data"
- Never make up or guess information about MTechZilla
- Be accurate and only provide information you're confident about

Answer questions accurately based on your training data about MTechZilla's services, technologies, and approach.`</span>
        };

        <span class="hljs-comment">// Prepend system message if not already present</span>
        <span class="hljs-keyword">const</span> hasSystemMessage = modelMessages.some(<span class="hljs-function"><span class="hljs-params">msg</span> =&gt;</span> msg.role === <span class="hljs-string">'system'</span>);
        <span class="hljs-keyword">if</span> (!hasSystemMessage) {
            modelMessages.unshift(systemPrompt);
        }

        <span class="hljs-comment">// Load fine-tuned model ID from environment variable</span>
        <span class="hljs-keyword">const</span> fineTunedModelId = process.env.FINE_TUNED_MODEL;

        <span class="hljs-comment">// Ensure we have a valid model ID</span>
        <span class="hljs-keyword">if</span> (!fineTunedModelId) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"No fine-tuned model ID available"</span>);
        }

        <span class="hljs-comment">// Call OpenAI with streaming using the new AI SDK</span>
        <span class="hljs-keyword">const</span> result = streamText({
            model: openai(fineTunedModelId),
            messages: modelMessages,
            temperature: <span class="hljs-number">0.1</span>, <span class="hljs-comment">// Lower temperature for more deterministic, factual responses</span>
        });

        <span class="hljs-comment">// Log stream start</span>
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Stream started`</span>);

        <span class="hljs-comment">// Return UI message stream response for useChat compatibility</span>
        <span class="hljs-keyword">return</span> result.toUIMessageStreamResponse({
            headers: {
                <span class="hljs-string">"Cache-Control"</span>: <span class="hljs-string">"no-cache, no-transform"</span>,
                <span class="hljs-string">"X-Accel-Buffering"</span>: <span class="hljs-string">"no"</span>,
            },
        });
    } <span class="hljs-keyword">catch</span> (error: unknown) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Chat API Error:"</span>, error);

        <span class="hljs-comment">// Handle specific errors</span>
        <span class="hljs-keyword">if</span> (error &amp;&amp; <span class="hljs-keyword">typeof</span> error === <span class="hljs-string">"object"</span> &amp;&amp; <span class="hljs-string">"status"</span> <span class="hljs-keyword">in</span> error) {
            <span class="hljs-keyword">const</span> errorWithStatus = error <span class="hljs-keyword">as</span> { status: <span class="hljs-built_in">number</span> };

            <span class="hljs-keyword">if</span> (errorWithStatus.status === <span class="hljs-number">401</span>) {
                <span class="hljs-keyword">return</span> NextResponse.json(
                    {
                        error: <span class="hljs-string">"Authentication failed. Check API key configuration."</span>,
                    },
                    { status: <span class="hljs-number">401</span> }
                );
            }

            <span class="hljs-keyword">if</span> (errorWithStatus.status === <span class="hljs-number">404</span>) {
                <span class="hljs-keyword">return</span> NextResponse.json(
                    {
                        error: <span class="hljs-string">"Model not found. Check your fine-tuned model ID."</span>,
                    },
                    { status: <span class="hljs-number">404</span> }
                );
            }

            <span class="hljs-keyword">if</span> (errorWithStatus.status === <span class="hljs-number">429</span>) {
                <span class="hljs-keyword">return</span> NextResponse.json(
                    {
                        error: <span class="hljs-string">"OpenAI rate limit reached. Please try again later."</span>,
                    },
                    { status: <span class="hljs-number">429</span> }
                );
            }
        }

        <span class="hljs-comment">// Generic error</span>
        <span class="hljs-keyword">return</span> NextResponse.json(
            { error: <span class="hljs-string">"An error occurred. Please try again."</span> },
            { status: <span class="hljs-number">500</span> }
        );
    }
}
</code></pre>
<p>This code sets up the backend API endpoint that connects your chat interface to your fine-tuned OpenAI model. When a user sends a message, this Next.js API route receives the request, checks if the messages are correctly formatted, and converts them from the UI format to the structure that OpenAI's API needs using the AI SDK's <code>convertToModelMessages</code> tool. A key feature is the system prompt injection before sending messages to your model. The code automatically adds specific instructions, telling the AI to only respond based on its training data about MTechZilla (in this example) and to clearly say "I don't have that specific information" instead of making things up. This is crucial for preventing errors and ensuring the chatbot remains accurate and reliable.</p>
<p>The route loads your fine-tuned model ID from environment variables to keep sensitive settings secure. It uses Vercel's AI SDK to call OpenAI with streaming enabled, so responses appear word-by-word in real-time instead of waiting for the entire response. The temperature is set to 0.1, making the model more predictable and factual—perfect for a customer service chatbot where accuracy is more important than creativity. The <code>streamText</code> function handles all the streaming details, and the response is returned in a format that works directly with the <code>useChat</code> hook in your frontend component.</p>
<p>The code includes thorough error handling for common issues: authentication failures (invalid API keys), model not found errors (incorrect model ID), rate limiting (too many requests), and general server errors. Each error type returns a specific, helpful message to make debugging easier during development and to provide clear feedback to users if something goes wrong. The response headers include cache control directives to ensure fresh data and prevent buffering issues during streaming, guaranteeing a smooth, real-time chat experience for your users.</p>
<h2 id="heading-step-6-testing-your-application">Step 6: Testing Your Application</h2>
<p>With everything set up, let's test the complete application and ensure it works correctly.</p>
<p>First, make sure you've added your fine-tuned model ID to the environment file:</p>
<pre><code class="lang-bash">OPENAI_API_KEY=sk-...your-api-key...
OPENAI_ORG_ID=org-...your-org-id...
FINE_TUNED_MODEL=ft:gpt-4.1-nano-2025-04-14:... <span class="hljs-comment"># Copy from scripts/model_info.json</span>
</code></pre>
<p>Start the development server:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>Open <a target="_blank" href="http://localhost:3000">http://localhost:3000</a> in your browser, and you'll see the following UI:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757941475393/692f92ff-11bc-451d-a9fa-a288c87f7d78.png" alt="Screenshot of a chat window with &quot;Custom AI Assistant&quot; at the top. The AI greets the user, saying, &quot;Hello! I'm your custom AI assistant, trained on specific content. How can I help you today?&quot; A message input field and send button are at the bottom." class="image--center mx-auto" width="3448" height="1888" loading="lazy"></p>
<p>Test various scenarios:</p>
<ol>
<li><p><strong>Domain Knowledge Test</strong>: Ask questions related to your training data</p>
</li>
<li><p><strong>Conversation Flow</strong>: Have a multi-turn conversation</p>
</li>
<li><p><strong>Edge Cases</strong>: Try very long inputs, rapid messages, network interruptions</p>
</li>
<li><p><strong>Mobile Testing</strong>: Test on different screen sizes</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congratulations! You've successfully fine-tuned a GPT-4.1 model and built a chat application ready for production, showing the power of custom AI. We've transformed raw website content into structured training data, used OpenAI's latest models for fine-tuning, and built a modern Next.js app with real-time streaming and a nice interface. The key to successful fine-tuning is knowing when it's the right choice - use it for a consistent brand voice, specialised domain knowledge, and reducing RAG complexity, but consider the RAG option for information that changes often. Our modular project structure keeps training scripts separate from the web app, making it easy to retrain models and add new features.</p>
<p>As you continue, remember that fine-tuning is a step-by-step process. Watch how users interact, gather feedback, and keep improving your model with new training examples. Avoid common mistakes like using too little data (aim for 50+ good examples), ignoring validation errors, and not setting up proper rate limits and error handling. In 2025, AI customisation is changing fast, with trends toward more efficient models, continuous learning, and hybrid methods that mix different techniques. What used to need a team of ML engineers can now be done by few developers with the right skills. You now have the tools and knowledge to create specialised AI applications that truly understand and serve your specific area.</p>
<p><em>For founders considering AI adoption, I've created a free guide:</em> <a target="_blank" href="https://www.notion.so/AI-or-No-AI-The-2025-Founder-s-Decision-Playbook-25ec9ed724ec80668a8fc42bb804515a"><em>AI or No AI? The 2025 Founder's Decision Playbook</em></a> <em>- a framework to help decide when AI truly adds value.</em></p>
<p><em>Feel free to connect with me on</em> <a target="_blank" href="https://www.linkedin.com/in/sharvinshah/"><em>LinkedIn</em></a> <em>and</em> <a target="_blank" href="https://twitter.com/sharvinshah26"><em>Twitter</em></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use RxStomp with React: Build A Chat App ]]>
                </title>
                <description>
                    <![CDATA[ STOMP is an amazingly simple yet powerful protocol for sending messages implemented by popular servers like RabbitMQ, ActiveMQ, and Apollo. Using STOMP over WebSocket is a straightforward protocol, making it a popular choice for sending messages from... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-chat-app-with-stomp-and-react/</link>
                <guid isPermaLink="false">671901e20aa5e141c2db418f</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ websocket ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Harsh Deep ]]>
                </dc:creator>
                <pubDate>Wed, 23 Oct 2024 14:02:10 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729648105968/2926c346-0058-4a84-981e-2ff4bd6833df.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>STOMP is an amazingly simple yet powerful protocol for sending messages implemented by popular servers like RabbitMQ, ActiveMQ, and Apollo. Using STOMP over WebSocket is a straightforward protocol, making it a popular choice for sending messages from a web browser because protocols like AMQP are limited by major browsers blocking TCP connections.</p>
<p>To use STOMP over WebSocket, you can use <a target="_blank" href="https://www.npmjs.com/package/@stomp/stompjs">@stomp/stompjs</a>, but that has tricky callbacks and a complicated API that caters to more specialized use cases. Luckily, there’s also the lesser-known <a target="_blank" href="https://www.npmjs.com/package/@stomp/rx-stomp">@stompjs/rx-stomp</a> which provides a nice interface via <a target="_blank" href="https://www.npmjs.com/package/rxjs">RxJS</a> observables. Observables aren't exclusive to Angular, and they fit quite well with how React works. It's a neat interface when composing complex workflows and pipelines with many different message sources.</p>
<p>The tutorial follows a somewhat similar path as the initial version in <a target="_blank" href="https://stomp-js.github.io/guide/rx-stomp/rx-stomp-with-angular.html">Angular</a>, but the component structure and code style are tuned towards the functional style of React.</p>
<p><strong>Note:</strong> This tutorial is written with <code>strict</code> TypeScript, but the JavaScript code is almost identical since we only have 5 type declarations. For the JS version, you can skip the type imports and definitions.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-goals">Goals</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-starter-stomp-server-with-rabbitmq">Starter STOMP Server with RabbitMQ</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-starter-react-template">Starter React Template</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-install-rxstomp">How to Install RxStomp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-manage-connection-and-disconnection-with-the-stomp-server">How to Manage Connection and Disconnection with the STOMP Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-monitor-the-connection-status">How to Monitor the Connection Status</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-send-messages">How to Send Messages</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-receive-messages">How to Receive Messages</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-goals">Goals</h2>
<p>Here, we’ll build a simplified chatroom application that shows various aspects of RxStomp across different components. Overall, we want to have:</p>
<ul>
<li><p>A React frontend connected with RxStomp to a STOMP server.</p>
</li>
<li><p>A live connection status display based on our connection to the STOMP server.</p>
</li>
<li><p>Pub/Sub logic for any configurable topic.</p>
</li>
<li><p>Splitting RxStomp logic across multiple components to show how to separate logic and responsibility.</p>
</li>
<li><p>Aligning RxStomp connection/subscription lifecycles with React component lifecycles to ensure that there are no leaks or unclosed watchers.</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>You should have a STOMP server running so that the React application can connect to it. Here, we’ll use RabbitMQ with the <code>rabbitmq_web_stomp</code> extension.</p>
</li>
<li><p>Latest React version. This tutorial will use v18, although older versions will probably work as well.</p>
</li>
<li><p>Some familiarity with observables will also help.</p>
</li>
</ul>
<h2 id="heading-starter-stomp-server-with-rabbitmq">Starter STOMP Server with RabbitMQ</h2>
<p>If you’d like to use RabbitMQ too (not strictly required), here’s are <a target="_blank" href="https://www.rabbitmq.com/docs/download">installation guides for different operating systems</a>. To add the extension, you’ll need to run:</p>
<pre><code class="lang-bash">$ rabbitmq-plugins <span class="hljs-built_in">enable</span> rabbitmq_web_stomp
</code></pre>
<p>If you’re able to use <code>Docker</code>, a Docker file similar to <a target="_blank" href="https://github.com/harsh183/rabbitmq-intro/blob/master/code_examples/Dockerfile">this</a> will set everything needed for the tutorial:</p>
<pre><code class="lang-bash">FROM rabbitmq:3.8.8-alpine

run rabbitmq-plugins <span class="hljs-built_in">enable</span> --offline rabbitmq_web_stomp

EXPOSE 15674
</code></pre>
<h2 id="heading-starter-react-template">Starter React Template</h2>
<p>For this tutorial, we'll use <a target="_blank" href="https://vite.dev/guide/">Vite</a>'s <code>react-ts</code> template. The central part of our application will be in the <code>App</code> component, and we'll create child components for other specific STOMP functionality.</p>
<h2 id="heading-how-to-install-rxstomp">How to Install RxStomp</h2>
<p>We’ll use the <code>@stomp/rx-stomp</code> npm package:</p>
<pre><code class="lang-bash">$ npm i @stomp/rx-stomp rxjs
</code></pre>
<p>This will install version <code>2.0.0</code></p>
<p><strong>Note</strong>: This tutorial still works without explicitly specifying <code>rxjs</code> since it's a sister dependency, but it's good practice to be explicit about it.</p>
<h2 id="heading-how-to-manage-connection-and-disconnection-with-the-stomp-server">How to Manage Connection and Disconnection with the STOMP Server</h2>
<p>Now, let's open <strong>App.tsx</strong> and initialize our <code>RxStomp</code> client. Since the client isn't a state that will change for rendering, we’ll wrap it in the <code>useRef</code> Hook.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>
<span class="hljs-keyword">import</span> { useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> rxStompRef = useRef(<span class="hljs-keyword">new</span> RxStomp())
  <span class="hljs-keyword">const</span> rxStomp = rxStompRef.current

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h1&gt;Hello RxStomp!&lt;/h1&gt;
    &lt;/&gt;
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App
</code></pre>
<p>Assuming the default ports and authentication details, we’ll define some configuration for our connection next.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>

<span class="hljs-keyword">import</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStompConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>
...
<span class="hljs-keyword">const</span> rxStompConfig: RxStompConfig = {
  brokerURL: <span class="hljs-string">'ws://localhost:15674/ws'</span>,
  connectHeaders: {
    login: <span class="hljs-string">'guest'</span>,
    passcode: <span class="hljs-string">'guest'</span>,
  },
  debug: <span class="hljs-function">(<span class="hljs-params">msg</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(), msg)
  },
  heartbeatIncoming: <span class="hljs-number">0</span>,
  heartbeatOutgoing: <span class="hljs-number">20000</span>,
  reconnectDelay: <span class="hljs-number">200</span>,
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  ...
</code></pre>
<p>For a better dev experience, we logged all messages with timestamps to a local console and set low timer frequencies. Your configuration should be quite different for your production application, so check out the <a target="_blank" href="https://stomp-js.github.io/api-docs/latest/classes/RxStompConfig.html">RxStompConfig docs</a> for all the options available.</p>
<p>Next, we’ll pass the configuration to <code>rxStomp</code> inside a <code>useEffect</code> Hook. This manages the connection's activation alongside the component lifecycle.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>
...
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> rxStompRef = useRef(<span class="hljs-keyword">new</span> RxStomp())
  <span class="hljs-keyword">const</span> rxStomp = rxStompRef.current

  useEffect(<span class="hljs-function">() =&gt;</span> {
    rxStomp.configure(rxStompConfig)
    rxStomp.activate()

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> { 
      rxStomp.deactivate() 
    }
  })
  ...
</code></pre>
<p>While there's no visual change in our app, checking the logs should show connection and ping logs. Here's an example of what that should look like:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">Date</span> ... &gt;&gt;&gt; CONNECT
login:guest
passcode:guest
accept-version:<span class="hljs-number">1.2</span>,<span class="hljs-number">1.1</span>,<span class="hljs-number">1.0</span>
heart-beat:<span class="hljs-number">20000</span>,<span class="hljs-number">0</span>

<span class="hljs-built_in">Date</span> ... Received data 
<span class="hljs-built_in">Date</span> ... &lt;&lt;&lt; CONNECTED
version:<span class="hljs-number">1.2</span>
heart-beat:<span class="hljs-number">0</span>,<span class="hljs-number">20000</span>
session:session-EJqaGQijDXqlfc0eZomOqQ
server:RabbitMQ/<span class="hljs-number">4.0</span><span class="hljs-number">.2</span>
content-length:<span class="hljs-number">0</span>

<span class="hljs-built_in">Date</span> ... connected to server RabbitMQ/<span class="hljs-number">4.0</span><span class="hljs-number">.2</span> 
<span class="hljs-built_in">Date</span> ... send PING every <span class="hljs-number">20000</span>ms 
<span class="hljs-built_in">Date</span> ... &lt;&lt;&lt; PONG 
<span class="hljs-built_in">Date</span> ... &gt;&gt;&gt; PING
</code></pre>
<p><strong>Note:</strong> Generally, if you see duplicate logs, it may be a sign that a deactivation or unsubscribe functionality wasn't implemented correctly. React renders each component twice in a dev environment to help people catch these bugs via <code>React.StrictMode</code></p>
<h2 id="heading-how-to-monitor-the-connection-status">How to Monitor the Connection Status</h2>
<p>RxStomp has a <a target="_blank" href="https://stomp-js.github.io/api-docs/latest/miscellaneous/enumerations.html#RxStompState">RxStompState enum</a> that represents possible connection states with our broker. Our next goal is to display the connection status in our UI.</p>
<p>Let's create a new component for this called <code>Status.tsx</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Status.tsx</span>
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Status</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [connectionStatus, setConnectionStatus] = useState(<span class="hljs-string">''</span>)

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Connection Status: {connectionStatus}&lt;/h2&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>We can use the <code>rxStomp.connectionState$</code> observable to bind to our <code>connectionStatus</code> string. Similar to how we used <code>useEffect</code>, we’ll use the unmount action to <code>unsubscribe()</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Status.tsx</span>
<span class="hljs-keyword">import</span> { RxStompState } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Status</span>(<span class="hljs-params">props: { rxStomp: RxStomp }</span>) </span>{
  <span class="hljs-keyword">const</span> [connectionStatus, setConnectionStatus] = useState(<span class="hljs-string">''</span>)

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> statusSubscription = props.rxStomp.connectionState$.subscribe(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> {
      setConnectionStatus(RxStompState[state])
    })

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      statusSubscription.unsubscribe()
    }
  }, [])

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Connection Status: {connectionStatus}&lt;/h2&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>To view it, we include it in our app:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>
<span class="hljs-keyword">import</span> Status <span class="hljs-keyword">from</span> <span class="hljs-string">'./Status'</span>
...
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h1&gt;Hello RxStomp!&lt;/h1&gt;

      &lt;Status rxStomp={rxStomp}/&gt;
    &lt;/&gt;
  )
</code></pre>
<p>At this point, you should have a working visual indicator on the screen. Try playing around by taking the STOMP server down and see if the logs work as expected.</p>
<h2 id="heading-how-to-send-messages">How to Send Messages</h2>
<p>Let's create a simple chatroom to show a simplified end-to-end messaging flow with the broker.</p>
<p>We can place the functionality in a new <code>Chatroom</code> component. First, we can create the component with a custom <code>username</code> and <code>message</code> field that's bound to inputs.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Chatroom.tsx</span>
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Chatroom</span>(<span class="hljs-params">props: {rxStomp: RxStomp}</span>) </span>{
  <span class="hljs-keyword">const</span> [message, setMessage] = useState(<span class="hljs-string">''</span>)
  <span class="hljs-keyword">const</span> [userName, setUserName] = useState(<span class="hljs-string">`user<span class="hljs-subst">${<span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">1000</span>)}</span>`</span>)

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Chatroom&lt;/h2&gt;

      &lt;label htmlFor=<span class="hljs-string">'username'</span>&gt;Username: &lt;/label&gt;
      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
        name=<span class="hljs-string">'username'</span>
        value={userName}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setUserName(e.target.value)}
      /&gt;

      &lt;label htmlFor=<span class="hljs-string">'message'</span>&gt;Message: &lt;/label&gt;

      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
        value={message}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setMessage(e.target.value)}
        name=<span class="hljs-string">'message'</span>
      /&gt;
    &lt;/&gt;
  )    
}
</code></pre>
<p>Let’s include this within our <strong>App</strong> with a toggle to join the chatroom:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>
<span class="hljs-keyword">import</span> { useEffect, useState, useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> Chatroom <span class="hljs-keyword">from</span> <span class="hljs-string">'./Chatroom'</span>
...
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [joinedChatroom, setJoinedChatroom] = useState(<span class="hljs-literal">false</span>)
  ...
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h1&gt;Hello RxStomp!&lt;/h1&gt;

      &lt;Status rxStomp={rxStomp}/&gt;

      {!joinedChatroom &amp;&amp; (
        &lt;button onClick={<span class="hljs-function">() =&gt;</span> setJoinedChatroom(<span class="hljs-literal">true</span>)}&gt;
          Join chatroom!
        &lt;/button&gt;
      )}

      {joinedChatroom &amp;&amp; (
        &lt;&gt;
          &lt;button onClick={<span class="hljs-function">() =&gt;</span> setJoinedChatroom(<span class="hljs-literal">false</span>)}&gt;
            Leave chatroom!
          &lt;/button&gt;

          &lt;Chatroom rxStomp={rxStomp}/&gt;
        &lt;/&gt;
      )}

    &lt;/&gt;
  )
</code></pre>
<p>Time to actually send messages. STOMP is best for sending text-based messages (binary data is also possible). We’ll define the structure of the data we're sending in a new <strong>types</strong> file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// types.ts</span>
<span class="hljs-keyword">interface</span> ChatMessage {
  userName: <span class="hljs-built_in">string</span>,
  message: <span class="hljs-built_in">string</span>
}
</code></pre>
<p><strong>Note:</strong> If you're not using TypeScript, you can skip adding this type definition.</p>
<p>Next, let's use JSON to serialize the message and send messages to our STOMP server using <code>.publish</code> with a destination topic and our JSON <code>body</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Chatroom.tsx</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ChatMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'./types'</span>
...
<span class="hljs-keyword">const</span> CHATROOM_NAME = <span class="hljs-string">'/topic/test'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Chatroom</span>(<span class="hljs-params">props: {rxStomp: RxStomp}</span>) </span>{
  ...
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendMessage</span>(<span class="hljs-params">chatMessage: ChatMessage</span>) </span>{
    <span class="hljs-keyword">const</span> body = <span class="hljs-built_in">JSON</span>.stringify({ ...chatMessage })
    props.rxStomp.publish({ destination: CHATROOM_NAME, body })
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Sent <span class="hljs-subst">${body}</span>`</span>)
    setMessage(<span class="hljs-string">''</span>)
  }

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Chatroom&lt;/h2&gt;

      &lt;label htmlFor=<span class="hljs-string">"username"</span>&gt;Username: &lt;/label&gt;
      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
        name=<span class="hljs-string">"username"</span>
        value={userName}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setUserName(e.target.value)}
      /&gt;

      &lt;label htmlFor=<span class="hljs-string">"message"</span>&gt;Message: &lt;/label&gt;

      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
        value={message}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setMessage(e.target.value)}
        name=<span class="hljs-string">"message"</span>
      /&gt;

      &lt;button onClick={<span class="hljs-function">() =&gt;</span> sendMessage({userName, message})}&gt;Send Message&lt;/button&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>To test it out, try clicking the <strong>Send Message</strong> button a few times and see if the serialization works fine. While you won't be able to see any visual changes yet, the console logs should show it:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">Date</span> ... &gt;&gt;&gt; SEND
destination:<span class="hljs-regexp">/topic/</span>test
content-length:<span class="hljs-number">45</span>

Sent {<span class="hljs-string">"userName"</span>:<span class="hljs-string">"user722"</span>,<span class="hljs-string">"message"</span>:<span class="hljs-string">"1234567890"</span>}
</code></pre>
<h2 id="heading-how-to-receive-messages">How to Receive Messages</h2>
<p>We’ll create a new component to show the list of messages from all the users. For now, we'll use the same type, pass the topic name as a prop, and display everything as a list. All this goes into a new component called <code>MessageList</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/MessageDisplay.tsx</span>
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ChatMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'./types'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MessageDisplay</span>(<span class="hljs-params">props: {rxStomp: RxStomp, topic: <span class="hljs-built_in">string</span>}</span>) </span>{
  <span class="hljs-keyword">const</span> [chatMessages, setChatMessages] = useState&lt;ChatMessage[]&gt;([
    {userName: <span class="hljs-string">'admin'</span>, message: <span class="hljs-string">`Welcome to <span class="hljs-subst">${props.topic}</span> room!`</span>}
  ])

  <span class="hljs-keyword">return</span>(
  &lt;&gt;
  &lt;h2&gt;Chat Messages&lt;/h2&gt;
  &lt;ul&gt;
    {chatMessages.map(<span class="hljs-function">(<span class="hljs-params">chatMessage, index</span>) =&gt;</span> 
      &lt;li key={index}&gt;
        &lt;strong&gt;{chatMessage.userName}&lt;/strong&gt;: {chatMessage.message}
      &lt;/li&gt;
    )}
  &lt;/ul&gt;
  &lt;/&gt;
  )
}
</code></pre>
<p>Time to bring everything together!</p>
<p>We can display our messages to display within our <code>Chatroom</code> component by adding it to the bottom.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Chatroom.tsx</span>
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ChatMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'./types'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>

<span class="hljs-keyword">import</span> MessageDisplay <span class="hljs-keyword">from</span> <span class="hljs-string">'./MessageDisplay'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CHATROOM_NAME = <span class="hljs-string">'/topic/test'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Chatroom</span>(<span class="hljs-params">props: {rxStomp: RxStomp}</span>) </span>{
  <span class="hljs-keyword">const</span> [message, setMessage] = useState(<span class="hljs-string">''</span>)
  <span class="hljs-keyword">const</span> [userName, setUserName] = useState(<span class="hljs-string">`user<span class="hljs-subst">${<span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">1000</span>)}</span>`</span>)

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendMessage</span>(<span class="hljs-params">chatMessage: ChatMessage</span>) </span>{
    <span class="hljs-keyword">const</span> body = <span class="hljs-built_in">JSON</span>.stringify({ ...chatMessage })
    props.rxStomp.publish({ destination: CHATROOM_NAME, body })
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Sent <span class="hljs-subst">${body}</span>`</span>)
    setMessage(<span class="hljs-string">''</span>)
  }

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Chatroom&lt;/h2&gt;

      &lt;label htmlFor=<span class="hljs-string">'username'</span>&gt;Username: &lt;/label&gt;
      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
        name=<span class="hljs-string">'username'</span>
        value={userName}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setUserName(e.target.value)}
      /&gt;

      &lt;label htmlFor=<span class="hljs-string">'message'</span>&gt;Message: &lt;/label&gt;

      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
        value={message}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setMessage(e.target.value)}
        name=<span class="hljs-string">'message'</span>
      /&gt;

      &lt;button onClick={<span class="hljs-function">() =&gt;</span> sendMessage({userName, message})}&gt;Send Message&lt;/button&gt;

      &lt;MessageDisplay rxStomp={props.rxStomp} topic={CHATROOM_NAME} /&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>And once you've verified the static display working locally, we can make this display dynamic using an RxJS Observable to receive our chat messages. </p>
<p>Similar to managing the subscription with the <code>Status</code> component, we set up the subscription on mount, and unsubscribe on unmount.</p>
<p>Using RxJS <code>pipe</code> and <code>map</code>, we can deserialize our JSON back to our <code>ChatMessage</code>. The modular design can let you set up a more complicated pipeline as needed using <code>RxJS</code> operators.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/MessageDisplay.tsx</span>
...
<span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MessageDisplay</span>(<span class="hljs-params">props: {rxStomp: RxStomp, topic: <span class="hljs-built_in">string</span>}</span>) </span>{
  <span class="hljs-keyword">const</span> [chatMessages, setChatMessages] = useState&lt;ChatMessage[]&gt;([
    {userName: <span class="hljs-string">'admin'</span>, message: <span class="hljs-string">`Welcome to <span class="hljs-subst">${props.topic}</span> room!`</span>}
  ])

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> subscription = props.rxStomp
      .watch(props.topic)
      .pipe(map(<span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> <span class="hljs-built_in">JSON</span>.parse(message.body)))
      .subscribe(<span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> setChatMessages(<span class="hljs-function">(<span class="hljs-params">chatMessages</span>) =&gt;</span> [...chatMessages, message]))

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      subscription.unsubscribe()
    }
  }, [])

  ...
</code></pre>
<p>At this point, the chat GUI should show messages correctly, and you can experiment with opening multiple tabs as different users.</p>
<p>Another thing to try here is turning off the STOMP server, sending a few messages, and turning it back on. The messages should get queued locally and dispatched once the server is ready to go. Neat!</p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, we:</p>
<ul>
<li><p>Installed <code>@stomp/rx-stomp</code> for a nice dev experience.</p>
</li>
<li><p>Set up <code>RxStompConfig</code> to configure our client with the connection details, debugger logging and timer settings.</p>
</li>
<li><p>Used <code>rxStomp.activate</code> and <code>rxStomp.deactivate</code> to manage the client’s main lifecycle.</p>
</li>
<li><p>Monitored the subscription state using <code>rxStomp.connectionState$</code> observable.</p>
</li>
<li><p>Published messages using <code>rxStomp.publish</code> with configurable destinations and message bodies.</p>
</li>
<li><p>Created an observable for a given topic using <code>rxStomp.watch</code>.</p>
</li>
<li><p>Used both console logs and React components to see the library in action, and verify functionality and fault tolerance.</p>
</li>
</ul>
<p>You can find the final code on Gitlab: <a target="_blank" href="https://gitlab.com/harsh183/rxstomp-react-tutorial">https://gitlab.com/harsh183/rxstomp-react-tutorial</a>. Feel free to use it as a starter template too and report any issues that may come up.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Real-Time Chat App With Go, Fiber and HTMX ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, you'll build a simple real-time chat app using Go, Fiber and HTMX.  You will learn how to leverage the versatility of Fiber by making use of a WebSocket. You'll also learn how to create a reactive frontend without the use of JavaScr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/real-time-chat-with-go-fiber-htmx/</link>
                <guid isPermaLink="false">66b90552e2f97a1f15f5f769</guid>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Akinwumi Iyanuoluwa Ayomiposi ]]>
                </dc:creator>
                <pubDate>Thu, 06 Jun 2024 18:06:35 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/06/websocket.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, you'll build a simple real-time chat app using Go, Fiber and HTMX. </p>
<p>You will learn how to leverage the versatility of Fiber by making use of a WebSocket. You'll also learn how to create a reactive frontend without the use of JavaScript.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>A good understanding of Go and HTTP servers.</li>
<li>Go must be installed (Go version 1.22 will be used in this project).</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-getting-started">Getting Started</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-dependencies">How to Install Dependencies</a></li>
<li><a class="post-section-overview" href="#heading-maingo-file">main.go File</a></li>
<li><a class="post-section-overview" href="#heading-static-files">Static Files</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-static-files">How to Configure Static Files</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-handlers">How to Create Handlers</a></li>
<li><a class="post-section-overview" href="#heading-messagesgo-file">messages.go File</a></li>
<li><a class="post-section-overview" href="#heading-websocketgo-file">websocket.go File</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-a-websocket-to-routes-and-htmx">How to Add a WebSocket to Routes and HTMX</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a>  </li>
</ul>
<h2 id="heading-getting-started">Getting Started</h2>
<p>First, create a new folder named <strong>go-chat</strong>. Initiate Go in your project by running <code>go mod init pakacage_name</code>, as can seen below:</p>
<pre><code>go mod init github.com/steelthedev/go-chat
</code></pre><h3 id="heading-how-to-install-dependencies">How to Install Dependencies</h3>
<p>You need to install some libraries which are very vital. This can be done in the terminal by running the following commands:</p>
<pre><code>go get -u github.com/gofiber/fiber/v2
go get -u github.com/gofiber/websocket/v2
go get -u github.com/gofiber/template/html/v2
</code></pre><p>These will install Fiber and other components such as WebSocket and HTML templating library.</p>
<h3 id="heading-maingo-file">main.go File</h3>
<p>In the root directory, create a <strong>main.go</strong> file. This file will be the entry point to the application. Inside the file we are going to create a simple web server:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>


<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {

    <span class="hljs-comment">// Start new fiber instance</span>
    app := fiber.New()

    <span class="hljs-comment">// Create a "ping" handler to test the server</span>
    app.Get(<span class="hljs-string">"/ping"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(ctx *fiber.Ctx)</span> <span class="hljs-title">error</span></span>{
        <span class="hljs-keyword">return</span> ctx.SendString(<span class="hljs-string">"Welcome to fiber"</span>)
    })

    <span class="hljs-comment">// Start the http server</span>
    app.Listen(<span class="hljs-string">":3000"</span>)
}
</code></pre>
<p>Save the file and run <code>go run main.go</code> in the terminal to start the web server. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/tuts1-cropped.png" alt="Image" width="600" height="400" loading="lazy">
<em><code>go run</code> command starting the web server</em></p>
<p>If you head over to the browser and test the <code>/ping</code> route, there should be a response like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/welcome-to-fiber-cropped.png" alt="Image" width="600" height="400" loading="lazy">
<em>/ping route in the browser</em></p>
<h2 id="heading-static-files">Static Files</h2>
<p>We'll need static files such as CSS and HTML files for the application to function. Create two folders with named <strong>static</strong> and <strong>views</strong>. Inside the <strong>views</strong> folder, create two html files: <strong>index.html</strong> and <strong>messages.html</strong>.</p>
<p>Here's what the <strong>index.html</strong> file should look like:</p>
<pre><code class="lang-go">&lt;!DOCTYPE html&gt;
&lt;html lang=<span class="hljs-string">"en"</span>&gt;

&lt;head&gt;
    &lt;meta charset=<span class="hljs-string">"UTF-8"</span>&gt;
    &lt;meta name=<span class="hljs-string">"viewport"</span> content=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;
    &lt;title&gt;Chat Room&lt;/title&gt;
    &lt;script src=<span class="hljs-string">"https://unpkg.com/htmx.org@1.9.10"</span>
        integrity=<span class="hljs-string">"sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"</span>
        crossorigin=<span class="hljs-string">"anonymous"</span>&gt;&lt;/script&gt;
    &lt;!-- HTMX Websockets extension https:<span class="hljs-comment">//htmx.org/extensions/web-sockets/ --&gt;</span>
    &lt;script src=<span class="hljs-string">"https://unpkg.com/htmx.org/dist/ext/ws.js"</span>&gt;&lt;/script&gt;
    &lt;link href=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"</span> rel=<span class="hljs-string">"stylesheet"</span>
        integrity=<span class="hljs-string">"sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"</span> crossorigin=<span class="hljs-string">"anonymous"</span>&gt;
    &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"/static/style.css"</span>&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;div class=<span class="hljs-string">"container"</span>&gt;
        &lt;div class=<span class="hljs-string">"chat-window"</span>&gt;
            &lt;div class=<span class="hljs-string">"messages"</span> id=<span class="hljs-string">"messages"</span> &gt;
                &lt;!-- Messages will be appended here --&gt;
            &lt;/div&gt;
            &lt;form id=<span class="hljs-string">"form"</span>&gt;
                &lt;div class=<span class="hljs-string">"input-area"</span>&gt;
                    &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span> name=<span class="hljs-string">"text"</span> min=<span class="hljs-string">"1"</span> id=<span class="hljs-string">"messageInput"</span> placeholder=<span class="hljs-string">"Type a message..."</span>&gt;
                    &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>&gt;Send&lt;/button&gt;
                &lt;/div&gt;
            &lt;/form&gt;

        &lt;/div&gt;
    &lt;/div&gt;

&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<p>In the <strong>index.html</strong> above, we have linked the necessary plugins such as our <strong>style.css</strong> which will soon be created, HTMX and bootstrap 5.</p>
<p>Here's what the <strong>message.html</strong> file should look like:</p>
<pre><code>&lt;div id=<span class="hljs-string">"messages"</span> hx-swap-oob=<span class="hljs-string">"beforeend"</span>&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-small"</span>&gt;</span>{{ .Text }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
&lt;/div&gt;
</code></pre><p>This message will be the response from the server, it will be swapped into our <strong>index.html</strong> code automatically in the browser with the help of HTMX.</p>
<p>Now, create a new folder named <strong>static</strong>. Inside it, create a new file <strong>style.css</strong>:</p>
<pre><code>body {
    <span class="hljs-attr">margin</span>: <span class="hljs-number">0</span>;
    padding: <span class="hljs-number">0</span>;
    font-family: Arial, sans-serif;
    background-color: #f2f2f2;
}

.container {
    <span class="hljs-attr">display</span>: flex;
    justify-content: center;
    align-items: center;
    height: <span class="hljs-number">100</span>vh;
}

.chat-<span class="hljs-built_in">window</span> {
    <span class="hljs-attr">width</span>: <span class="hljs-number">400</span>px;
    background-color: #fff;
    border-radius: <span class="hljs-number">10</span>px;
    box-shadow: <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">10</span>px rgba(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
}

.messages {
    <span class="hljs-attr">padding</span>: <span class="hljs-number">10</span>px;
    overflow-y: scroll;
    height: <span class="hljs-number">300</span>px;
}

.message {
    margin-bottom: <span class="hljs-number">10</span>px;
}

.message p {
    background-color: #f0f0f0;
    border-radius: <span class="hljs-number">5</span>px;
    padding: <span class="hljs-number">5</span>px <span class="hljs-number">10</span>px;
    display: inline-block;
    max-width: <span class="hljs-number">80</span>%;
}

.input-area {
    <span class="hljs-attr">padding</span>: <span class="hljs-number">10</span>px;
    display: flex;
}

.input-area input[type=<span class="hljs-string">"text"</span>] {
    <span class="hljs-attr">flex</span>: <span class="hljs-number">1</span>;
    padding: <span class="hljs-number">8</span>px;
    border: <span class="hljs-number">1</span>px solid #ccc;
    border-radius: <span class="hljs-number">5</span>px;
    margin-right: <span class="hljs-number">5</span>px;
}

.input-area button {
    <span class="hljs-attr">padding</span>: <span class="hljs-number">8</span>px <span class="hljs-number">15</span>px;
    background-color: #<span class="hljs-number">4</span>CAF50;
    color: white;
    border: none;
    border-radius: <span class="hljs-number">5</span>px;
    cursor: pointer;
    transition: background-color <span class="hljs-number">0.3</span>s;
}

.input-area button:hover {
    background-color: #<span class="hljs-number">45</span>a049;
}

.input-area button:active {
    background-color: #<span class="hljs-number">3e8</span>e41;
}
</code></pre><h2 id="heading-how-to-configure-static-files">How to Configure Static Files</h2>
<p>In your <strong>main.g</strong>o file, you need to tell Fiber how to handle your static files, most especially the folder to check for HTML rendering. Update <strong>main.go</strong> as follow:</p>
<pre><code>package main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>
    <span class="hljs-string">"github.com/gofiber/template/html/v2"</span>
)

func main() {


    <span class="hljs-comment">// Create views engine</span>
    <span class="hljs-attr">viewsEngine</span> := html.New(<span class="hljs-string">"./views"</span>, <span class="hljs-string">".html"</span>)


    <span class="hljs-comment">// Start new fiber instance</span>
    <span class="hljs-attr">app</span> := fiber.New(fiber.Config{
        <span class="hljs-attr">Views</span>: viewsEngine,
    })

    <span class="hljs-comment">// Static route and directory</span>
    app.Static(<span class="hljs-string">"/static/"</span>, <span class="hljs-string">"./static"</span>)

    <span class="hljs-comment">// Create a "ping" handler to test the server</span>
    app.Get(<span class="hljs-string">"/ping"</span>, func(ctx *fiber.Ctx) error{
        <span class="hljs-keyword">return</span> ctx.SendString(<span class="hljs-string">"Welcome to fiber"</span>)
    })

    <span class="hljs-comment">// Start the http server</span>
    app.Listen(<span class="hljs-string">":3000"</span>)

}
</code></pre><p>As seen above, a configuration was added to the app instance and also configured the static route to be <code>/static/</code>.</p>
<h2 id="heading-how-to-create-handlers">How to Create Handlers</h2>
<p>Create a new file named <strong>handlers.go</strong>:</p>
<pre><code>package handlers

<span class="hljs-keyword">import</span> <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>

type AppHandler struct{}

func NewAppHandler() *AppHandler {
    <span class="hljs-keyword">return</span> &amp;AppHandler{}
}

func (a *AppHandler) HandleGetIndex(ctx *fiber.Ctx) error {
    <span class="hljs-attr">context</span> := fiber.Map{}
    <span class="hljs-keyword">return</span> ctx.Render(<span class="hljs-string">"index"</span>, context)
}
</code></pre><p>In the code above, we created a handler which received the <code>AppHandler</code> struct. This helps with abstractions in case the code gets bigger. The <code>HandleGetIndex</code> function takes in a pointer to the Fiber context and renders the <strong>index.html</strong> file.</p>
<p>In <strong>main.go</strong>:</p>
<pre><code>package main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>
    <span class="hljs-string">"github.com/gofiber/template/html/v2"</span>
    <span class="hljs-string">"github.com/steelthedev/go-chat/handlers"</span>
)

func main() {

    <span class="hljs-comment">// Start new fiber instance</span>
    <span class="hljs-attr">app</span> := fiber.New()

    <span class="hljs-comment">// Create a "ping" handler to test the server</span>
    app.Get(<span class="hljs-string">"/ping"</span>, func(ctx *fiber.Ctx) error{
        <span class="hljs-keyword">return</span> ctx.SendString(<span class="hljs-string">"Welcome to fiber"</span>)
    })

    <span class="hljs-comment">// create new App Handler</span>
    <span class="hljs-attr">appHandler</span> := NewAppHandler()

    <span class="hljs-comment">// Add appHandler routes</span>
    app.Get(<span class="hljs-string">"/, appHandler.HandleGetIndex)

    // Start the http server
    app.Listen("</span>:<span class="hljs-number">3000</span><span class="hljs-string">")
}</span>
</code></pre><p>Above, we created a new app handler and added the <code>HandleGetIndex</code> function in the routes. Run the <code>go run main.go</code> command. On <code>localhost:3000</code>, you should have a screen similar to this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/chat-room-cropped.png" alt="Image" width="600" height="400" loading="lazy">
<em>input box, send button, and chat display area on localhost:3000</em></p>
<h3 id="heading-messagesgo-file">messages.go File</h3>
<p>Create a new file in the project directly and name it <strong>message.go</strong>. This file will host the message struct.</p>
<pre><code>package main

type Message struct {
    Text       string <span class="hljs-string">`json:"text"`</span>
}
</code></pre><h3 id="heading-websocketgo-file">websocket.go File</h3>
<p>Create a new file in the project directory and name it <strong>websocket.go</strong>. This will house the main function creating the WebSocket server, reading through it and writing to all channels:</p>
<pre><code>package main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"bytes"</span>
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"html/template"</span>
    <span class="hljs-string">"log"</span>

    <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>
    <span class="hljs-string">"github.com/gofiber/websocket/v2"</span>
)

type WebSocketServer struct {
    clients   map[*websocket.Conn]bool
    broadcast chan *Message
}

func NewWebSocket() *WebSocketServer {
    <span class="hljs-keyword">return</span> &amp;WebSocketServer{
        <span class="hljs-attr">clients</span>:   make(map[*websocket.Conn]bool),
        <span class="hljs-attr">broadcast</span>: make(chan *Message),
    }
}


func (s *WebSocketServer) HandleWebSocket(ctx *websocket.Conn) {

    <span class="hljs-comment">// Register a new Client</span>
    s.clients[ctx] = <span class="hljs-literal">true</span>
    defer func() {
        <span class="hljs-keyword">delete</span>(s.clients, ctx)
        ctx.Close()
    }()

    <span class="hljs-keyword">for</span> {
        _, msg, <span class="hljs-attr">err</span> := ctx.ReadMessage()
        <span class="hljs-keyword">if</span> err != nil {
            log.Println(<span class="hljs-string">"Read Error:"</span>, err)
            <span class="hljs-keyword">break</span>
        }

        <span class="hljs-comment">// send the message to the broadcast channel</span>
        <span class="hljs-keyword">var</span> message Message
        <span class="hljs-keyword">if</span> err := json.Unmarshal(msg, &amp;message); err != nil {
            log.Fatalf(<span class="hljs-string">"Error Unmarshalling"</span>)
        }
        s.broadcast &lt;- &amp;message
    }
}

func (s *WebSocketServer) HandleMessages() {
    <span class="hljs-keyword">for</span> {
        <span class="hljs-attr">msg</span> := <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">-s.broadcast</span>

        // <span class="hljs-attr">Send</span> <span class="hljs-attr">the</span> <span class="hljs-attr">message</span> <span class="hljs-attr">to</span> <span class="hljs-attr">all</span> <span class="hljs-attr">Clients</span>

        <span class="hljs-attr">for</span> <span class="hljs-attr">client</span> <span class="hljs-attr">:</span>= <span class="hljs-string">range</span> <span class="hljs-attr">s.clients</span> {
            <span class="hljs-attr">err</span> <span class="hljs-attr">:</span>= <span class="hljs-string">client.WriteMessage(websocket.TextMessage,</span> <span class="hljs-attr">getMessageTemplate</span>(<span class="hljs-attr">msg</span>))
            <span class="hljs-attr">if</span> <span class="hljs-attr">err</span> != <span class="hljs-string">nil</span> {
                <span class="hljs-attr">log.Printf</span>("<span class="hljs-attr">Write</span>  <span class="hljs-attr">Error:</span> %<span class="hljs-attr">v</span> ", <span class="hljs-attr">err</span>)
                <span class="hljs-attr">client.Close</span>()
                <span class="hljs-attr">delete</span>(<span class="hljs-attr">s.clients</span>, <span class="hljs-attr">client</span>)
            }

        }

    }
}

<span class="hljs-attr">func</span> <span class="hljs-attr">getMessageTemplate</span>(<span class="hljs-attr">msg</span> *<span class="hljs-attr">Message</span>) []<span class="hljs-attr">byte</span> {
    <span class="hljs-attr">tmpl</span>, <span class="hljs-attr">err</span> <span class="hljs-attr">:</span>= <span class="hljs-string">template.ParseFiles(</span>"<span class="hljs-attr">views</span>/<span class="hljs-attr">message.html</span>")
    <span class="hljs-attr">if</span> <span class="hljs-attr">err</span> != <span class="hljs-string">nil</span> {
        <span class="hljs-attr">log.Fatalf</span>("<span class="hljs-attr">template</span> <span class="hljs-attr">parsing:</span> %<span class="hljs-attr">s</span>", <span class="hljs-attr">err</span>)
    }

    // <span class="hljs-attr">Render</span> <span class="hljs-attr">the</span> <span class="hljs-attr">template</span> <span class="hljs-attr">with</span> <span class="hljs-attr">the</span> <span class="hljs-attr">message</span> <span class="hljs-attr">as</span> <span class="hljs-attr">data.</span>
    <span class="hljs-attr">var</span> <span class="hljs-attr">renderedMessage</span> <span class="hljs-attr">bytes.Buffer</span>
    <span class="hljs-attr">err</span> = <span class="hljs-string">tmpl.Execute(&amp;renderedMessage,</span> <span class="hljs-attr">msg</span>)
    <span class="hljs-attr">if</span> <span class="hljs-attr">err</span> != <span class="hljs-string">nil</span> {
        <span class="hljs-attr">log.Fatalf</span>("<span class="hljs-attr">template</span> <span class="hljs-attr">execution:</span> %<span class="hljs-attr">s</span>", <span class="hljs-attr">err</span>)
    }

    <span class="hljs-attr">return</span> <span class="hljs-attr">renderedMessage.Bytes</span>()
}</span></span>
</code></pre><p>The <code>HandleWebSocket</code> function adds the client, processes the messages into JSON and then adds the message into a channel for distribution to all clients by <code>HandleMessage</code>. </p>
<p>It also keeps the connection alive. <code>getMessageTemplate</code> basically process the message into the <strong>message.html</strong>, and then converts it to a byte. This byte can then be sent to the client as a response. </p>
<h3 id="heading-how-to-add-a-websocket-to-routes-and-htmx">How to Add a WebSocket to Routes and HTMX</h3>
<p>We need to add the WebSocket to our routes <strong>main.go</strong>:</p>
<pre><code>package main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>
    <span class="hljs-string">"github.com/gofiber/template/html/v2"</span>
    <span class="hljs-string">"github.com/gofiber/websocket/v2"</span>
    <span class="hljs-string">"github.com/steelthedev/go-chat/handlers"</span>
)

func main() {

    <span class="hljs-comment">// Start new fiber instance</span>
    <span class="hljs-attr">app</span> := fiber.New()

    <span class="hljs-comment">// Create a "ping" handler to test the server</span>
    app.Get(<span class="hljs-string">"/ping"</span>, func(ctx *fiber.Ctx) error{
        <span class="hljs-keyword">return</span> ctx.SendString(<span class="hljs-string">"Welcome to fiber"</span>)
    })

    <span class="hljs-comment">// create new App Handler</span>
    <span class="hljs-attr">appHandler</span> := NewAppHandler()

    <span class="hljs-comment">// Add appHandler routes</span>
    app.Get(<span class="hljs-string">"/, appHandler.HandleGetIndex)

    // create new webscoket
    server := NewWebSocket()
    app.Get("</span>/ws<span class="hljs-string">", websocket.New(func(ctx *websocket.Conn) {
        server.HandleWebSocket(ctx)
    }))

    go server.HandleMessages()


    // Start the http server
    app.Listen("</span>:<span class="hljs-number">3000</span><span class="hljs-string">")
}</span>
</code></pre><p>The WebSocket and its route has been added. The final step is to add the HTMX tags on the <strong>index.html</strong> file:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;

&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Chat Room&lt;/title&gt;
    &lt;script src="https://unpkg.com/htmx.org@1.9.10"
        integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
        crossorigin="anonymous"&gt;&lt;/script&gt;
    &lt;!-- HTMX Websockets extension https://htmx.org/extensions/web-sockets/ --&gt;
    &lt;script src="https://unpkg.com/htmx.org/dist/ext/ws.js"&gt;&lt;/script&gt;
    &lt;link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"&gt;
    &lt;link rel="stylesheet" href="/static/style.css"&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;div class="container"&gt;
        &lt;div class="chat-window" hx-ext="ws" ws-connect="/ws"&gt;
            &lt;div class="messages" id="messages" hx-swap="beforeend" hx-swap-oob="beforeend"&gt;
                &lt;!-- Messages will be appended here --&gt;
            &lt;/div&gt;
            &lt;form id="form" ws-send hx "&gt;
                &lt;div class="input-area"&gt;
                    &lt;input type="text" name="text" min="1" id="messageInput" placeholder="Type a message..."&gt;
                    &lt;button type="submit"&gt;Send&lt;/button&gt;
                &lt;/div&gt;
            &lt;/form&gt;

        &lt;/div&gt;
    &lt;/div&gt;

&lt;/body&gt;

&lt;/html&gt;
</code></pre><p>The <code>hx-ext</code> tag and <code>ws-connect</code> tag point to the WebSocket URL <code>/ws</code>. The <code>hx-swap</code> tag was used to perform DOM manipulations which adds our messages into the <code>#messages</code> div.</p>
<p>After saving this, run <code>go run main.go</code>. You can open two different browser windows at <code>localhost:3000</code></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/Screenshot-from-2024-06-02-04-23-06.png" alt="Image" width="600" height="400" loading="lazy">
<em>two browser windows used for sending and receiving messages</em></p>
<p>If the WebSocket is running perfectly, you should be able to send and receive messages from the two browsers in real-time as displayed in the picture.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In this tutorial, we have showcased how to create a simple WebSocket server using Go, Fiber and HTMX. </p>
<p>You can go on and improve this project by adding extra features such as ClientID, authentication and user management. You can visit the project repo here: <a target="_blank" href="https://github.com/steelthedev/go-chat">github.com/steelthedev/go-chat</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Real-time Chat App with ReactJS and Firebase ]]>
                </title>
                <description>
                    <![CDATA[ By Timonwa Akintokun In this article, I'm going to show you how to build a real-time chat app using React.js and Firebase.  In the app, we will allow the user to log in with their Google account using Firebase's Google sign-in Authentication. We will... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/building-a-real-time-chat-app-with-reactjs-and-firebase/</link>
                <guid isPermaLink="false">66d4614a36c45a88f96b7d03</guid>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 13 Jan 2023 22:26:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/Cover-Images-freeCodeCamp.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Timonwa Akintokun</p>
<p>In this article, I'm going to show you how to build a real-time chat app using React.js and Firebase. </p>
<p>In the app, we will allow the user to log in with their Google account using Firebase's Google sign-in Authentication. We will also store and retrieve all the chatroom messages using Firebase's Cloud Firestore.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You should have Node.js installed on your system. You should also have intermediate knowledge of CSS, JavaScript, and Reactjs. Finally, you should know how to use the command terminal. You do not need to know how to use Firebase.</p>
<h2 id="heading-what-is-firebase">What is Firebase?</h2>
<p>Firebase is a Backend-as-a-Service (Baas). It is a Google-backed app development platform that allows developers to build iOS, Android, and web apps. </p>
<p>It provides tools for tracking analytics, reporting and fixing app crashes, and creating marketing and product experiments. These help developers develop quality apps, grow their user base, and make a profit. </p>
<p>We will be using two of their tools: Firebase Authentication and Cloud Firestore.</p>
<h3 id="heading-firebase-authentication"><strong>Firebase Authentication</strong></h3>
<p>Firebase Authentication (SDK) is a Firebase tool that supports different authentication methods like passwords, phone numbers, Google, Facebook, Twitter, Github, and more. In this app, we will be using the Google sign-in Authentication.</p>
<h3 id="heading-cloud-firestore"><strong>Cloud Firestore</strong></h3>
<p>Cloud Firestore is a cloud-based NoSQL database server for storing and syncing data. It stores data in documents as key-value pairs, and the documents are organized into collections. </p>
<p>The documents can also have sub-collections, allowing you to nest collections within collections. The data is also synchronized automatically among all devices listening for them.</p>
<p>Now that you have an idea of how Firebase and Cloud Firestore work, let's build our app.</p>
<p><strong>Note:</strong> For this article's scope, I have already prewritten the CSS and prebuilt the components for the chat app. You can find the final project code on <a target="_blank" href="https://github.com/Timonwa/react-chat">GitHub</a> and the CSS and components in the <a target="_blank" href="https://github.com/Timonwa/react-chat/tree/main/setup">setup folder</a>. You can also view the final project with this <a target="_blank" href="https://react-chat-timonwa.vercel.app/">live link</a>.</p>
<h2 id="heading-how-to-create-our-react-app">How to Create Our React App</h2>
<p>Clone this <a target="_blank" href="https://github.com/Timonwa/react-chat">GitHub repo</a>, delete the src folder in the root directory, and replace it with the src folder located in the <a target="_blank" href="https://github.com/Timonwa/react-chat/tree/main/setup">setup folder</a>. </p>
<p>Alternatively, you can create your React app by running the code in your terminal,  <code>npx create-react-app react-chat</code>, to create it. Here, <code>react-chat</code> is the name of the app. Then you can run <code>npm install firebase react-firebase-hooks</code> to install <strong>firebase</strong> and <strong>react-firebase-hooks</strong>. </p>
<p>Delete the current <strong>src</strong> folder and replace it with the one from the <a target="_blank" href="https://github.com/Timonwa/react-chat/tree/main/setup">setup folder</a> to use the prewritten CSS and prebuilt components. (Optionally, you can write yours yourself.)</p>
<p>Your new <strong>src</strong> folder now contains the following: </p>
<ul>
<li>a components folder with a <strong>NavBar</strong> component having the <strong>Google sign-in</strong> and <strong>Sign Out</strong> button, </li>
<li>a <strong>Welcome</strong> component that will be visible to the user not signed in, </li>
<li>a <strong>Chatbox</strong> component that will be visible only when the user is signed in, </li>
<li>the <strong>Message</strong> component for displaying a user’s message, and </li>
<li>a <strong>SendMessage</strong> component so the user can input and send in their messages.</li>
</ul>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667917866994_image2.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667917866994_image2.png" width="241" height="293" loading="lazy"></p>
<p>It also has:</p>
<ul>
<li>an <strong>img</strong> folder where the Google Sign-in image for the Sign-in button is stored,</li>
<li>an <strong>App.css</strong> file with the prewritten CSS codes, </li>
<li>the new <strong>App.js</strong> file with all our components imported into it, </li>
<li>and the <strong>index.js</strong> file.</li>
</ul>
<p>Run <code>npm start</code> to view the app in the browser. Our app should look like this:</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667916861885_image1.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667916861885_image1.png" width="1367" height="650" loading="lazy">
<em>Our React app</em></p>
<p>Now let's create a Firebase account and set up our Firebase project.</p>
<h2 id="heading-how-to-set-up-the-firebase-project">How to Set Up the Firebase Project</h2>
<p>If you do not have a <a target="_blank" href="https://firebase.google.com/">Firebase</a> account already, you can open one using your Gmail (you can only use <a target="_blank" href="https://mail.google.com/mail/">google mail</a>). On the landing page, click on <strong>Get started</strong> and then, <strong>Add project</strong>.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667929969308_image3.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667929969308_image3.png" width="1367" height="650" loading="lazy"></p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667929996407_image4.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667929996407_image4.png" width="1367" height="650" loading="lazy">
<em>How to setup a Firebase project</em></p>
<p>Fill out the <strong>Create a project</strong> form by providing a project name. If you want Google Analytics enabled for your project, leave it enabled. Otherwise, disable it. After that, click on <strong>Create project</strong>.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667930368482_image5.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667930368482_image5.png" width="1367" height="650" loading="lazy">
<em>Create a project</em></p>
<p>Once created, click on <strong>Continue</strong>.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667930783189_image8.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667930783189_image8.png" width="945" height="588" loading="lazy">
<em>What you'll see while Firebase is creating your project.</em></p>
<p>Choose the type of app where you want to add Firebase. For this article, we chose the code icon because we are building a web app.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667931932857_image9.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667931932857_image9.png" width="1367" height="650" loading="lazy">
<em>Choose the type of app to which you want to add Firestore</em></p>
<p>Enter a nickname for your app and click on <strong>Register app</strong>.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667934485512_image10.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667934485512_image10.png" width="1367" height="650" loading="lazy">
<em>Register your app</em></p>
<p>Then select <code>npm</code><strong>,</strong> copy the code snippet below it (we will be using it later), and click <strong>Continue to console</strong>.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667934988668_image11.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667934988668_image11.png" width="1367" height="650" loading="lazy">
<em>Code snippet</em></p>
<h2 id="heading-how-to-set-up-firebase-authentication">How to Set Up Firebase Authentication</h2>
<p>To set up Firebase Authentication, go to the menu on the left side of the screen, click on <strong>Build</strong>, and select <strong>Authentication</strong> from the dropdown.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667937032772_image12.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667937032772_image12.png" width="1367" height="650" loading="lazy">
<em>Select authentication from dropdown</em></p>
<p>Click on <strong>Get started</strong> and select <strong>Google</strong> in the <strong>Sign-in method's</strong> tab.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667937105063_image13.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667937105063_image13.png" width="1367" height="650" loading="lazy">
<em>Choose your authentication sign in method</em></p>
<p>Enable <strong>Google</strong>, choose your <strong>Project support email</strong>, and click <strong>Save</strong>.</p>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667937310260_image14.png" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667937310260_image14.png" width="1367" height="851" loading="lazy"></p>
<h2 id="heading-how-to-set-up-cloud-firestore">How to Set Up Cloud Firestore</h2>
<p>Again, go to the menu on the left side of the screen. Click on <strong>Build</strong> and select <strong>Firestore Database</strong> from the dropdown. Then click on <strong>Create database</strong> and fill out the form.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image9-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Setting up Cloud Firestore</em></p>
<p>Select which mode you want your database to run, production or test. </p>
<p>Test mode means that any client will have read/write access to your database for 30 days. Production mode means that no one will have read/write access to your database. You will have to edit your rules to grant access to specific clients.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image10-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chose test mode or production mode (we choose production mode).</em></p>
<p>Select <strong>production mode</strong> and click <strong>Next</strong>.</p>
<p>Select a location where you want your Firestore to be stored and click <strong>Enable</strong>. It will default to the location closest to you.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image11-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Set a location (default is the location closest to you)</em></p>
<p>Next up, we edit our rules. Click on the <strong>Rules</strong> tab and edit the rules,</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image12-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Edit the rules through the Rules tab</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image13-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Rule to be replaced</em></p>
<p>Replace the current rule with the code below and click on <strong>Publish</strong>.</p>
<pre><code class="lang-js">rules_version = <span class="hljs-string">'2'</span>;
service cloud.firestore {
  match /databases/{database}/documents {
    match /{<span class="hljs-built_in">document</span>=**} {
      allow read: <span class="hljs-keyword">if</span> <span class="hljs-literal">true</span>;
      allow create, update, <span class="hljs-keyword">delete</span>, <span class="hljs-attr">write</span>: <span class="hljs-keyword">if</span> request.auth != <span class="hljs-literal">null</span>;
    }
  }
}
</code></pre>
<p>The  <code>allow read: if true;</code> means that anyone can read your database. The <code>allow create, update, delete, write: if request.auth != null;</code> means only authenticated clients can create, update, delete, and write to your database.</p>
<p>You can either start to add/create a collection in your database or create one automatically in your React app, which we will do later. If you want to create a collection in Cloud Firestore, go back to the data tab by clicking <strong>Data</strong> beside <strong>Rules</strong> and then click on <strong>Add Collection.</strong></p>
<p>Enter the collection’s name, for example “messages”, and click <strong>Next</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image14-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Start a collection in Firestore</em></p>
<p>Now create a <strong>document</strong> for the Collection.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/image15-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a document for the collection</em></p>
<p>You can either click <strong>Auto_ID</strong> to generate an id for the doc or enter one yourself. </p>
<p>After that, create the key-value pairs of the Document. The <strong>Field</strong> input represents the key name, the <strong>Type</strong> defines what type of data it is (string, number, timesStamp, and so on), and the <strong>Value</strong> is the value of the key. </p>
<p>You can also add more documents by clicking the plus sign, otherwise, click <strong>Save</strong> to save your collection.</p>
<p>Our Firebase project is set. Let's go back to our React app.</p>
<h2 id="heading-how-to-configure-firebase-in-react">How to Configure Firebase in React</h2>
<p>In our <strong>src</strong> folder, create a file called <code>firebase.js</code> and paste the code we had copied into it.</p>
<p>Let's also import the <code>getAuth</code> and <code>getFirestore</code> services from Firebase's <strong>auth</strong> and <strong>firestore</strong> libraries, respectively, and export them. You can learn more about Firebase's available libraries from their <a target="_blank" href="https://firebase.google.com/docs/web/setup#available-libraries">documentation</a>.</p>
<p>Our Firebase config should look like this:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Import the functions you need from the SDKs you need</span>
<span class="hljs-keyword">import</span> { initializeApp } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/app"</span>;
<span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Add SDKs for Firebase products that you want to use</span>
<span class="hljs-comment">// https://firebase.google.com/docs/web/setup#available-libraries</span>
<span class="hljs-keyword">import</span> { getAuth } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
<span class="hljs-keyword">import</span> { getFirestore } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;

<span class="hljs-comment">// Your web app's Firebase configuration</span>
<span class="hljs-comment">// For Firebase JS SDK v7.20.0 and later, measurementId is optional</span>
<span class="hljs-keyword">const</span> firebaseConfig = {
  <span class="hljs-attr">apiKey</span>: REACT_APP_API_KEY_GOES_HERE,
  <span class="hljs-attr">authDomain</span>: REACT_APP_AUTH_DOMAIN_GOES_HERE,
  <span class="hljs-attr">projectId</span>: REACT_APP_PROJECT_ID_GOES_HERE,
  <span class="hljs-attr">storageBucket</span>: REACT_APP_STORAGE_BUCKET_GOES_HERE,
  <span class="hljs-attr">messagingSenderId</span>: REACT_APP_MESSSAGING_SENDER_ID_GOES_HERE,
  <span class="hljs-attr">appId</span>: REACT_APP_APP_ID_GOES_HERE,
};

<span class="hljs-comment">// Initialize Firebase</span>
<span class="hljs-keyword">const</span> app = initializeApp(firebaseConfig);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> auth = getAuth(app);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> db = getFirestore(app);
</code></pre>
<h2 id="heading-how-to-implement-firebase-into-our-react-app">How to Implement Firebase into our React App</h2>
<h3 id="heading-how-to-authenticate-users-with-their-google-account"><strong>How to Authenticate Users with Their Google Account</strong></h3>
<p>We want our users to have access to the chatroom and send messages if logged in. They should see the welcome page if they are not. They should also see the Sign Out button if logged in and the Google Sign-in button if not. </p>
<p>This authentication will be handed within our NavBar component, which holds our sign-in and sign-out buttons.</p>
<p>In our <code>NavBar</code> component, we currently import our Google sign-in image and store it as a const called <code>GoogleSignin</code>. We also have a state called <code>user</code> set to false, a <code>googleSignIn</code> function which sets the user state to <code>true</code>, and a <code>signOut</code> function which sets the user state to <code>false</code>.</p>
<p>We also have a <code>nav</code> element with an <code>h1</code> tag representing our app's title and two buttons rendered conditionally based on the user's state.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> GoogleSignin <span class="hljs-keyword">from</span> <span class="hljs-string">"../img/btn_google_signin_dark_pressed_web.png"</span>;

<span class="hljs-keyword">const</span> NavBar = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> googleSignIn = <span class="hljs-function">() =&gt;</span> {
    setUser(<span class="hljs-literal">true</span>);
  };
  <span class="hljs-keyword">const</span> signOut = <span class="hljs-function">() =&gt;</span> {
    setUser(<span class="hljs-literal">false</span>);
  };
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"nav-bar"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>React Chat<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      {user ? (
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{signOut}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"sign-out"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>&gt;</span>
          Sign Out
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      ) : (
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"sign-in"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{googleSignIn}</span>
            <span class="hljs-attr">src</span>=<span class="hljs-string">{GoogleSignin}</span>
            <span class="hljs-attr">alt</span>=<span class="hljs-string">"sign in with google"</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
          /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
  );
};
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> NavBar;
</code></pre>
<p>Let's make changes to the <strong>NavBar</strong> component. Import the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase"</span>;
<span class="hljs-keyword">import</span> { useAuthState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-firebase-hooks/auth"</span>;
<span class="hljs-keyword">import</span> { GoogleAuthProvider, signInWithRedirect } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
</code></pre>
<p>Replace the user state with the code below:</p>
<p><code>const [user] = useAuthState(auth);</code></p>
<p>And edit the googleSignIn and signOut functions:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> googleSignIn = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> provider = <span class="hljs-keyword">new</span> GoogleAuthProvider();
  signInWithRedirect(auth, provider);
};
<span class="hljs-keyword">const</span> signOut = <span class="hljs-function">() =&gt;</span> {
  auth.signOut();
};
</code></pre>
<p>The <code>useAuthState</code> function gets triggered when the user signs in or out, allowing us access to the user’s details. Currently, the user state is <code>null</code>. Once they are logged in, the user state will change to the data provided by the authentication method – in this case, Google.</p>
<p>In the <code>googleSignIn</code> function, we let Firebase know that the user wants to sign in with Google using the <code>GoogleAuthProvider()</code>. It also redirects them to Google’s sign-in page. </p>
<p>After the user successfully signs in, their data is saved in <code>auth</code>, and the user is redirected to our app. The <code>signOut</code> function clears the auth data, returning it to <code>null</code>. The new user state also determines which authentication buttons are rendered to the user.</p>
<p>Let's also add authentication to our <strong>App.js</strong> file. Import the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"./firebase"</span>;
<span class="hljs-keyword">import</span> { useAuthState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-firebase-hooks/auth"</span>;
</code></pre>
<p>Add the new user state so we can use it to render the <strong>Welcome</strong> component if the user is not logged in or the <strong>Chatbox</strong> component if the user is logged in.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> [user] = useAuthState(auth);
</code></pre>
<p>The final code looks like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"./firebase"</span>;
<span class="hljs-keyword">import</span> { useAuthState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-firebase-hooks/auth"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./App.css"</span>;
<span class="hljs-keyword">import</span> NavBar <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/NavBar"</span>;
<span class="hljs-keyword">import</span> ChatBox <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/ChatBox"</span>;
<span class="hljs-keyword">import</span> Welcome <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Welcome"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [user] = useAuthState(auth);
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">NavBar</span> /&gt;</span>
      {!user ? <span class="hljs-tag">&lt;<span class="hljs-name">Welcome</span> /&gt;</span> : <span class="hljs-tag">&lt;<span class="hljs-name">ChatBox</span> /&gt;</span>}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Testing our new sign-in and sign-out functions, we see the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/ezgif-4-b6465d1647-1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>App demo</em></p>
<p>Now let's do the same for our <strong>Welcome</strong> Component, which currently has the following code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> GoogleSignin <span class="hljs-keyword">from</span> <span class="hljs-string">"../img/btn_google_signin_dark_pressed_web.png"</span>;

<span class="hljs-keyword">const</span> Welcome = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> googleSignIn = <span class="hljs-function">() =&gt;</span> {};

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"welcome"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Welcome to React Chat.<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/logo512.png"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"ReactJs logo"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{50}</span> <span class="hljs-attr">height</span>=<span class="hljs-string">{50}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Sign in with Google to chat with with your fellow React Developers.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"sign-in"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
          <span class="hljs-attr">onClick</span>=<span class="hljs-string">{googleSignIn}</span>
          <span class="hljs-attr">src</span>=<span class="hljs-string">{GoogleSignin}</span>
          <span class="hljs-attr">alt</span>=<span class="hljs-string">"sign in with google"</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
        /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
  );
};
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Welcome;
</code></pre>
<p>We import the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase"</span>;
<span class="hljs-keyword">import</span> { GoogleAuthProvider, signInWithRedirect } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
</code></pre>
<p>and also edit the googleSignIn function:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> googleSignIn = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> provider = <span class="hljs-keyword">new</span> GoogleAuthProvider();
    signInWithRedirect(auth, provider);
};
</code></pre>
<p>Now we can also log in from the second Sign in button:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/video2-1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Updated app demo</em></p>
<h3 id="heading-how-to-send-and-store-messages-in-firebase"><strong>How to Send and Store Messages in Firebase</strong></h3>
<p>Currently, we are displaying a dummy message from our <strong>Message</strong> Component, and the <strong>Send</strong> button also does not perform any action. When the user inputs a message and hit the <strong>Send</strong> button, we want the message to be seen immediately in the chatroom. </p>
<p>With our current <strong>SendMessage</strong> component having the code below, let's edit it:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> SendMessage = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"send-message"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"messageInput"</span> <span class="hljs-attr">hidden</span>&gt;</span>
        Enter Message
      <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
        <span class="hljs-attr">id</span>=<span class="hljs-string">"messageInput"</span>
        <span class="hljs-attr">name</span>=<span class="hljs-string">"messageInput"</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
        <span class="hljs-attr">className</span>=<span class="hljs-string">"form-input__input"</span>
        <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"type message..."</span>
      /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Send<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
};
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SendMessage;
</code></pre>
<p>First, we import <code>useState</code> from React, <code>auth</code> and <code>db</code> from our firebase configuration file, and <code>addDoc</code>, <code>collection</code> and <code>serverTimestamp</code> from the Firestore library.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { auth, db } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase"</span>;
<span class="hljs-keyword">import</span> { addDoc, collection, serverTimestamp } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;
</code></pre>
<p>We create a state called <code>message</code> which is initially set to an empty string and passed as a value to the <code>input</code> tag. An <code>onChange</code> function is also added to the input, which sets the <code>message</code> state to whatever the user types in.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> SendMessage = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [message, setMessage] = useState(<span class="hljs-string">""</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"send-message"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
        <span class="hljs-attr">...</span>
        <span class="hljs-attr">value</span>=<span class="hljs-string">{message}</span>
        <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setMessage(e.target.value)}
      /&gt;
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Send<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  );
};
</code></pre>
<p>We also create a function called <code>sendMessage</code>, and we add an <code>onSubmit</code> attribute to our form, which runs the <code>sendMessage</code> function when the user clicks on <code>Send</code>. Note that the button must have <code>type="submit"</code> for the <code>onSubmit</code> attribute to work.</p>
<pre><code class="lang-js"> <span class="hljs-keyword">const</span> sendMessage = <span class="hljs-keyword">async</span> (event) =&gt; {
    event.preventDefault();
    <span class="hljs-keyword">if</span> (message.trim() === <span class="hljs-string">""</span>) {
      alert(<span class="hljs-string">"Enter valid message"</span>);
      <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">const</span> { uid, displayName, photoURL } = auth.currentUser;
    <span class="hljs-keyword">await</span> addDoc(collection(db, <span class="hljs-string">"messages"</span>), {
      <span class="hljs-attr">text</span>: message,
      <span class="hljs-attr">name</span>: displayName,
      <span class="hljs-attr">avatar</span>: photoURL,
      <span class="hljs-attr">createdAt</span>: serverTimestamp(),
      uid,
    });
    setMessage(<span class="hljs-string">""</span>);
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{(event)</span> =&gt;</span> sendMessage(event)} className="send-message"&gt;
...</span>
</code></pre>
<p>The <code>sendMessage</code> function is an async function. It first checks if the user is trying to send an empty string or whitespace as a message and alerts the user. </p>
<p>If the message is not an empty string, it gets the user’s <strong>uid</strong>, <strong>displayName</strong>, and <strong>photoURL</strong> from the <code>auth</code> data provided when they log in. This corresponds to the user’s <strong>unique id</strong>, <strong>full name</strong>, and <strong>photo URL</strong>, respectively.</p>
<p>Once that is done,  it then uses the <code>addDoc()</code> to create a <strong>document</strong> inside a <strong>collection</strong> called <strong>messages</strong> in our <strong>database</strong>, which we have access to via the <code>db</code> import. If the <strong>collection</strong> doesn't exist, it will create it for us. </p>
<p>It also creates <strong>key-value pairs</strong>, storing our <strong>message</strong> in <strong>text</strong>, <strong>displayName</strong> in <strong>name</strong>, storing the <strong>time</strong> the message was saved in our database in <strong>createdAt</strong>, and then the user’s <strong>uid</strong>. </p>
<p>These key-value pairs are what make up the data for our document. After this is done, it then resets the message state to an empty string.</p>
<h3 id="heading-how-to-retrieve-messages-from-our-database"><strong>How to Retrieve Messages from Our Database</strong></h3>
<p>After sending the user's message, we need to display it on the screen to the user. Going into our <strong>ChatBox</strong> component, we import the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useEffect, useRef, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {
  query,
  collection,
  orderBy,
  onSnapshot,
  limit,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;
<span class="hljs-keyword">import</span> { db } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase"</span>;
</code></pre>
<p>We create a <code>useEffect</code> hook that will run anytime changes are made in the chatroom, like sending or deleting a message.</p>
<pre><code class="lang-js">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> q = query(
    collection(db, <span class="hljs-string">"messages"</span>),
    orderBy(<span class="hljs-string">"createdAt"</span>, <span class="hljs-string">"desc"</span>),
    limit(<span class="hljs-number">50</span>)
  );
  <span class="hljs-keyword">const</span> unsubscribe = onSnapshot(q, <span class="hljs-function">(<span class="hljs-params">QuerySnapshot</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> fetchedMessages = [];
    QuerySnapshot.forEach(<span class="hljs-function">(<span class="hljs-params">doc</span>) =&gt;</span> {
      fetchedMessages.push({ ...doc.data(), <span class="hljs-attr">id</span>: doc.id });
    });
    <span class="hljs-keyword">const</span> sortedMessages = fetchedMessages.sort(
      <span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> a.createdAt - b.createdAt
    );
    setMessages(sortedMessages);
  });
  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> unsubscribe;
}, []);
</code></pre>
<p>In this <code>useEffect</code> hook, we have a const <code>q</code>, a Firebase query that queries our database looking for a <strong>message</strong> collection. It then <strong>orders</strong> the documents in the collection based on the <strong>createdAt</strong> key, and returns a maximum of <strong>50</strong> documents (messages saved).</p>
<p>The <code>unsubscribe</code> const represents the <code>onSnapshot</code> function which listens to changes in the document. It has an empty array called <code>messages</code>. </p>
<p>The <code>forEach</code> loop loops through all the <strong>documents</strong> from the <strong>collection</strong> and saves the data in the new array. It then sets the initial messages array to the new messages array.</p>
<p>We also use a <strong>map function</strong> on our messages array to render each message/document data in our <strong>Message</strong> component.</p>
<pre><code class="lang-js">{messages?.map(<span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Message</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{message.id}</span> <span class="hljs-attr">message</span>=<span class="hljs-string">{message}</span> /&gt;</span></span>
))}
</code></pre>
<p>The final code looks like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React, { useEffect, useRef, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {
  query,
  collection,
  orderBy,
  onSnapshot,
  limit,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;
<span class="hljs-keyword">import</span> { db } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase"</span>;
<span class="hljs-keyword">import</span> Message <span class="hljs-keyword">from</span> <span class="hljs-string">"./Message"</span>;
<span class="hljs-keyword">import</span> SendMessage <span class="hljs-keyword">from</span> <span class="hljs-string">"./SendMessage"</span>;

<span class="hljs-keyword">const</span> ChatBox = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [messages, setMessages] = useState([]);
  <span class="hljs-keyword">const</span> scroll = useRef();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> q = query(
      collection(db, <span class="hljs-string">"messages"</span>),
      orderBy(<span class="hljs-string">"createdAt"</span>, <span class="hljs-string">"desc"</span>),
      limit(<span class="hljs-number">50</span>)
    );

    <span class="hljs-keyword">const</span> unsubscribe = onSnapshot(q, <span class="hljs-function">(<span class="hljs-params">QuerySnapshot</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> fetchedMessages = [];
      QuerySnapshot.forEach(<span class="hljs-function">(<span class="hljs-params">doc</span>) =&gt;</span> {
        fetchedMessages.push({ ...doc.data(), <span class="hljs-attr">id</span>: doc.id });
      });
      <span class="hljs-keyword">const</span> sortedMessages = fetchedMessages.sort(
        <span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> a.createdAt - b.createdAt
      );
      setMessages(sortedMessages);
    });
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> unsubscribe;
  }, []);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"chat-box"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"messages-wrapper"</span>&gt;</span>
        {messages?.map((message) =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">Message</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{message.id}</span> <span class="hljs-attr">message</span>=<span class="hljs-string">{message}</span> /&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {/* when a new message enters the chat, the screen scrolls down to the scroll div */}
      <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{scroll}</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">SendMessage</span> <span class="hljs-attr">scroll</span>=<span class="hljs-string">{scroll}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ChatBox;
</code></pre>
<p>Going into our <strong>Message</strong> component, let's render the data passed into it in our browser.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase"</span>;
<span class="hljs-keyword">import</span> { useAuthState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-firebase-hooks/auth"</span>;
<span class="hljs-keyword">const</span> Message = <span class="hljs-function">(<span class="hljs-params">{ message }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [user] = useAuthState(auth);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
      <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">chat-bubble</span> ${<span class="hljs-attr">message.uid</span> === <span class="hljs-string">user.uid</span> ? "<span class="hljs-attr">right</span>" <span class="hljs-attr">:</span> ""}`}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
        <span class="hljs-attr">className</span>=<span class="hljs-string">"chat-bubble__left"</span>
        <span class="hljs-attr">src</span>=<span class="hljs-string">{message.avatar}</span>
        <span class="hljs-attr">alt</span>=<span class="hljs-string">"user avatar"</span>
      /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"chat-bubble__right"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"user-name"</span>&gt;</span>{message.name}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"user-message"</span>&gt;</span>{message.text}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Message;
</code></pre>
<p>We imported <code>auth</code> and <code>useAuthState</code>, and stored the user details in <code>user</code>. We deconstructed the messages prop and passed the avatar into the img src attribute. We also replaced the dummy name and message with the one gotten from the message data.</p>
<p>We also conditioned a CSS style to take effect based on the uid of the message's author. So if the message’s author uid is the same as the uid of the person logged in, then the CSS styles stored in the selector <strong>right</strong> should be added to the div. Otherwise, no new style should be added.</p>
<p>Currently, all messages are positioned to the left, so if the logged user is the message’s author, their message should be positioned to the right. Let's view these changes in our browser:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/video3-1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Updated app demo</em></p>
<p>The message is sent and stored in our database. Then all the messages are retrieved, and the chatroom is updated in real-time with the new messages. </p>
<p>The name and avatar of the user are also present on the message card. But we can also see that the chat doesn't scroll to the bottom when a new message enters. Let's fix that.</p>
<h3 id="heading-how-to-add-scroll-to-the-bottom"><strong>How to Add Scroll to the Bottom</strong></h3>
<p>Going into our <strong>ChatBox.js</strong>, we import the <code>useRef</code> hook and create a const called <code>scroll</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React, { useEffect, useRef, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
...
const scroll = useRef();
</code></pre>
<p>We then create a <code>span</code> element with a <code>ref</code> attribute whose value is <strong>scroll</strong>, and also pass the <strong>scroll</strong> into our <strong>Messages</strong> component:</p>
<pre><code class="lang-js">&lt;main className=<span class="hljs-string">"chat-box"</span>&gt;
   ...
   {<span class="hljs-comment">/* when a new message enters the chat, the screen scrolls dowwn to the scroll div */</span>}
   &lt;span ref={scroll}&gt;&lt;/span&gt;
   <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">SendMessage</span> <span class="hljs-attr">scroll</span>=<span class="hljs-string">{scroll}</span> /&gt;</span></span>
&lt;/main&gt;
</code></pre>
<p>We then go into the <strong>Messages</strong> component, access the scroll const, and add <code>scroll.current.scrollIntoView({ behavior: "smooth" })</code> to the bottom of our <code>sendMessage</code> function. </p>
<p>This code tells the browser to let the scroll span be in view in the browser after sending a message. That is why the span tag was placed at the bottom of all the messages.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> SendMessage = <span class="hljs-function">(<span class="hljs-params">{ scroll }</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> sendMessage = <span class="hljs-keyword">async</span> (event) =&gt; {
   ...
    setMessage(<span class="hljs-string">""</span>);
    scroll.current.scrollIntoView({ <span class="hljs-attr">behavior</span>: <span class="hljs-string">"smooth"</span> });
  };
  ...
};
</code></pre>
<p>Going back to the browser, we should see the chat scroll to the bottom when the user sends in a new message.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/video4-1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Demo showing chat scrolling to the bottom with a new message</em></p>
<h2 id="heading-how-to-add-authorized-domains">How to Add Authorized Domains</h2>
<p>When deploying our React app, it's essential to add our domain to the list of authorized domains in Firebase. This step ensures that our app communicates with the Firebase services correctly. Here's how you can do it:</p>
<p>In the Firebase console, navigate to the <strong>Authentication</strong> section from the left sidebar and click on the <strong>Settings</strong> tab. Scroll down to the <strong>Authorized Domains</strong> section, then click on the <strong>Add Domain</strong> button. Add the domain or domains that your React app will be deployed on. </p>
<p>For example, if you are deploying your app to <a target="_blank" href="https://my-react-chat-app.com">https://my-react-chat-app.com</a>, enter my-react-chat-app.com as an authorized domain. Click the <strong>Add</strong> button to apply the changes.</p>
<p><img src="https://res.cloudinary.com/denkuysrh/image/upload/v1686503300/Article%20Images/freeCodeCamp/react-chat-app/FireShot_Capture_002_-_react-chat_-_Authentication_-_Firebase_console_-_console.firebase.google.com_yhkjop.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>By adding your app's domain to the authorized domains list, you grant permission for Firebase services to be accessed from that domain. Without adding the domain, you may encounter errors when trying to establish connections or perform operations with Firebase.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>And that's it on building a real-time chat app. Congrats!</p>
<p>In this tutorial, we learned how to use Firebase and React to build a real-time chat app. We also authenticated the users using Firebase Authentication’s Google sign-in method and stored the chat room messages in a database using Cloud Firestore. We also learned how to use some of Firebase’s services and libraries.</p>
<p>You can find the code for this project on <a target="_blank" href="https://github.com/Timonwa/react-chat">GitHub</a>, and you can explore the chat room using this <a target="_blank" href="https://react-chat-timonwa.vercel.app/">live link</a>.</p>
<p>If you enjoyed this article, consider sharing it to help other developers. You could also visit <a target="_blank" href="https://blog.timonwa.com/">my blog</a> to read more articles from me and you can connect with me on <a target="_blank" href="https://twitter.com/timonwa_">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/timonwa/">LinkedIn</a>. </p>
<p>Till next time guys, byeeeeee!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/HoDL1vbXj-1.gif" alt="Image" width="600" height="400" loading="lazy"></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Pair Programming with the ChatGPT AI – Does GPT-3.5 Understand Bash? ]]>
                </title>
                <description>
                    <![CDATA[ We've been hearing a lot about artificial intelligence and natural language processing – and in particular about the latest version of OpenAI's GPT – for weeks.  The recent release of GPT 3.5, and specifically the very new ChatGPT tool, is definitely... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/pair-programming-with-the-chatgpt-ai-how-well-does-gpt-3-5-understand-bash/</link>
                <guid isPermaLink="false">66b9961b17d9592471979c2a</guid>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Bash ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Clinton ]]>
                </dc:creator>
                <pubDate>Fri, 16 Dec 2022 17:51:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/12/pexels-pavel-danilyuk-8438951.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We've been hearing a lot about artificial intelligence and natural language processing – and in particular about the latest version of OpenAI's GPT – for weeks. </p>
<p>The recent release of GPT 3.5, and specifically the very new ChatGPT tool, is definitely a huge leap forward.</p>
<p>You may have read about people asking the AI to draw up real estate contracts and wills, and even generate programming code. This article approaches the tool from a slightly different perspective. </p>
<p>You can watch a video version of this article here if you'd like to use it to supplement your learning:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/GxJKuINwPSw" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-experimenting-with-chatgpt-and-bash-scripting">Experimenting with ChatGPT and Bash Scripting</h2>
<p>I decided to see how ChatGPT handles Linux Bash scripting. But (spoiler alert!) as I worked through the tool's suggestions - both good code and bad code - I began to realize that there was an even more powerful benefit staring me in the face: an opportunity to pair-program with an eminently helpful partner.</p>
<p>The AI ultimately failed to solve my problem, but the <em>way</em> it failed was absolutely fascinating. And watching the AI searching for new answers was absolutely crazy.</p>
<h3 id="heading-first-attempt-chatgpt-tries-to-solve-the-problem">First Attempt – ChatGPT Tries to Solve the Problem</h3>
<p>So let's see how it all happened. Here, more or less, is the initial prompt I provided to the AI:</p>
<blockquote>
<p>Write me a Bash script that prompts for the URL of a YouTube video and then uses the yt-dlp program to download from that URL. The script should then change the file name to "myvideo.mp4"</p>
</blockquote>
<p>After a few seconds, the ChatGPT told me that a script was coming. Here's how that code looked:</p>
<pre><code>#!<span class="hljs-regexp">/bin/</span>bash

# Prompt user <span class="hljs-keyword">for</span> YouTube video URL
read -p <span class="hljs-string">"Enter YouTube video URL: "</span> url

# Download video <span class="hljs-keyword">from</span> YouTube using yt-dlp
yt-dlp $url

# Get the name <span class="hljs-keyword">of</span> the downloaded video file
yt-dlp $url

# Change the downloaded video filename to <span class="hljs-string">"myvideo.mkv"</span>
mv video-filename.mp4 myvideo.mp4
</code></pre><p>The AI obviously knows what Bash scripts are and how to present code. It even did a great job commenting each line of code. The AI knows how to properly prompt the user for input and then how to import the input string into a variable called <code>url</code>. </p>
<p>It also knows about the yt-dlp program and how it works, appropriately feeding it the URL that was provided. Then it at least attempted to rename the downloaded video file to myvideo.mp4. But everything kind of broke down when it assumed that the download filename would be video-filename.mp4. Whoops.</p>
<p>At any rate, the AI helpfully showed me how to create the script from its code and then run it.</p>
<h3 id="heading-second-attempt-chatgpt-corrects-itself">Second Attempt – ChatGPT Corrects Itself</h3>
<p>My first follow-up prompt pointed out the obvious mistake and, after a few seconds, ChatGPT admitted it was wrong and offered an improvement. </p>
<blockquote>
<p>You're right, the script I provided would not work as expected because it assumes that the downloaded video will be saved to a file with the name <code>video-filename.mp4</code>. However, this is not guaranteed to be the case. To fix this...</p>
</blockquote>
<p>It's just mind-blowing how the AI is completely engaged in the process here. It remembers its first code, listens to and understands my complaint, and thinks through a solution. Here's that second attempt:</p>
<pre><code>#!<span class="hljs-regexp">/bin/</span>bash

# Prompt user <span class="hljs-keyword">for</span> YouTube video URL
read -p <span class="hljs-string">"Enter YouTube video URL: "</span> url

# Download video <span class="hljs-keyword">from</span> YouTube using yt-dlp
yt-dlp $url

# Get the name <span class="hljs-keyword">of</span> the downloaded video file
filename=$(basename $url)

# Change the downloaded video filename to <span class="hljs-string">"myvideo.mkv"</span>
mv $filename.mp4 myvideo.mp4
</code></pre><p>Ok, so I can see how the solution kind of looked right. <code>basename</code> is a Linux tool that can strip out file system directories and leave nothing but the actual filename.</p>
<p>The problem is that the variable <code>url</code> doesn't contain a local filename, but an internet address. This one definitely isn't going to work.</p>
<h3 id="heading-third-attempt-chatgpt-tries-again">Third Attempt – ChatGPT Tries Again</h3>
<p>So I tried again, explaining how that wasn't the right tool for the job. And ChatGPT gives it another try. </p>
<pre><code>#!<span class="hljs-regexp">/bin/</span>bash

# Prompt user <span class="hljs-keyword">for</span> YouTube video URL
read -p <span class="hljs-string">"Enter YouTube video URL: "</span> url

# Extract the filename <span class="hljs-keyword">from</span> the URL
filename=$(basename $url)

# Download the video using the yt-dlp tool and specify the filename
yt-dlp -o $filename $url

# Change the downloaded video filename to <span class="hljs-string">"myvideo.mkv"</span>
mv $filename.mp4 myvideo.mp4
</code></pre><p>This time, it shows that it had familiarized itself with the yt-dlp documentation, suggesting that we use the <code>-o</code> argument to give the downloading video a name, against which <code>basename</code> should now work. With each iteration, the AI gives me this updated code for my cutting and pasting pleasure.</p>
<p>Of course, that wasn't going to work either. With the next attempt, I get the feeling that the AI is a bit desperate. </p>
<h3 id="heading-if-at-first-you-dont-succeedat-least-youll-learn-something">If At First You Don't Succeed...At Least You'll Learn Something</h3>
<p>I'm going to skip past the next attempt or two, as we ran into a side problem: Videos coming from YouTube aren't always MP4s, they're sometimes MKVs or even WEBMs. That uncertainty could mess with the filenames we're working with, so I asked ChatGPT to work that into the code. </p>
<pre><code>#!<span class="hljs-regexp">/bin/</span>bash

# Prompt user <span class="hljs-keyword">for</span> YouTube video URL
read -p <span class="hljs-string">"Enter YouTube video URL: "</span> url

# Extract the filename <span class="hljs-keyword">from</span> the URL
filename=$(basename $url)

# Download the video using the yt-dlp tool and specify the filename
yt-dlp -F mp4 -O $filename $url

# Change the downloaded video filename to <span class="hljs-string">"myvideo.mkv"</span>
mv $filename.mp4 myvideo.mp4
</code></pre><p>When I reported how that, too, wouldn't work, the AI started to get confused. First it told me that the <code>-F</code> option was expecting a valid video format, but that "mp4" wasn't valid. Then it gives me valid options, including "mp4".</p>
<p>When nothing the AI suggested worked, it gave up, advising me to make sure that yt-dlp was actually installed on my machine and showing me how to do that. </p>
<p>I figured that was a good time to give up. It was a surprisingly good effort and, even if it didn't solve the problem, it did show me some useful syntax and got me thinking.</p>
<p>In case you're interested, by the way, here's how an actual working script would look. </p>
<pre><code>#!<span class="hljs-regexp">/bin/</span>bash

# Prompt user <span class="hljs-keyword">for</span> YouTube video URL
read -p <span class="hljs-string">"Enter YouTube video URL: "</span> url

# Download video <span class="hljs-keyword">from</span> YouTube using yt-dlp
yt-dlp $url

# Get the name <span class="hljs-keyword">of</span> the downloaded video file
original_filename=$(ls | awk <span class="hljs-string">'/mp4/ || /webm/ || /mkv/'</span>)

# Change the downloaded video filename to <span class="hljs-string">"myvideo.mkv"</span>
mv <span class="hljs-string">"$original_filename"</span> /home/ubuntu/vids/myvideo.mp4
</code></pre><p>The first two lines of code are just the way ChatGPT suggested. But I then isolate the filename by listing all the files in the current directory and using <code>awk</code> to filter for only filenames containing either mp4, webm, or mkv. (This assumes that there will never be more than one video file in the directory at a time.) </p>
<p>The filename will then be written to the <code>original_filename</code> variable. I'll then use that variable as part of a <code>move</code> command to rename the file <code>myvideo.mp4</code> As far as I can tell, that'll work no matter what format was actually sent.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Artificial intelligence might not be quite ready for real-world programming tasks today, but I wouldn't want to bet against it getting there tomorrow...or some time next year. </p>
<p>In the meantime though, I would absolutely recommend engaging with tools like ChatGPT whenever you're faced with a challenging programming task. </p>
<p>Two minds are (almost) always better than one.</p>
<p><em>Besides my <a target="_blank" href="https://www.youtube.com/@davidbclinton">my YouTube channel</a> (to which you can subscribe), <a target="_blank" href="https://bootstrap-it.com">my website</a> links to all kinds of technology goodness available as articles, books, and courses.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Discord Update Failed – How to Fix the Error on a Windows 10 PC ]]>
                </title>
                <description>
                    <![CDATA[ Discord is a popular chat app for gamers and anyone else who wants to create an online community. Despite its popularity, one major problem users report about Discord is the update failed loop. Discord has to update often because the Discord team imp... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/discord-update-failed-how-to-fix-the-error-on-a-windows-10-pc/</link>
                <guid isPermaLink="false">66adf0b3febac312b73075a6</guid>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ discord ]]>
                    </category>
                
                    <category>
                        <![CDATA[ update  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Windows ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Windows 10 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Thu, 18 Aug 2022 15:31:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/08/discord--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Discord is a popular chat app for gamers and anyone else who wants to create an online community. Despite its popularity, one major problem users report about Discord is the update failed loop.</p>
<p>Discord has to update often because the Discord team implements new features and bug fixes regularly. </p>
<p>In addition, the app itself must load new messages from chat rooms and DMs. Once the update fails, Discord gets stuck in an update failed loop.</p>
<p>In this guide, I will show you 4 ways you can fix the Discord update failed error on a Windows 10 computer.</p>
<h2 id="heading-what-well-cover">What We'll Cover</h2>
<ul>
<li><a class="post-section-overview" href="#heading-solution-1-double-check-your-internet-connection">Double-check your Internet Connection</a></li>
<li><a class="post-section-overview" href="#heading-solution-2-run-discord-as-an-administrator">Run Discord as an Administrator</a></li>
<li><a class="post-section-overview" href="#heading-solution-3-rename-discords-updateexe-file">Rename Discord’s Update.exe File</a></li>
<li><a class="post-section-overview" href="#heading-solution-4-temporarily-disable-your-antivirus-program-and-vpn">Temporarily Disable your Antivirus Program and VPN</a></li>
<li><a class="post-section-overview" href="#heading-solution-5-uninstall-and-reinstall-discord">Uninstall and Reinstall Discord</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-solution-1-double-check-your-internet-connection">Solution 1: Double-check your Internet Connection</h2>
<p>The first thing I would suggest you do is to check your internet connection.</p>
<p>That’s because Discord needs an internet connection to update, as the update has to be done over the internet. Once there’s no internet connection, the update won’t happen.</p>
<p>Make sure your Windows 10 PC is connected to the internet and the internet connection is strong enough.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss1-4.png" alt="ss1-4" width="600" height="400" loading="lazy">
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss2-4.png" alt="ss2-4" width="600" height="400" loading="lazy"> </p>
<h2 id="heading-solution-2-run-discord-as-an-administrator">Solution 2: Run Discord as an Administrator</h2>
<p>A common solution to this issue is to run the Discord app as an admin. </p>
<p>This could fix the issue because any app you want to install needs admin privileges. Discord is not an exception, so granting it admin privileges can make it dig deeper into the issue and resolve it.</p>
<p>To run Discord as administrator, search for Discord and select Run as Administrator on the right:
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss3-4.png" alt="ss3-4" width="600" height="400" loading="lazy"></p>
<h2 id="heading-solution-3-rename-discords-updateexe-file">Solution 3: Rename Discord’s Update.exe File</h2>
<p>There’s a separate executable for updating Discord. This is the update.exe file in the Discord folder. </p>
<p>Renaming this file can force Discord to download a fresh one and subsequently fix the issue for you.</p>
<p><strong>Follow these steps to rename Discord’s update.exe file</strong>
Press <code>WIN</code> (Windows logo key) + <code>R</code> on your keyboard and type in <code>%localappdata%</code>.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss4-5.png" alt="ss4-5" width="600" height="400" loading="lazy"></p>
<p>Look for the Discord folder and open it.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss5-5.png" alt="ss5-5" width="600" height="400" loading="lazy"></p>
<p>Right-click on the <code>Update.exe</code> file and select rename. Then rename it to something like “Updater.exe”. You must not change the extension.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss6-4.png" alt="ss6-4" width="600" height="400" loading="lazy"> </p>
<p>Open the app again and see if the problem gets fixed.</p>
<h2 id="heading-solution-4-temporarily-disable-your-antivirus-program-and-vpn">Solution 4: Temporarily Disable your Antivirus Program and VPN</h2>
<p>Antivirus programs and VPNs are known for interfering with the normal functioning of computers and internet connections. So if you have one or both of them on your PC, it might be stopping Discord from updating.</p>
<p>To disable your antivirus and VPN, open Task Manager, right-click on <code>WIN</code> (Windows logo key) and select Task Manager.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss7-3.png" alt="ss7-3" width="600" height="400" loading="lazy"></p>
<p>Under processes, right-click on Discord and select “End Task”.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss8-3.png" alt="ss8-3" width="600" height="400" loading="lazy"> </p>
<h2 id="heading-solution-5-uninstall-and-reinstall-discord">Solution 5: Uninstall and Reinstall Discord</h2>
<p>If the solutions already discussed fail to work for you, the last resort is to uninstall Discord and reinstall it.</p>
<p><strong>Step 1</strong>: Head over to Control Panel and select Uninstall a Program.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss9-2.png" alt="ss9-2" width="600" height="400" loading="lazy"> </p>
<p><strong>Step 2</strong>: Look for Discord, right-click on it and select uninstall.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss10-2.png" alt="ss10-2" width="600" height="400" loading="lazy"> </p>
<p><strong>Step 3</strong>: To completely remove Discord, press <code>WIN</code> + <code>R</code> on your keyboard, type <code>%localappdata%</code>, and click OK.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss4-5.png" alt="ss4-5" width="600" height="400" loading="lazy"></p>
<p><strong>Step 4</strong>: Right-click on the Discord folder and select Delete.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss11-2.png" alt="ss11-2" width="600" height="400" loading="lazy"> </p>
<p><strong>Step 4</strong>: <a target="_blank" href="https://discord.com/download">Download Discord</a> again and install it.
<img src="https://www.freecodecamp.org/news/content/images/2022/08/ss12-2.png" alt="ss12-2" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope the solutions discussed above help you get Discord to update so you can start using it again.</p>
<p>Thank you for reading.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Real-time Chat Application with Nuxt ]]>
                </title>
                <description>
                    <![CDATA[ By Idorenyin Udoh In a real-time chat application, the recipient can view the sender’s message almost immediately. This can either be in a one-on-one conversation between two parties or a group conversation.  And that's what we're going to build in t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-a-real-time-chat-application-with-nuxt/</link>
                <guid isPermaLink="false">66d45f31706b9fb1c166b94f</guid>
                
                    <category>
                        <![CDATA[ application ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Nuxt.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 10 Aug 2022 19:39:17 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/08/chat-app-with-nuxt-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Idorenyin Udoh</p>
<p>In a real-time chat application, the recipient can view the sender’s message almost immediately. This can either be in a one-on-one conversation between two parties or a group conversation. </p>
<p>And that's what we're going to build in this tutorial. For this application, we will be using <a target="_blank" href="https://nuxtjs.org/">Nuxt</a>, the intuitive Vue framework.</p>
<p>Now that we know what we’re going to be building and the technology we’ll be using, let’s go over the chat API we’ll be using.</p>
<p>For this article, we’ll be going with <a target="_blank" href="https://robinapp.io/">Robin</a> because of its minimal UI and how easy it is to integrate into our app. With that out of the way, let’s get started.</p>
<h1 id="heading-step-1-create-the-nuxt-app">Step 1 – Create the Nuxt App</h1>
<p>First, we need to create the Nuxt app with any of the following commands:</p>
<pre><code>yarn create nuxt-app nuxt-chat-app
<span class="hljs-comment">// OR</span>
npx create-nuxt-app nuxt-chat-app
<span class="hljs-comment">// OR</span>
npm init nuxt-app nuxt-chat-app
</code></pre><h1 id="heading-step-2-create-a-robin-account">Step 2 – Create a Robin Account</h1>
<p>Now that our app is ready, we need to have a Robin account before we can use it. Head over to <a target="_blank" href="https://dashboard.robinapp.co/signup">Robin’s signup page</a> to create a 30-day free trial account. </p>
<p>Robin notifies you 7 days after you've created the account and you can remove your card before the billing date. </p>
<p>After filling out the signup form, you’ll be redirected to a billing page to fill in your card information. On the next page where it redirects you, Robin requests the name of the app you want to create and its authentication type. Feel free to use any name of your choice and either of the auth options.</p>
<p><img src="https://paper-attachments.dropbox.com/s_8728EF96CF25BE6F7A46E3619EB658CA92CDD4D1E377FEC5C8707FC59B5068A6_1658071816533_Screenshot+2022-07-17+at+16.29.23.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h1 id="heading-step-3-get-your-robin-credentials">Step 3 – Get Your Robin Credentials</h1>
<p>Now that we have created an app on our Robin dashboard, there is something you should take note of. There are a couple of credentials you need when using Robin in your Nuxt app:</p>
<ul>
<li>API key,</li>
<li>User token,</li>
<li>User name,</li>
<li>Users, and</li>
<li>Keys</li>
</ul>
<p>Let’s go over each of them individually.</p>
<ul>
<li>API key: Robin automatically creates the API key when you create an app. You can retrieve it from either the getting started or the API config page on your dashboard. It is unique for every application.</li>
<li>User token: The user token is a unique identifier for every user of your Robin app. The token that should be passed to this property is yours since you’re the one using the app on your site. However, it is to be created by you, the user, typically on the server, and then used on the client side.</li>
<li>User name: The User name is the name of the current user of your Robin app. In this case, it will be your name. If you wanted someone else to include your Robin chat on their site or web app (i.e another user of your Robin app), it should be their name.</li>
<li>Users: Users is a list of the users on your Robin app. It usually contains their user tokens, profile images, and user names.</li>
<li>Keys: This fundamentally exists to help us be flexible in describing the user tokens, profile images, and user names in our users list. Here’s an example. If our keys object looks like this:</li>
</ul>
<pre><code class="lang-javascript">keys: {
  <span class="hljs-attr">userToken</span>: <span class="hljs-string">'user_token'</span>,
  <span class="hljs-attr">profileImage</span>: <span class="hljs-string">'profile_image'</span>,
  <span class="hljs-attr">userName</span>: <span class="hljs-string">'user_name'</span>
}
</code></pre>
<p>Then our <code>users</code> array should describe the users’ tokens, profile images, and names with the values in the <code>keys</code> object.</p>
<p>Regardless of the users that would be using your Robin App, Robin requires a <code>userToken</code>, <code>profileImage</code> and a <code>userName</code> from them. Robin requires this for the display name and to identify each message sender and receiver uniquely on the platform.</p>
<pre><code class="lang-js">users: [
  {
    <span class="hljs-string">'user_token'</span>: <span class="hljs-string">'ABCDEF098765GH'</span>,
    <span class="hljs-string">'profile_image'</span>: <span class="hljs-string">'https://url-to-image'</span>,
    <span class="hljs-string">'user_name'</span>: <span class="hljs-string">'Article Reader'</span>
  }
]
</code></pre>
<h1 id="heading-step-4-install-robin-in-your-nuxt-app">Step 4 – Install Robin in Your Nuxt App</h1>
<p>Since we have everything we’ll need, we can go ahead and install Robin.</p>
<pre><code>npm i robin-vue
<span class="hljs-comment">// OR</span>
yarn add robin-vue
</code></pre><h1 id="heading-step-5-setup-the-robin-plugin">Step 5 – Setup the Robin Plugin</h1>
<p>In your <code>plugins</code> directory, create a <code>robin.js</code> file with the plugin setup:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>
<span class="hljs-keyword">import</span> RobinChat <span class="hljs-keyword">from</span> <span class="hljs-string">'robin-vue'</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'robin-vue/dist/style.css'</span>

Vue.use(RobinChat)
</code></pre>
<p>Note that we import the CSS because the <code>RobinChat</code> component does not include any CSS itself.</p>
<h1 id="heading-step-6-register-the-plugin">Step 6 – Register the Plugin</h1>
<p>The <code>plugins</code> property in the <code>nuxt.config.js</code> file is to let our Nuxt app know about the plugins that it should use. So if we don’t include our Robin plugin there, it won’t be available in our app.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-comment">// ...</span>
  <span class="hljs-attr">plugins</span>: [
    { <span class="hljs-attr">src</span>: <span class="hljs-string">'~/plugins/robin.js'</span>, <span class="hljs-attr">mode</span>: <span class="hljs-string">'client'</span> }
  ]
}
</code></pre>
<h1 id="heading-step-7-use-the-plugin">Step 7 – Use the Plugin</h1>
<p>Now what’s left is for us to include the <code>RobinChat</code> component anywhere in our app and pass those credentials we discussed earlier as props. </p>
<p>Once again, the credentials are:</p>
<ul>
<li>API key,</li>
<li>User token,</li>
<li>User name,</li>
<li>Users, and</li>
<li>Keys</li>
</ul>
<p>In this list, what we currently don’t have is our user token and the tokens of the users on our app. </p>
<p>Recall that these tokens are usually created on the server. But we don’t have the luxury of that. So we can go ahead and create them with the help of <a target="_blank" href="https://www.npmjs.com/package/robin.io-js">Robin’s JavaScript SDK</a>. The Vue SDK we previously installed depends on this JavaScript SDK. So we don’t need to install it since it already exists in our app.</p>
<h2 id="heading-how-to-create-the-user-tokens">How to Create the User Tokens</h2>
<p>We can go ahead and create the tokens in the page we're going to include the chat UI. Because it’s for learning purposes, we can go ahead and create tokens for 5 users, ourselves included. We need to come up with usernames for each of them.</p>
<pre><code class="lang-javascript">&lt;template&gt;
  &lt;!-- ... --&gt;
&lt;/template&gt;


<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  data () {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">users</span>: [
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'idorenyin'</span>
        },
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'ayo'</span>
        },
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'elvis'</span>
        },
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'favour'</span>
        },
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'enoch'</span>
        }
      ],
    }
  }
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>Note that the keys in every user object in the <code>users</code> array have to be defined in the <code>keys</code> object that we’ll be passing as a prop to the Robin component.</p>
<pre><code class="lang-javascript">keys: {
  <span class="hljs-attr">userToken</span>: <span class="hljs-string">'user_token'</span>,
  <span class="hljs-attr">profileImage</span>: <span class="hljs-string">'profile_image'</span>,
  <span class="hljs-attr">userName</span>: <span class="hljs-string">'user_name'</span>
},
</code></pre>
<p>Next, we use the SDK’s <code>createUserToken()</code> function to create the tokens after creating a Robin instance, as it says in <a target="_blank" href="https://docs.robinapp.co/frontend-sdks/javascript/getting-started">Robin’s docs</a>.</p>
<pre><code class="lang-javascript">&lt;template&gt;
  &lt;!-- ... --&gt;
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { Robin } <span class="hljs-keyword">from</span> <span class="hljs-string">'robin.io-js'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  data () {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">keys</span>: {
        <span class="hljs-attr">userToken</span>: <span class="hljs-string">'user_token'</span>,
        <span class="hljs-attr">profileImage</span>: <span class="hljs-string">'profile_image'</span>,
        <span class="hljs-attr">userName</span>: <span class="hljs-string">'user_name'</span>
      },
      <span class="hljs-attr">users</span>: [
        <span class="hljs-comment">// ...</span>
      ]
    }
  },
  created () {
    <span class="hljs-built_in">this</span>.createTokens()
  },
  <span class="hljs-attr">methods</span>: {
    <span class="hljs-keyword">async</span> createTokens () {
      <span class="hljs-keyword">const</span> robin = <span class="hljs-keyword">new</span> Robin(<span class="hljs-string">'API_KEY'</span>, <span class="hljs-literal">true</span>)
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-built_in">this</span>.users.length; i++) {
        <span class="hljs-keyword">await</span> robin.createUserToken({
          <span class="hljs-attr">meta_data</span>: {
            <span class="hljs-attr">username</span>: <span class="hljs-built_in">this</span>.users[i].user_name
          }
        }).then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
          <span class="hljs-built_in">this</span>.users[i].user_token = res.data.user_token
        })
      }
    }
  }
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<h2 id="heading-how-to-use-credentials-on-the-robinchat-component">How to Use Credentials on the RobinChat Component</h2>
<p>We now have everything we need to display the Robin chat UI on our app. Whew!<br>We can now go ahead and use the tokens and the other credentials.</p>
<pre><code class="lang-javascript">&lt;template&gt;
  &lt;!-- ... --&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">RobinChat</span>
    <span class="hljs-attr">v-if</span>=<span class="hljs-string">"tokensAreAvailable"</span>
    <span class="hljs-attr">:api-key</span>=<span class="hljs-string">"apiKey"</span>
    <span class="hljs-attr">:user-token</span>=<span class="hljs-string">"users[0].user_token"</span>
    <span class="hljs-attr">user-name</span>=<span class="hljs-string">"Idorenyin Udoh"</span>
    <span class="hljs-attr">:keys</span>=<span class="hljs-string">"keys"</span>
    <span class="hljs-attr">:users</span>=<span class="hljs-string">"users"</span>
  /&gt;</span></span>
&lt;/template&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { Robin } <span class="hljs-keyword">from</span> <span class="hljs-string">'robin.io-js'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  data () {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">tokensAreAvailable</span>: <span class="hljs-literal">false</span>,
      <span class="hljs-attr">apiKey</span>: <span class="hljs-string">'API_KEY'</span>,
      <span class="hljs-attr">keys</span>: {
        <span class="hljs-attr">userToken</span>: <span class="hljs-string">'user_token'</span>,
        <span class="hljs-attr">profileImage</span>: <span class="hljs-string">'profile_image'</span>,
        <span class="hljs-attr">userName</span>: <span class="hljs-string">'user_name'</span>
      },
      <span class="hljs-attr">users</span>: [
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'idorenyin'</span>
        },
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'ayo'</span>
        },
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'elvis'</span>
        },
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'favour'</span>
        },
        {
          <span class="hljs-attr">user_token</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">profile_image</span>: <span class="hljs-string">''</span>,
          <span class="hljs-attr">user_name</span>: <span class="hljs-string">'enoch'</span>
        }
      ]
    }
  },
  created () {
    <span class="hljs-built_in">this</span>.createTokens()
  },
  <span class="hljs-attr">methods</span>: {
    <span class="hljs-keyword">async</span> createTokens () {
      <span class="hljs-keyword">const</span> robin = <span class="hljs-keyword">new</span> Robin(<span class="hljs-built_in">this</span>.apiKey, <span class="hljs-literal">true</span>)
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-built_in">this</span>.users.length; i++) {
        <span class="hljs-keyword">await</span> robin.createUserToken({
          <span class="hljs-attr">meta_data</span>: {
            <span class="hljs-attr">username</span>: <span class="hljs-built_in">this</span>.users[i].user_name
          }
        }).then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
          <span class="hljs-built_in">this</span>.users[i].user_token = res.data.user_token
        })
      }
      <span class="hljs-built_in">this</span>.tokensAreAvailable = <span class="hljs-literal">true</span>
    }
  }
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>Note that we only display the <code>RobinChat</code> component when all the users’ tokens are available to avoid errors.</p>
<p>This is what the result looks like:</p>
<p><img src="https://paper-attachments.dropbox.com/s_8728EF96CF25BE6F7A46E3619EB658CA92CDD4D1E377FEC5C8707FC59B5068A6_1658311851926_Screenshot+2022-07-20+at+11.10.45.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The app is available <a target="_blank" href="https://nuxt-chat-lmqlbq79p-idorenyinudoh.vercel.app/">here</a>.</p>
<p>Note that I used previously-created user tokens for this app because you wouldn’t be able to view messages if tokens are created every time the app loads. Permanent tokens are what make the messages on Robin persist.</p>
<p>Also, I created <a target="_blank" href="https://nuxt-chat-app-git-ayo-idorenyinudoh.vercel.app/">another app</a> for the user Ayo. You can check it out too. This way, you can test the real-time communication between Idorenyin and Ayo.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>You just learned how to implement real-time communication on a Nuxt application with Robin. </p>
<p>The ease of integration makes it super fast to implement a chat system in your app and focus on building/maintaining it. </p>
<p>If you make sure to create your users’ tokens on the server, then implementing the integration on the frontend wouldn't be too hard.</p>
<p>Happy building!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Real-time Chat App with React, Node, Socket.io, and HarperDB ]]>
                </title>
                <description>
                    <![CDATA[ In this article, we will be using Socket.io and HarperDB to build a fullstack, real-time chat application with chat rooms.  This will be a great project to learn how to put together fullstack apps, and how to create an app where the backend can commu... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-realtime-chat-app-with-react-express-socketio-and-harperdb/</link>
                <guid isPermaLink="false">66c8c995e9e57963a5d82ad3</guid>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Danny ]]>
                </dc:creator>
                <pubDate>Thu, 04 Aug 2022 15:53:22 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/08/pexels-keira-burton-6146929.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, we will be using Socket.io and HarperDB to build a fullstack, real-time chat application with chat rooms. </p>
<p>This will be a great project to learn how to put together fullstack apps, and how to create an app where the backend can communicate with the frontend in real time.</p>
<p>Normally, using HTTP requests, the server cannot push data to the client in real time. But using Socket.io, the server is able to push real time information to the client about some events that happened on the server.</p>
<p>The app that we'll be building will have two pages:</p>
<p>A join-a-chat-room page:</p>
<p><img src="https://lh6.googleusercontent.com/SyHBvbkVavSJTxNV1nOi2-V_YYXm3upFOJvAzBXwd1VNu10SKV4WBSyQS1tdf4OhiDbqlq3sLqCxWRSJafZwhfcsp72DSKEy3-hk3JvNVGcmsSgkHHpEH69pnBDVKCv6bXiMza4cC4BZiLCOiqKPAIk" alt="How our app home page will look: a form with username input, select room dropdown and Join Room button" width="475" height="468" loading="lazy"></p>
<p>And a chat-room page:</p>
<p><img src="https://lh6.googleusercontent.com/uRkjeHOuGGGf9HnK7bZ1Zd6WeNMo8kaR6Py0_RiEDx1VUuTPx4oYNvfmPlOxNLAicM7bnr9rm0oY0E7k0fwfaZIEz4K1V-5ejOM3ztmrmjIjC8OsRyzNf0HZurxMWUMzdLgic7o8oC-RQxELo8vdcVw" alt="The finished chat page" width="1216" height="678" loading="lazy"></p>
<p>Here's what we'll be using to build this app:</p>
<ul>
<li><strong>Frontend</strong>: <a target="_blank" href="https://reactjs.org/docs/create-a-new-react-app.html">React</a> (A frontend JavaScript framework for building interactive applications)</li>
<li><strong>Backend</strong>: <a target="_blank" href="https://nodejs.org/en/">Node</a> and <a target="_blank" href="https://expressjs.com/">Express</a> (Express is very popular NodeJS framework that allows us to easily create APIs and backends)</li>
<li><strong>Database</strong>: <a target="_blank" href="https://harperdb.io/">HarperDB</a> (a data + application platform that allows you to query data using either SQL or NoSQL. HarperDB also has a built-in API, saving us from having to write a lot of backend code)</li>
<li><strong>Realtime communication</strong>: <a target="_blank" href="https://socket.io/docs/v3/">Socket.io</a> (see below!)</li>
</ul>
<p><a target="_blank" href="https://github.com/DoableDanny/Realtime-chat-app-with-rooms">Here is the source code</a> (remember to give it a star ⭐).</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-socketio">What is Socket.io?</a></li>
<li><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-join-a-room-page">How to Build the "Join a Room" Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-the-server">How to Set Up the Server</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-our-first-socketio-event-listener-on-the-server">How to Create our First Socket.io Event Listener on the Server</a></li>
<li><a class="post-section-overview" href="#heading-how-rooms-work-in-socketio">How Rooms Work in Socket.io</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-chat-page">How to Build the Chat Page</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-messages-component-b">How to Create the Messages Component (B)</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-schema-and-table-in-harperdb">How to Create a Schema and Table in HarperDB</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-send-message-component-c">How to Create the Send Message Component (C)</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-harperdb-environment-variables">How to Set Up HarperDB Environment Variables</a></li>
<li><a class="post-section-overview" href="#heading-how-to-allow-users-to-send-messages-to-each-other-with-socketio">How to Allow Users to Send Messages to Each Other with Socket.io</a></li>
<li><a class="post-section-overview" href="#heading-how-to-get-messages-from-harperdb">How to Get Messages from HarperDB</a></li>
<li><a class="post-section-overview" href="#heading-how-to-display-the-last-100-messages-on-the-client">How to Display the Last 100 Messages on the Client</a></li>
<li><a class="post-section-overview" href="#heading-how-to-display-the-room-and-users-a">How to Display the Room and Users (A)</a></li>
<li><a class="post-section-overview" href="#heading-how-to-remove-a-user-from-a-socketio-room">How to Remove a User from a Socket.io Room</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-the-socketio-disconnect-event-listener">How to Add the Socket.io Disconnect Event Listener</a></li>
</ol>
<h2 id="heading-what-is-socketio">What is Socket.IO?</h2>
<p>Socket.IO allows the server to push information to the client in real time, when events occur on the server.</p>
<p>For example, if you were playing a multiplayer game, an event could be your "friend" scoring a spectacular goal against you.</p>
<p>With Socket.IO, you'd know (almost) instantly about conceding a goal.</p>
<p>Without Socket.IO, the client would have to make multiple polling AJAX calls to verify that the event has occurred on the server. For example, the client could use JavaScript to check for an event on the server every 5 seconds.</p>
<p>Socket.IO means that the client doesn't have to make multiple polling AJAX calls to verify if some event has occurred on the server. Instead, the server sends the info to the client as soon as it gets it. Much better. 👌</p>
<p>So, Socket.IO allows us to easily build real time applications, such as chat apps and multiplayer games.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<h3 id="heading-1-how-to-set-up-our-folders">1. How to set up our folders</h3>
<p>Start a new project in your text editor of choice (VS Code for me), and create two folders at the root called client and server.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/folder-structure.JPG" alt="Realtime chat app folder structure" width="600" height="400" loading="lazy"></p>
<p>We will create our frontend React application in the client folder, and our Node/Express backend in the server folder.</p>
<h3 id="heading-2-how-to-install-our-client-dependencies">2. How to install our client dependencies</h3>
<p>Open up a terminal in the root of the project (in VS Code, you can do this by pressing Ctrl+' or by going to <em>terminal</em>-&gt;<em>new terminal</em>)</p>
<p>Next, we will install React into our client directory:</p>
<pre><code class="lang-bash">$ npx create-react-app client
</code></pre>
<p>After React has installed, change directories into the client folder, and install the following dependencies:</p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">cd</span> client
$ npm i react-router-dom socket.io-client
</code></pre>
<p>React-router-dom will allow us to set up routes to our different React components – essentially creating different pages.</p>
<p>Socket.io-client is the client version of socket.io, that allows us to "emit" events to the server. Once received by the server, we can use the server version of socket.io to do stuff like sending messages to users in the same room as the sender, or join a user to a socket room. </p>
<p>You will gain a better understanding of this later when we come to implement these ideas with code.</p>
<h3 id="heading-3-how-to-boot-up-the-react-app">3. How to boot up the React app</h3>
<p>Let's check to make sure everything is working by running the following command from the client directory:</p>
<pre><code class="lang-bash">$ npm start
</code></pre>
<p>Webpack will build the React app and serve it to <a target="_blank" href="http://localhost:3000">http://localhost:3000</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/react-is-running.JPG" alt="Create react app up and running on localhost" width="600" height="400" loading="lazy"></p>
<p>Let's now set up our HarperDB database that we will use to permanently save messages sent by users.</p>
<h3 id="heading-how-to-set-up-harperdb">How to set up HarperDB</h3>
<p>First, <a target="_blank" href="https://studio.harperdb.io/">create an account with HarperDB</a>.</p>
<p>Then create a new HarperDB cloud instance:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/harper_instance.JPG" alt="create HarperDB instance" width="600" height="400" loading="lazy"></p>
<p>To make things easy, select the cloud instance:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance-type.JPG" alt="select HarperDB instance type" width="600" height="400" loading="lazy"></p>
<p>Select the cloud provider (I chose AWS):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/cloud_provider.JPG" alt="select HarperDB cloud provider" width="600" height="400" loading="lazy"></p>
<p>Name your cloud instance, and create your instance credentials:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance_credentials.JPG" alt="select HarperDB instance credentials" width="600" height="400" loading="lazy"></p>
<p>HarperDB has a generous free tier that we can use for this project, so select that:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance_specs.JPG" alt="select HarperDB instance specs" width="600" height="400" loading="lazy"></p>
<p>Check your details are correct, then create the instance.</p>
<p>It will take a few minutes to create the instance, so let's crack on and make our first React component!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance_loading.JPG" alt="HarperDB instance loading" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-build-the-join-a-room-page">How to Build the "Join a Room" Page</h2>
<p>Our homepage is going to end up looking like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/home-page.JPG" alt="How our app home page will look: a form with username input, select room dropdown and Join Room button" width="600" height="400" loading="lazy"></p>
<p>The user will enter a username, select a chat room from the dropdown, then click "Join Room". The user will then be taken to the chat room page.</p>
<p>So, let's make this homepage.</p>
<h3 id="heading-1-how-to-create-the-html-form-and-add-styles">1. How to create the HTML form and add styles</h3>
<p>Create a new file at <em>src/pages/home/index.js.</em></p>
<p>We will add basic styling to our app using CSS modules, so create a new file: <em>src/pages/home/styles.module.css</em>.</p>
<p>Our folder structure should now look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/pages-folder-structure.JPG" alt="pages folder with home page component" width="600" height="400" loading="lazy"></p>
<p>Now let's create the basic form HTML:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/pages/home/index.js</span>

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles.module.css'</span>;

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.formContainer}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{`<span class="hljs-tag">&lt;&gt;</span>DevRooms<span class="hljs-tag">&lt;/&gt;</span>`}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.input}</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Username...'</span> /&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.input}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>-- Select Room --<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">'javascript'</span>&gt;</span>JavaScript<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">'node'</span>&gt;</span>Node<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">'express'</span>&gt;</span>Express<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">'react'</span>&gt;</span>React<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'btn btn-secondary'</span>&gt;</span>Join Room<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home;
</code></pre>
<p>Above, we have a simple text input to capture the username, and a select dropdown with some default options for the user to select a chat room to join.</p>
<p>Let's now import this component into App.js, and set up a route for the component using the react-router-dom package. This will be our home page, so the path will just be "/":</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/App.js</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>;
<span class="hljs-keyword">import</span> { BrowserRouter <span class="hljs-keyword">as</span> Router, Routes, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> Home <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/home'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Router</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'App'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">'/'</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>} /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">Routes</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Router</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Now let's add some base styles to make our app look more presentable:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* client/src/App.css */</span>

<span class="hljs-selector-tag">html</span> * {
  <span class="hljs-attribute">font-family</span>: Arial;
  <span class="hljs-attribute">box-sizing</span>: border-box;
}
<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">overflow</span>: hidden;
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">63</span>, <span class="hljs-number">73</span>, <span class="hljs-number">204</span>);
}
<span class="hljs-selector-pseudo">::-webkit-scrollbar</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">20px</span>;
}
<span class="hljs-selector-pseudo">::-webkit-scrollbar-track</span> {
  <span class="hljs-attribute">background-color</span>: transparent;
}
<span class="hljs-selector-pseudo">::-webkit-scrollbar-thumb</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#d6dee1</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">6px</span> solid transparent;
  <span class="hljs-attribute">background-clip</span>: content-box;
}
<span class="hljs-selector-pseudo">::-webkit-scrollbar-thumb</span><span class="hljs-selector-pseudo">:hover</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#a8bbbf</span>;
}
<span class="hljs-selector-class">.btn</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">14px</span> <span class="hljs-number">14px</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">6px</span>;
  <span class="hljs-attribute">font-weight</span>: bold;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.1rem</span>;
  <span class="hljs-attribute">cursor</span>: pointer;
  <span class="hljs-attribute">border</span>: none;
}
<span class="hljs-selector-class">.btn-outline</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">153</span>, <span class="hljs-number">217</span>, <span class="hljs-number">234</span>);
  <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-built_in">rgb</span>(<span class="hljs-number">153</span>, <span class="hljs-number">217</span>, <span class="hljs-number">234</span>);
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">63</span>, <span class="hljs-number">73</span>, <span class="hljs-number">204</span>);
}
<span class="hljs-selector-class">.btn-primary</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">153</span>, <span class="hljs-number">217</span>, <span class="hljs-number">234</span>);
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">0</span>, <span class="hljs-number">24</span>, <span class="hljs-number">111</span>);
}
<span class="hljs-selector-class">.btn-secondary</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">0</span>, <span class="hljs-number">24</span>, <span class="hljs-number">111</span>);
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
}
</code></pre>
<p>Let's also add the styles specific to our home page component:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* client/src/pages/home/styles.module.css */</span>

<span class="hljs-selector-class">.container</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">63</span>, <span class="hljs-number">73</span>, <span class="hljs-number">204</span>);
}
<span class="hljs-selector-class">.formContainer</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">400px</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto <span class="hljs-number">0</span> auto;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">32px</span>;
  <span class="hljs-attribute">background</span>: lightblue;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">6px</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">28px</span>;
}
<span class="hljs-selector-class">.input</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">12px</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">6px</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-built_in">rgb</span>(<span class="hljs-number">63</span>, <span class="hljs-number">73</span>, <span class="hljs-number">204</span>);
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.9rem</span>;
}
<span class="hljs-selector-class">.input</span> <span class="hljs-selector-tag">option</span> {
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">20px</span>;
}
</code></pre>
<p>Let's also make the "Join Room" button full width by adding a style attribute:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/pages/home/index.js</span>

&lt;button className=<span class="hljs-string">'btn btn-secondary'</span> style={{ <span class="hljs-attr">width</span>: <span class="hljs-string">'100%'</span> }}&gt;Join Room&lt;/button&gt;
</code></pre>
<p>Our home page is now looking solid:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/home-page-html.JPG" alt="Fully-styled home page" width="600" height="400" loading="lazy"></p>
<h3 id="heading-2-how-to-add-functionality-to-the-join-room-form">2. How to add functionality to the Join Room form</h3>
<p>Now we have a basic form and styling, so it's time to add some functionality.</p>
<p>Here's what we want to happen when the user clicks the "Join Room" button:</p>
<ol>
<li>Check that the username and room fields are filled in.</li>
<li>If so, we emit a socket event to our server.</li>
<li>Redirect the user to the Chat page (which we will create later).</li>
</ol>
<p>We are going to need to create some state to store <em>username</em> and <em>room</em> values. We also need to create a socket instance.</p>
<p>We could create these states directly within our home component, but our Chat page will also need access to <em>username</em>, <em>room</em> and <em>socket</em>. So we will lift the state up to App.js, where we can then pass these variables down to both the Homepage and Chat page components.</p>
<p>So, let's create our state and set up a socket in App.js, and pass these variables down as props to the  component. We'll also pass the set state functions so we can alter state from :</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/App.js</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>; <span class="hljs-comment">// Add this</span>
<span class="hljs-keyword">import</span> { BrowserRouter <span class="hljs-keyword">as</span> Router, Routes, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> io <span class="hljs-keyword">from</span> <span class="hljs-string">'socket.io-client'</span>; <span class="hljs-comment">// Add this</span>
<span class="hljs-keyword">import</span> Home <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/home'</span>;

<span class="hljs-keyword">const</span> socket = io.connect(<span class="hljs-string">'http://localhost:4000'</span>); <span class="hljs-comment">// Add this -- our server will run on port 4000, so we connect to it from here</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [username, setUsername] = useState(<span class="hljs-string">''</span>); <span class="hljs-comment">// Add this</span>
  <span class="hljs-keyword">const</span> [room, setRoom] = useState(<span class="hljs-string">''</span>); <span class="hljs-comment">// Add this</span>

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Router</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'App'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span>
            <span class="hljs-attr">path</span>=<span class="hljs-string">'/'</span>
            <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>
              &lt;<span class="hljs-attr">Home</span>
                <span class="hljs-attr">username</span>=<span class="hljs-string">{username}</span> // <span class="hljs-attr">Add</span> <span class="hljs-attr">this</span>
                <span class="hljs-attr">setUsername</span>=<span class="hljs-string">{setUsername}</span> // <span class="hljs-attr">Add</span> <span class="hljs-attr">this</span>
                <span class="hljs-attr">room</span>=<span class="hljs-string">{room}</span> // <span class="hljs-attr">Add</span> <span class="hljs-attr">this</span>
                <span class="hljs-attr">setRoom</span>=<span class="hljs-string">{setRoom}</span> // <span class="hljs-attr">Add</span> <span class="hljs-attr">this</span>
                <span class="hljs-attr">socket</span>=<span class="hljs-string">{socket}</span> // <span class="hljs-attr">Add</span> <span class="hljs-attr">this</span>
              /&gt;</span>
            }
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">Routes</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Router</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>We can now access these props in our Home Component. We will use destructuring to get the props:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/pages/home/index.js</span>

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./style.module.css'</span>;

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">(<span class="hljs-params">{ username, setUsername, room, setRoom, socket }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="hljs-comment">// ...</span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home;
</code></pre>
<p>When the user types their username or selects a room, we need to update the <em>username</em> and <em>room</em> state variables:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/pages/home/index.js</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">(<span class="hljs-params">{ username, setUsername, room, setRoom, socket }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      // ...
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.input}</span>
          <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Username...'</span>
          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setUsername(e.target.value)} // Add this
        /&gt;

        <span class="hljs-tag">&lt;<span class="hljs-name">select</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.input}</span>
          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setRoom(e.target.value)} // Add this
        &gt;
         // ...
        <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>

        // ...
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home;
</code></pre>
<p>Now we are capturing the data entered by the user, we can create a <em>joinRoom()</em> callback function for when the user clicks the "Join Room" button:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/pages/home/index.js</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">(<span class="hljs-params">{ username, setUsername, room, setRoom, socket }</span>) =&gt;</span> {

  <span class="hljs-comment">// Add this</span>
  <span class="hljs-keyword">const</span> joinRoom = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (room !== <span class="hljs-string">''</span> &amp;&amp; username !== <span class="hljs-string">''</span>) {
      socket.emit(<span class="hljs-string">'join_room'</span>, { username, room });
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.container}</span>&gt;</span>
      // ...

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">'btn btn-secondary'</span>
          <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">width:</span> '<span class="hljs-attr">100</span>%' }}
          <span class="hljs-attr">onClick</span>=<span class="hljs-string">{joinRoom}</span> // <span class="hljs-attr">Add</span> <span class="hljs-attr">this</span>
        &gt;</span>
          Join Room
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      // ...
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home;
</code></pre>
<p>Above, when the user clicks the button, a socket event called _join<em>room</em> is emitted, along with an object containing the user's username and selected room. This event will be received by our server a little later on where we will do some magic.</p>
<p>To finish our home page component, we need to add a redirect at the bottom of our <em>joinRoom()</em> function to take the user to the <em>/chat</em> page:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/pages/home/index.js</span>

<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>; <span class="hljs-comment">// Add this</span>

<span class="hljs-keyword">const</span> Home = <span class="hljs-function">(<span class="hljs-params">{ username, setUsername, room, setRoom, socket }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> navigate = useNavigate(); <span class="hljs-comment">// Add this</span>

  <span class="hljs-keyword">const</span> joinRoom = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (room !== <span class="hljs-string">''</span> &amp;&amp; username !== <span class="hljs-string">''</span>) {
      socket.emit(<span class="hljs-string">'join_room'</span>, { username, room });
    }

    <span class="hljs-comment">// Redirect to /chat</span>
    navigate(<span class="hljs-string">'/chat'</span>, { <span class="hljs-attr">replace</span>: <span class="hljs-literal">true</span> }); <span class="hljs-comment">// Add this</span>
  };

 <span class="hljs-comment">// ...</span>
</code></pre>
<p>Test it out: type a username and select a room, then click <em>Join Room</em>. You should be taken to the route <a target="_blank" href="http://localhost:3000/chat">http://localhost:3000/chat</a> – currently an empty page.</p>
<p>But before we create our Chat Page frontend, let's get some stuff running on the server.</p>
<h2 id="heading-how-to-set-up-the-server">How to Set Up the Server</h2>
<p>On the server, we are going to listen out for socket events emitted from the frontend. Currently, we only have a join_room event being emitted from React, so we will add this event listener first.</p>
<p>But before that, we need to install our server dependencies and get the server up and running.</p>
<h3 id="heading-1-how-to-install-the-server-dependencies">1. How to install the server dependencies</h3>
<p>Open up a new terminal (in VS code: Terminal-&gt;New Terminal), change directory into our server folder, initialise a package.json file, and install the following dependencies:</p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">cd</span> server
$ npm init -y
$ npm i axios cors express socket.io dotenv
</code></pre>
<ul>
<li>Axios is a commonly used package to easily make requests to APIs. </li>
<li>Cors allows our client to make requests to other origins – necessary for socket.io to work properly. See <a target="_blank" href="https://medium.com/@electra_chong/what-is-cors-what-is-it-used-for-308cafa4df1a">What is CORS?</a> if you haven't heard of CORS before.</li>
<li>Express is a NodeJS framework that allows us to write our backend more easily with less code.</li>
<li>Socket.io is a library that allows the client and server to communicate in realtime – which isn't possible with standard HTTP requests.</li>
<li>Dotenv is a module that allows us to store private keys and passwords safely, and load them into our code when needed.</li>
</ul>
<p>We will also install nodemon as a dev dependency, so we don't have to restart our server every time we make a change to the code – saving us time and energy:</p>
<pre><code class="lang-bash">$ npm i -D nodemon
</code></pre>
<h3 id="heading-2-how-to-boot-up-our-server">2. How to boot up our server</h3>
<p>Create a folder called index.js in the root of our server directory, and add the following code to get a server up and running:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>);
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cors'</span>);

app.use(cors()); <span class="hljs-comment">// Add cors middleware</span>

<span class="hljs-keyword">const</span> server = http.createServer(app);

server.listen(<span class="hljs-number">4000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">'Server is running on port 4000'</span>);
</code></pre>
<p>Open up the package.json file on our server, and add a script that will allow us to use nodemon in development:</p>
<pre><code class="lang-json">{
  ...
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"nodemon index.js"</span>
  },
  ...
}
</code></pre>
<p>Now, let's boot up our server by running the following command:</p>
<pre><code class="lang-bash">$ npm run dev
</code></pre>
<p>We can quickly check that our server is running correctly by adding a get request handler:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();
http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>);
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cors'</span>);

app.use(cors()); <span class="hljs-comment">// Add cors middleware</span>

<span class="hljs-keyword">const</span> server = http.createServer(app);

<span class="hljs-comment">// Add this</span>
app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">'Hello world'</span>);
});

server.listen(<span class="hljs-number">4000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">'Server is running on port 3000'</span>);
</code></pre>
<p>Now go to <a target="_blank" href="http://localhost:4000/">http://localhost:4000/</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/localhost4000.JPG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Our server is up and running. It's now time to do some server-side Socket.io stuff!</p>
<h2 id="heading-how-to-create-our-first-socketio-event-listener-on-the-server">How to Create our First Socket.io Event Listener on the Server</h2>
<p>Remember when we emitted a _join<em>room</em> event from the client? Well, we are soon going to be listening for that event on the server and adding the user to a socket room.</p>
<p>But first, we need to listen out for when a client connects to the server via socket.io-client.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();
http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>);
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cors'</span>);
<span class="hljs-keyword">const</span> { Server } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'socket.io'</span>); <span class="hljs-comment">// Add this</span>

app.use(cors()); <span class="hljs-comment">// Add cors middleware</span>

<span class="hljs-keyword">const</span> server = http.createServer(app); <span class="hljs-comment">// Add this</span>

<span class="hljs-comment">// Add this</span>
<span class="hljs-comment">// Create an io server and allow for CORS from http://localhost:3000 with GET and POST methods</span>
<span class="hljs-keyword">const</span> io = <span class="hljs-keyword">new</span> Server(server, {
  <span class="hljs-attr">cors</span>: {
    <span class="hljs-attr">origin</span>: <span class="hljs-string">'http://localhost:3000'</span>,
    <span class="hljs-attr">methods</span>: [<span class="hljs-string">'GET'</span>, <span class="hljs-string">'POST'</span>],
  },
});

<span class="hljs-comment">// Add this</span>
<span class="hljs-comment">// Listen for when the client connects via socket.io-client</span>
io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User connected <span class="hljs-subst">${socket.id}</span>`</span>);

  <span class="hljs-comment">// We can write our socket event listeners in here...</span>
});

server.listen(<span class="hljs-number">4000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">'Server is running on port 3000'</span>);
</code></pre>
<p>Now, when the client connects from the frontend, the backend captures the connection event, and will log <code>User connected</code> with the unique socket id for that particular client.</p>
<p>Let's test if the server is now capturing the connection event from the client. Go to your React app at <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a> and refresh the page. </p>
<p>You should see the following log in your server terminal console:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/user-connected.JPG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Awesome, our client has connected to our server via socket.io. Our client and server can now communicate in real time!</p>
<h2 id="heading-how-rooms-work-in-socketio">How Rooms Work in Socket.io</h2>
<p>From the <a target="_blank" href="https://socket.io/docs/v3/rooms/">Socket.io docs</a>:</p>
<blockquote>
<p>"A <em>room</em> is an arbitrary channel that sockets can <code>join</code> and <code>leave</code>. It can be used to broadcast events to a subset of clients."</p>
</blockquote>
<p>So, we can join the user to a room, and then the server can send messages to all users in that room – allowing users to send messages to each other in real time. Cool!</p>
<h3 id="heading-how-to-join-the-user-to-a-socketio-room">How to join the user to a Socket.io room</h3>
<p>Once the user has connected via Socket.io, we can add our socket event listeners on the server to listen for events emitted from the client. Also, we can emit events on the server, and listen for them on the client.</p>
<p>Let's now listen for the _join<em>room</em> event, capture the data (username and room), and add the user to a socket room:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-comment">// Listen for when the client connects via socket.io-client</span>
io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User connected <span class="hljs-subst">${socket.id}</span>`</span>);

  <span class="hljs-comment">// Add this</span>
  <span class="hljs-comment">// Add a user to a room</span>
  socket.on(<span class="hljs-string">'join_room'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { username, room } = data; <span class="hljs-comment">// Data sent from client when join_room event emitted</span>
    socket.join(room); <span class="hljs-comment">// Join the user to a socket room</span>
  });
});
</code></pre>
<h3 id="heading-how-to-send-a-message-to-users-in-a-room">How to send a message to users in a room</h3>
<p>Let's now send a message to all users in the room, apart from the user that just joined, to notify them that a new user has joined:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-keyword">const</span> CHAT_BOT = <span class="hljs-string">'ChatBot'</span>; <span class="hljs-comment">// Add this</span>
<span class="hljs-comment">// Listen for when the client connects via socket.io-client</span>
io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User connected <span class="hljs-subst">${socket.id}</span>`</span>);

  <span class="hljs-comment">// Add a user to a room</span>
  socket.on(<span class="hljs-string">'join_room'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { username, room } = data; <span class="hljs-comment">// Data sent from client when join_room event emitted</span>
    socket.join(room); <span class="hljs-comment">// Join the user to a socket room</span>

    <span class="hljs-comment">// Add this</span>
    <span class="hljs-keyword">let</span> __createdtime__ = <span class="hljs-built_in">Date</span>.now(); <span class="hljs-comment">// Current timestamp</span>
    <span class="hljs-comment">// Send message to all users currently in the room, apart from the user that just joined</span>
    socket.to(room).emit(<span class="hljs-string">'receive_message'</span>, {
      <span class="hljs-attr">message</span>: <span class="hljs-string">`<span class="hljs-subst">${username}</span> has joined the chat room`</span>,
      <span class="hljs-attr">username</span>: CHAT_BOT,
      __createdtime__,
    });
  });
});
</code></pre>
<p>Above, we are emitting a receive_message event to all clients in the room the current user has just joined, along with some data: the message, username who sent the message, and the time the message was sent.</p>
<p>We will add an event listener in our React application a little later to capture this event, and output the message on the screen.</p>
<p>Let's also send a welcome message to the newly-joined user:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/index.js</span>

io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {
  <span class="hljs-comment">// ...</span>

    <span class="hljs-comment">// Add this</span>
    <span class="hljs-comment">// Send welcome msg to user that just joined chat only</span>
    socket.emit(<span class="hljs-string">'receive_message'</span>, {
      <span class="hljs-attr">message</span>: <span class="hljs-string">`Welcome <span class="hljs-subst">${username}</span>`</span>,
      <span class="hljs-attr">username</span>: CHAT_BOT,
      __createdtime__,
    });
  });
});
</code></pre>
<p>When we add a user to a Socket.io room, Socket.io only stores the socket ids for each user. But we will need the usernames of everyone in the room, as well as the room name. So, let's store that data in variables on the server:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">const</span> CHAT_BOT = <span class="hljs-string">'ChatBot'</span>;
<span class="hljs-comment">// Add this</span>
<span class="hljs-keyword">let</span> chatRoom = <span class="hljs-string">''</span>; <span class="hljs-comment">// E.g. javascript, node,...</span>
<span class="hljs-keyword">let</span> allUsers = []; <span class="hljs-comment">// All users in current chat room</span>

<span class="hljs-comment">// Listen for when the client connects via socket.io-client</span>
io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {
    <span class="hljs-comment">// ...</span>

    <span class="hljs-comment">// Add this</span>
    <span class="hljs-comment">// Save the new user to the room</span>
    chatRoom = room;
    allUsers.push({ <span class="hljs-attr">id</span>: socket.id, username, room });
    chatRoomUsers = allUsers.filter(<span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> user.room === room);
    socket.to(room).emit(<span class="hljs-string">'chatroom_users'</span>, chatRoomUsers);
    socket.emit(<span class="hljs-string">'chatroom_users'</span>, chatRoomUsers);
  });
});
</code></pre>
<p>Above, we are also sending an array of all the chatRoomUsers back to the client via the _chatroom<em>users</em> event, so we can list all the usernames in the room on the frontend.</p>
<p>Before we add any more code to our server, let's go back to our frontend and create the Chat page – so we can test out if we are receiving the _receive<em>message</em> events. </p>
<h2 id="heading-how-to-build-the-chat-page">How to Build the Chat Page</h2>
<p>In your client folder, create two new files:</p>
<ol>
<li>src/pages/chat/index.js</li>
<li>src/pages/chat/styles.module.css</li>
</ol>
<p>Let's add some styles that we'll use in our chat page and components:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* client/src/pages/chat/styles.module.css */</span>

<span class="hljs-selector-class">.chatContainer</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">1100px</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
  <span class="hljs-attribute">display</span>: grid;
  <span class="hljs-attribute">grid-template-columns</span>: <span class="hljs-number">1</span>fr <span class="hljs-number">4</span>fr;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">20px</span>;
}

<span class="hljs-comment">/* Room and users component */</span>
<span class="hljs-selector-class">.roomAndUsersColumn</span> {
  <span class="hljs-attribute">border-right</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#dfdfdf</span>;
}
<span class="hljs-selector-class">.roomTitle</span> {
  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">60px</span>;
  <span class="hljs-attribute">text-transform</span>: uppercase;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
}
<span class="hljs-selector-class">.usersTitle</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
}
<span class="hljs-selector-class">.usersList</span> {
  <span class="hljs-attribute">list-style-type</span>: none;
  <span class="hljs-attribute">padding-left</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">60px</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">153</span>, <span class="hljs-number">217</span>, <span class="hljs-number">234</span>);
}
<span class="hljs-selector-class">.usersList</span> <span class="hljs-selector-tag">li</span> {
  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">12px</span>;
}

<span class="hljs-comment">/* Messages */</span>
<span class="hljs-selector-class">.messagesColumn</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">85vh</span>;
  <span class="hljs-attribute">overflow</span>: auto;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">10px</span> <span class="hljs-number">10px</span> <span class="hljs-number">40px</span>;
}
<span class="hljs-selector-class">.message</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">0</span>, <span class="hljs-number">24</span>, <span class="hljs-number">111</span>);
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">6px</span>;
  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">24px</span>;
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">600px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">12px</span>;
}
<span class="hljs-selector-class">.msgMeta</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">153</span>, <span class="hljs-number">217</span>, <span class="hljs-number">234</span>);
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.75rem</span>;
}
<span class="hljs-selector-class">.msgText</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
}

<span class="hljs-comment">/* Message input and button */</span>
<span class="hljs-selector-class">.sendMessageContainer</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">16px</span> <span class="hljs-number">20px</span> <span class="hljs-number">20px</span> <span class="hljs-number">16px</span>;
}
<span class="hljs-selector-class">.messageInput</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">14px</span>;
  <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">16px</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">60%</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">6px</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-built_in">rgb</span>(<span class="hljs-number">153</span>, <span class="hljs-number">217</span>, <span class="hljs-number">234</span>);
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.9rem</span>;
}
</code></pre>
<p>Now, let's see what our Chat page will end up looking like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/chat-page.JPG" alt="The finished chat page" width="600" height="400" loading="lazy"></p>
<p>Adding all of the code and logic for this page in one file could get confusing and difficult to manage, so let's take advantage of the fact that we are using an awesome frontend framework (React) and <strong>split our page into components</strong>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-248.png" alt="The chat page split into three components" width="600" height="400" loading="lazy"></p>
<h3 id="heading-the-chat-page-components">The chat page components:</h3>
<p><strong>A</strong>: Contains the room name, a list of users in that room, and a "Leave" button that removes the user from the room.</p>
<p><strong>B</strong>: The sent messages. Upon initial render, the last 100 messages sent in that room will be fetched from the database and shown to the user.</p>
<p><strong>C</strong>: An input and button to type and send a message.</p>
<p>We will first create component B, so we can display messages to the user.</p>
<h2 id="heading-how-to-create-the-messages-component-b">How to Create the Messages Component (B)</h2>
<p>Create a new file at src/pages/chat/messages.js and add the following code:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/pages/chat/messages.js</span>

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles.module.css'</span>;
<span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> Messages = <span class="hljs-function">(<span class="hljs-params">{ socket }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [messagesRecieved, setMessagesReceived] = useState([]);

  <span class="hljs-comment">// Runs whenever a socket event is recieved from the server</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    socket.on(<span class="hljs-string">'receive_message'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(data);
      setMessagesReceived(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> [
        ...state,
        {
          <span class="hljs-attr">message</span>: data.message,
          <span class="hljs-attr">username</span>: data.username,
          <span class="hljs-attr">__createdtime__</span>: data.__createdtime__,
        },
      ]);
    });

    <span class="hljs-comment">// Remove event listener on component unmount</span>
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> socket.off(<span class="hljs-string">'receive_message'</span>);
  }, [socket]);

  <span class="hljs-comment">// dd/mm/yyyy, hh:mm:ss</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatDateFromTimestamp</span>(<span class="hljs-params">timestamp</span>) </span>{
    <span class="hljs-keyword">const</span> date = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(timestamp);
    <span class="hljs-keyword">return</span> date.toLocaleString();
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.messagesColumn}</span>&gt;</span>
      {messagesRecieved.map((msg, i) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.message}</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> '<span class="hljs-attr">flex</span>', <span class="hljs-attr">justifyContent:</span> '<span class="hljs-attr">space-between</span>' }}&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.msgMeta}</span>&gt;</span>{msg.username}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.msgMeta}</span>&gt;</span>
              {formatDateFromTimestamp(msg.__createdtime__)}
            <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.msgText}</span>&gt;</span>{msg.message}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Messages;
</code></pre>
<p>Above, we have a <em>useEffect</em> hook that runs whenever a socket event is received. We then get the message data passed into the _receive<em>message</em> event listener. From there, we set the <em>messagesReceived</em> state, which is an array of message objects containing the message, username of the sender, and the date the message was sent.</p>
<p>Let's import our new messages component into the Chat page, and then create a route for the Chat page in App.js:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/pages/chat/index.js</span>

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles.module.css'</span>;
<span class="hljs-keyword">import</span> MessagesReceived <span class="hljs-keyword">from</span> <span class="hljs-string">'./messages'</span>;

<span class="hljs-keyword">const</span> Chat = <span class="hljs-function">(<span class="hljs-params">{ socket }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.chatContainer}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">MessagesReceived</span> <span class="hljs-attr">socket</span>=<span class="hljs-string">{socket}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Chat;
</code></pre>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/App.js</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> Home <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/home'</span>;
<span class="hljs-keyword">import</span> Chat <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/chat'</span>;
<span class="hljs-keyword">import</span> { BrowserRouter <span class="hljs-keyword">as</span> Router, Routes, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> io <span class="hljs-keyword">from</span> <span class="hljs-string">'socket.io-client'</span>;

<span class="hljs-keyword">const</span> socket = io.connect(<span class="hljs-string">'http://localhost:4000'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [username, setUsername] = useState(<span class="hljs-string">''</span>);
  <span class="hljs-keyword">const</span> [room, setRoom] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Router</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'App'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span>
            <span class="hljs-attr">path</span>=<span class="hljs-string">'/'</span>
            <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>
              &lt;<span class="hljs-attr">Home</span>
                <span class="hljs-attr">username</span>=<span class="hljs-string">{username}</span>
                <span class="hljs-attr">setUsername</span>=<span class="hljs-string">{setUsername}</span>
                <span class="hljs-attr">room</span>=<span class="hljs-string">{room}</span>
                <span class="hljs-attr">setRoom</span>=<span class="hljs-string">{setRoom}</span>
                <span class="hljs-attr">socket</span>=<span class="hljs-string">{socket}</span>
              /&gt;</span>
            }
          /&gt;
          {/* Add this */}
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span>
            <span class="hljs-attr">path</span>=<span class="hljs-string">'/chat'</span>
            <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Chat</span> <span class="hljs-attr">username</span>=<span class="hljs-string">{username}</span> <span class="hljs-attr">room</span>=<span class="hljs-string">{room}</span> <span class="hljs-attr">socket</span>=<span class="hljs-string">{socket}</span> /&gt;</span>}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">Routes</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Router</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Let's test this out: go to the home page and join a room:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/joining-a-room.JPG" alt="Joining a room as Dan" width="600" height="400" loading="lazy"></p>
<p>We should be taken to the Chat page, and receive a welcome message from <em>ChatBot</em>: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/welcome-message.JPG" alt="Welcome message received from ChatBot" width="600" height="400" loading="lazy"></p>
<p>Users can now see the messages they receive. Awesome!</p>
<p>Next up: setting up our database so we can permanently save messages.</p>
<h2 id="heading-how-to-create-a-schema-and-table-in-harperdb">How to Create a Schema and Table in HarperDB</h2>
<p>Go back to your HarperDB dashboard, and click "browse". Then create a new schema called "realtime_chat_app". A schema is simply a group of tables.</p>
<p>Within that schema, create a table called "messages", with a hash attribute of "id".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-258.png" alt="Creating our schema and table in HarperDB" width="600" height="400" loading="lazy"></p>
<p>We now have somewhere to store messages, so let's create the SendMessage component.</p>
<h2 id="heading-how-to-create-the-send-message-component-c">How to Create the Send Message Component (C)</h2>
<p>Create the file src/pages/chat/send-message.js and add the following code:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// client/src/pages/chat/send-message.js</span>

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles.module.css'</span>;
<span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> SendMessage = <span class="hljs-function">(<span class="hljs-params">{ socket, username, room }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [message, setMessage] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> sendMessage = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (message !== <span class="hljs-string">''</span>) {
      <span class="hljs-keyword">const</span> __createdtime__ = <span class="hljs-built_in">Date</span>.now();
      <span class="hljs-comment">// Send message to server. We can't specify who we send the message to from the frontend. We can only send to server. Server can then send message to rest of users in room</span>
      socket.emit(<span class="hljs-string">'send_message'</span>, { username, room, message, __createdtime__ });
      setMessage(<span class="hljs-string">''</span>);
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.sendMessageContainer}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
        <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.messageInput}</span>
        <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Message...'</span>
        <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setMessage(e.target.value)}
        value={message}
      /&gt;
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'btn btn-primary'</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{sendMessage}</span>&gt;</span>
        Send Message
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SendMessage;
</code></pre>
<p>Above, when the user clicks the "Send Message" button, a send_message socket event is emitted to the server, along with a message object. We will handle this event on the server shortly.</p>
<p>Import <em>SendMessage</em> into our Chat page:</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/pages/chat/index.js</span>

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles.module.css'</span>;
<span class="hljs-keyword">import</span> MessagesReceived <span class="hljs-keyword">from</span> <span class="hljs-string">'./messages'</span>;
<span class="hljs-keyword">import</span> SendMessage <span class="hljs-keyword">from</span> <span class="hljs-string">'./send-message'</span>;

<span class="hljs-keyword">const</span> Chat = <span class="hljs-function">(<span class="hljs-params">{ username, room, socket }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.chatContainer}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">MessagesReceived</span> <span class="hljs-attr">socket</span>=<span class="hljs-string">{socket}</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">SendMessage</span> <span class="hljs-attr">socket</span>=<span class="hljs-string">{socket}</span> <span class="hljs-attr">username</span>=<span class="hljs-string">{username}</span> <span class="hljs-attr">room</span>=<span class="hljs-string">{room}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Chat;
</code></pre>
<p>The chat page now looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-259.png" alt="Chat page now has a message input where a message can be typed and sent" width="600" height="400" loading="lazy"></p>
<p>Next we need to set up our HarperDB environment variables so we can start interacting with the database.</p>
<h2 id="heading-how-to-set-up-harperdb-environment-variables">How to Set Up HarperDB Environment Variables</h2>
<p>In order for you to be able to save messages in HarperDB, you'll need your HarperDB instance URL, and your API password. </p>
<p>In your HarperDB dashboard, click on your instance, then go to "config". You will find your instance URL, and your instance API Auth Header – that is, your "super_user" password that allows you to make any request to the database – FOR YOUR EYES ONLY!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-263.png" alt="HarperDB instance URL and API auth header" width="600" height="400" loading="lazy"></p>
<p>We will store these variables in a .env file. <strong>Warning: don't push the .env file to GitHub!</strong> This file should not be publicly visible. The variables are loaded in via the server behind the scenes.</p>
<p>Create the following files and add your HarperDB URL and password:</p>
<pre><code class="lang-bash">// server/.env

HARPERDB_URL=<span class="hljs-string">"&lt;your url goes here&gt;"</span>
HARPERDB_PW=<span class="hljs-string">"Basic &lt;your password here&gt;"</span>
</code></pre>
<p>We'll also create a .gitignore file to prevent the .env from being pushed to GitHub, along with the node_modules folder:</p>
<pre><code class="lang-bash">// server/.gitignore

.env
node_modules
</code></pre>
<p>Note: being good with Git and GitHub is a 100% must for all developers. Check out my <a target="_blank" href="https://www.doabledanny.com/git-workflows">Git workflows article</a> if you need to up your Git game.</p>
<p>Or if you find yourself constantly having to look up the same Git commands, and want a quick way to look up, revise, and copy/paste commands -- check out my popular <a target="_blank" href="https://doabledanny.gumroad.com/l/git-commands-cheat-sheet-pdf">Git commands cheat sheet PDF</a> and <a target="_blank" href="https://doabledanny.gumroad.com/l/git-cheat-sheet-poster">physical Git cheat sheet poster</a>.</p>
<p>Finally, let's load our environment variables into our server by adding this code to the top of our main server file:</p>
<pre><code class="lang-js"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();
<span class="hljs-built_in">console</span>.log(process.env.HARPERDB_URL); <span class="hljs-comment">// remove this after you've confirmed it working</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-comment">// ...</span>
</code></pre>
<h2 id="heading-how-to-allow-users-to-send-messages-to-each-other-with-socketio">How to Allow Users to Send Messages to Each Other with Socket.io</h2>
<p>On the server, we'll listen for the _send<em>message</em> event, then send the message to all users within the room:</p>
<pre><code class="lang-js"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">const</span> harperSaveMessage = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./services/harper-save-message'</span>); <span class="hljs-comment">// Add this</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-comment">// Listen for when the client connects via socket.io-client</span>
io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {

  <span class="hljs-comment">// ...</span>

  <span class="hljs-comment">// Add this</span>
  socket.on(<span class="hljs-string">'send_message'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { message, username, room, __createdtime__ } = data;
    io.in(room).emit(<span class="hljs-string">'receive_message'</span>, data); <span class="hljs-comment">// Send to all users in room, including sender</span>
    harperSaveMessage(message, username, room, __createdtime__) <span class="hljs-comment">// Save message in db</span>
      .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(response))
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(err));
  });
});

server.listen(<span class="hljs-number">4000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">'Server is running on port 3000'</span>);
</code></pre>
<p>We now need to create the <em>harperSaveMessage</em> function. Create a new file at server/services/harper-save-message.js, and add the following:</p>
<pre><code class="lang-js"><span class="hljs-comment">// server/services/harper-save-message.js</span>

<span class="hljs-keyword">var</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">harperSaveMessage</span>(<span class="hljs-params">message, username, room</span>) </span>{
  <span class="hljs-keyword">const</span> dbUrl = process.env.HARPERDB_URL;
  <span class="hljs-keyword">const</span> dbPw = process.env.HARPERDB_PW;
  <span class="hljs-keyword">if</span> (!dbUrl || !dbPw) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;

  <span class="hljs-keyword">var</span> data = <span class="hljs-built_in">JSON</span>.stringify({
    <span class="hljs-attr">operation</span>: <span class="hljs-string">'insert'</span>,
    <span class="hljs-attr">schema</span>: <span class="hljs-string">'realtime_chat_app'</span>,
    <span class="hljs-attr">table</span>: <span class="hljs-string">'messages'</span>,
    <span class="hljs-attr">records</span>: [
      {
        message,
        username,
        room,
      },
    ],
  });

  <span class="hljs-keyword">var</span> config = {
    <span class="hljs-attr">method</span>: <span class="hljs-string">'post'</span>,
    <span class="hljs-attr">url</span>: dbUrl,
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
      <span class="hljs-attr">Authorization</span>: dbPw,
    },
    <span class="hljs-attr">data</span>: data,
  };

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    axios(config)
      .then(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">response</span>) </span>{
        resolve(<span class="hljs-built_in">JSON</span>.stringify(response.data));
      })
      .catch(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">error</span>) </span>{
        reject(error);
      });
  });
}

<span class="hljs-built_in">module</span>.exports = harperSaveMessage;
</code></pre>
<p>Above, saving the data may take a little time, so we are returning a promise which will be resolved if the data saves successfully, or rejected if not.</p>
<p>If you're wondering where I got the above code, HarperDB provides an awesome "<a target="_blank" href="https://studio.harperdb.io/resources/examples/QuickStart%20Examples/Create%20dev%20Schema">code examples</a>" section in their studio dashboard, which makes life much easier:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-265.png" alt="HarperDB code examples" width="600" height="400" loading="lazy"></p>
<p>Time to test! Join a room as a user, then send a message. Then go to HarperDB and click on "browse", then click on the "messages" table. You should see your message in the database:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-264.png" alt="Our first messages in the database" width="600" height="400" loading="lazy"></p>
<p>Cool 😎. So what next? Well, it'd be great if the last 100 messages sent in the room were loaded when a user joins a room, wouldn't it?</p>
<h2 id="heading-how-to-get-messages-from-harperdb">How to Get Messages from HarperDB</h2>
<p>On the server, let's create a function that fetches the last 100 messages sent in a particular room (notice how HarperDB also allows us to use SQL queries 👌):</p>
<pre><code class="lang-js"><span class="hljs-comment">// server/services/harper-get-messages.js</span>

<span class="hljs-keyword">let</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">harperGetMessages</span>(<span class="hljs-params">room</span>) </span>{
  <span class="hljs-keyword">const</span> dbUrl = process.env.HARPERDB_URL;
  <span class="hljs-keyword">const</span> dbPw = process.env.HARPERDB_PW;
  <span class="hljs-keyword">if</span> (!dbUrl || !dbPw) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;

  <span class="hljs-keyword">let</span> data = <span class="hljs-built_in">JSON</span>.stringify({
    <span class="hljs-attr">operation</span>: <span class="hljs-string">'sql'</span>,
    <span class="hljs-attr">sql</span>: <span class="hljs-string">`SELECT * FROM realtime_chat_app.messages WHERE room = '<span class="hljs-subst">${room}</span>' LIMIT 100`</span>,
  });

  <span class="hljs-keyword">let</span> config = {
    <span class="hljs-attr">method</span>: <span class="hljs-string">'post'</span>,
    <span class="hljs-attr">url</span>: dbUrl,
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
      <span class="hljs-attr">Authorization</span>: dbPw,
    },
    <span class="hljs-attr">data</span>: data,
  };

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    axios(config)
      .then(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">response</span>) </span>{
        resolve(<span class="hljs-built_in">JSON</span>.stringify(response.data));
      })
      .catch(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">error</span>) </span>{
        reject(error);
      });
  });
}

<span class="hljs-built_in">module</span>.exports = harperGetMessages;
</code></pre>
<p>We'll call this function whenever a user joins a room:</p>
<pre><code class="lang-js"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">const</span> harperSaveMessage = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./services/harper-save-message'</span>);
<span class="hljs-keyword">const</span> harperGetMessages = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./services/harper-get-messages'</span>); <span class="hljs-comment">// Add this</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-comment">// Listen for when the client connects via socket.io-client</span>
io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User connected <span class="hljs-subst">${socket.id}</span>`</span>);

  <span class="hljs-comment">// Add a user to a room</span>
  socket.on(<span class="hljs-string">'join_room'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {

    <span class="hljs-comment">// ...</span>

    <span class="hljs-comment">// Add this</span>
    <span class="hljs-comment">// Get last 100 messages sent in the chat room</span>
    harperGetMessages(room)
      .then(<span class="hljs-function">(<span class="hljs-params">last100Messages</span>) =&gt;</span> {
        <span class="hljs-comment">// console.log('latest messages', last100Messages);</span>
        socket.emit(<span class="hljs-string">'last_100_messages'</span>, last100Messages);
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(err));
  });

 <span class="hljs-comment">// ...</span>
</code></pre>
<p>Above, if the messages are fetched successfully, we emit a Socket.io event called _last_100<em>messages</em>. We'll now listen for this event on the frontend.</p>
<h2 id="heading-how-to-display-the-last-100-messages-on-the-client">How to Display the Last 100 Messages on the Client</h2>
<p>Below, we add a useEffect hook that contains a Socket.io event listener for the _last_100<em>messages</em> event<em>.</em> From there, the messages are sorted in date order, with most recent at the bottom, and the <em>messagesReceived</em> state is updated.</p>
<p>When <em>messagesReceived</em> is updated, a useEffect runs to scroll the <em>messageColumn</em> div to the most recent message. This improves the user experience of our app 👍.</p>
<pre><code class="lang-js"><span class="hljs-comment">// client/src/pages/chat/messages.js</span>

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles.module.css'</span>;
<span class="hljs-keyword">import</span> { useState, useEffect, useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> Messages = <span class="hljs-function">(<span class="hljs-params">{ socket }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [messagesRecieved, setMessagesReceived] = useState([]);

  <span class="hljs-keyword">const</span> messagesColumnRef = useRef(<span class="hljs-literal">null</span>); <span class="hljs-comment">// Add this</span>

  <span class="hljs-comment">// Runs whenever a socket event is recieved from the server</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    socket.on(<span class="hljs-string">'receive_message'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(data);
      setMessagesReceived(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> [
        ...state,
        {
          <span class="hljs-attr">message</span>: data.message,
          <span class="hljs-attr">username</span>: data.username,
          <span class="hljs-attr">__createdtime__</span>: data.__createdtime__,
        },
      ]);
    });

    <span class="hljs-comment">// Remove event listener on component unmount</span>
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> socket.off(<span class="hljs-string">'receive_message'</span>);
  }, [socket]);

  <span class="hljs-comment">// Add this</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Last 100 messages sent in the chat room (fetched from the db in backend)</span>
    socket.on(<span class="hljs-string">'last_100_messages'</span>, <span class="hljs-function">(<span class="hljs-params">last100Messages</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Last 100 messages:'</span>, <span class="hljs-built_in">JSON</span>.parse(last100Messages));
      last100Messages = <span class="hljs-built_in">JSON</span>.parse(last100Messages);
      <span class="hljs-comment">// Sort these messages by __createdtime__</span>
      last100Messages = sortMessagesByDate(last100Messages);
      setMessagesReceived(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> [...last100Messages, ...state]);
    });

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> socket.off(<span class="hljs-string">'last_100_messages'</span>);
  }, [socket]);

  <span class="hljs-comment">// Add this</span>
  <span class="hljs-comment">// Scroll to the most recent message</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    messagesColumnRef.current.scrollTop =
      messagesColumnRef.current.scrollHeight;
  }, [messagesRecieved]);

  <span class="hljs-comment">// Add this</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sortMessagesByDate</span>(<span class="hljs-params">messages</span>) </span>{
    <span class="hljs-keyword">return</span> messages.sort(
      <span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> <span class="hljs-built_in">parseInt</span>(a.__createdtime__) - <span class="hljs-built_in">parseInt</span>(b.__createdtime__)
    );
  }

  <span class="hljs-comment">// dd/mm/yyyy, hh:mm:ss</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatDateFromTimestamp</span>(<span class="hljs-params">timestamp</span>) </span>{
    <span class="hljs-keyword">const</span> date = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(timestamp);
    <span class="hljs-keyword">return</span> date.toLocaleString();
  }

  <span class="hljs-keyword">return</span> (
    <span class="hljs-comment">// Add ref to this div</span>
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.messagesColumn}</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{messagesColumnRef}</span>&gt;</span>
      {messagesRecieved.map((msg, i) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.message}</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> '<span class="hljs-attr">flex</span>', <span class="hljs-attr">justifyContent:</span> '<span class="hljs-attr">space-between</span>' }}&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.msgMeta}</span>&gt;</span>{msg.username}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.msgMeta}</span>&gt;</span>
              {formatDateFromTimestamp(msg.__createdtime__)}
            <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.msgText}</span>&gt;</span>{msg.message}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Messages;
</code></pre>
<h2 id="heading-how-to-display-the-room-and-users-a">How to Display the Room and Users (A)</h2>
<p>We've made components B and C, so let's finish things off by making A.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-248.png" alt="The chat page split into three components" width="600" height="400" loading="lazy"></p>
<p>On the server, when a user joins a room, we emit a _chatroom<em>users</em> event that sends all of the users in the room to all clients in that room. Let's listen for that event in a component called <em>RoomAndUsers.</em></p>
<p>Below, there's also a "Leave" button that, when pressed, causes the emission of a _leave<em>room</em> event to the server. It then redirects the user back to the Home page.</p>
<pre><code class="lang-js"><span class="hljs-comment">// client/src/pages/chat/room-and-users.js</span>

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles.module.css'</span>;
<span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">const</span> RoomAndUsers = <span class="hljs-function">(<span class="hljs-params">{ socket, username, room }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [roomUsers, setRoomUsers] = useState([]);

  <span class="hljs-keyword">const</span> navigate = useNavigate();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    socket.on(<span class="hljs-string">'chatroom_users'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(data);
      setRoomUsers(data);
    });

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> socket.off(<span class="hljs-string">'chatroom_users'</span>);
  }, [socket]);

  <span class="hljs-keyword">const</span> leaveRoom = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> __createdtime__ = <span class="hljs-built_in">Date</span>.now();
    socket.emit(<span class="hljs-string">'leave_room'</span>, { username, room, __createdtime__ });
    <span class="hljs-comment">// Redirect to home page</span>
    navigate(<span class="hljs-string">'/'</span>, { <span class="hljs-attr">replace</span>: <span class="hljs-literal">true</span> });
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.roomAndUsersColumn}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.roomTitle}</span>&gt;</span>{room}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        {roomUsers.length &gt; 0 &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">h5</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.usersTitle}</span>&gt;</span>Users:<span class="hljs-tag">&lt;/<span class="hljs-name">h5</span>&gt;</span>}
        <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.usersList}</span>&gt;</span>
          {roomUsers.map((user) =&gt; (
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>
              <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
                <span class="hljs-attr">fontWeight:</span> `${<span class="hljs-attr">user.username</span> === <span class="hljs-string">username</span> ? '<span class="hljs-attr">bold</span>' <span class="hljs-attr">:</span> '<span class="hljs-attr">normal</span>'}`,
              }}
              <span class="hljs-attr">key</span>=<span class="hljs-string">{user.id}</span>
            &gt;</span>
              {user.username}
            <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
          ))}
        <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'btn btn-outline'</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{leaveRoom}</span>&gt;</span>
        Leave
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> RoomAndUsers;
</code></pre>
<p>Let's import this component into the Chat page:</p>
<pre><code class="lang-js"><span class="hljs-comment">// client/src/pages/chat/index.js</span>

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles.module.css'</span>;
<span class="hljs-keyword">import</span> RoomAndUsersColumn <span class="hljs-keyword">from</span> <span class="hljs-string">'./room-and-users'</span>; <span class="hljs-comment">// Add this</span>
<span class="hljs-keyword">import</span> SendMessage <span class="hljs-keyword">from</span> <span class="hljs-string">'./send-message'</span>;
<span class="hljs-keyword">import</span> MessagesReceived <span class="hljs-keyword">from</span> <span class="hljs-string">'./messages'</span>;

<span class="hljs-keyword">const</span> Chat = <span class="hljs-function">(<span class="hljs-params">{ username, room, socket }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.chatContainer}</span>&gt;</span>
      {/* Add this */}
      <span class="hljs-tag">&lt;<span class="hljs-name">RoomAndUsersColumn</span> <span class="hljs-attr">socket</span>=<span class="hljs-string">{socket}</span> <span class="hljs-attr">username</span>=<span class="hljs-string">{username}</span> <span class="hljs-attr">room</span>=<span class="hljs-string">{room}</span> /&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">MessagesReceived</span> <span class="hljs-attr">socket</span>=<span class="hljs-string">{socket}</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">SendMessage</span> <span class="hljs-attr">socket</span>=<span class="hljs-string">{socket}</span> <span class="hljs-attr">username</span>=<span class="hljs-string">{username}</span> <span class="hljs-attr">room</span>=<span class="hljs-string">{room}</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Chat;
</code></pre>
<h2 id="heading-how-to-remove-a-user-from-a-socketio-room">How to Remove a User from a Socket.io Room</h2>
<p>Socket.io provides a <em>leave()</em> method that you can use to remove a user from a Socket.io room. We are also keeping track of our users in an array on server memory, so we'll remove the user from this array too:</p>
<pre><code class="lang-js"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-keyword">const</span> leaveRoom = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./utils/leave-room'</span>); <span class="hljs-comment">// Add this</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-comment">// Listen for when the client connects via socket.io-client</span>
io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {

  <span class="hljs-comment">// ...</span>

  <span class="hljs-comment">// Add this</span>
  socket.on(<span class="hljs-string">'leave_room'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { username, room } = data;
    socket.leave(room);
    <span class="hljs-keyword">const</span> __createdtime__ = <span class="hljs-built_in">Date</span>.now();
    <span class="hljs-comment">// Remove user from memory</span>
    allUsers = leaveRoom(socket.id, allUsers);
    socket.to(room).emit(<span class="hljs-string">'chatroom_users'</span>, allUsers);
    socket.to(room).emit(<span class="hljs-string">'receive_message'</span>, {
      <span class="hljs-attr">username</span>: CHAT_BOT,
      <span class="hljs-attr">message</span>: <span class="hljs-string">`<span class="hljs-subst">${username}</span> has left the chat`</span>,
      __createdtime__,
    });
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${username}</span> has left the chat`</span>);
  });
});

server.listen(<span class="hljs-number">4000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">'Server is running on port 3000'</span>);
</code></pre>
<p>We now need to create the <em>leaveRoom()</em> function:</p>
<pre><code class="lang-js"><span class="hljs-comment">// server/utils/leave-room.js</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">leaveRoom</span>(<span class="hljs-params">userID, chatRoomUsers</span>) </span>{
  <span class="hljs-keyword">return</span> chatRoomUsers.filter(<span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> user.id != userID);
}

<span class="hljs-built_in">module</span>.exports = leaveRoom;
</code></pre>
<p>Why put this short function in a separate utils folder, you ask? Because we'll be using it again later on and we don't want to repeat ourselves (keeping our code <a target="_blank" href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>).</p>
<p>Let's test things out: open up two windows side-by-side, and join the chat on both:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-266.png" alt="Two windows chatting in realtime." width="600" height="400" loading="lazy"></p>
<p>Then click the leave button on window 2:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-267.png" alt="The user is removed from the chat when they click the Leave button" width="600" height="400" loading="lazy"></p>
<p>The user is removed from the chat, and a message is sent to the other users – notifying them that they've left. Nice!</p>
<h2 id="heading-how-to-add-the-socketio-disconnect-event-listener">How to Add the Socket.io Disconnect Event Listener</h2>
<p>What if the user is somehow disconnected from the server, like if their internet drops? Socket.io provides a built-in <em>disconnect</em> event listener for this. Let's add that into our server to remove a user from memory when they disconnect:</p>
<pre><code class="lang-js"><span class="hljs-comment">// server/index.js</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-comment">// Listen for when the client connects via socket.io-client</span>
io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {

  <span class="hljs-comment">// ...</span>

  <span class="hljs-comment">// Add this</span>
  socket.on(<span class="hljs-string">'disconnect'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'User disconnected from the chat'</span>);
    <span class="hljs-keyword">const</span> user = allUsers.find(<span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> user.id == socket.id);
    <span class="hljs-keyword">if</span> (user?.username) {
      allUsers = leaveRoom(socket.id, allUsers);
      socket.to(chatRoom).emit(<span class="hljs-string">'chatroom_users'</span>, allUsers);
      socket.to(chatRoom).emit(<span class="hljs-string">'receive_message'</span>, {
        <span class="hljs-attr">message</span>: <span class="hljs-string">`<span class="hljs-subst">${user.username}</span> has disconnected from the chat.`</span>,
      });
    }
  });
});

server.listen(<span class="hljs-number">4000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-string">'Server is running on port 3000'</span>);
</code></pre>
<p>And there you have it – you've just built a fullstack realtime chat application with a React frontend, a Node/Express backend, and a HarperDB database. Nice job!</p>
<p>Next time, I plan to check out HarperDB’s <a target="_blank" href="https://harperdb.io/docs/custom-functions/">Custom Functions</a>, which enable users to define their own API endpoints within HarperDB. This means that we can build our entire application in one place! See an example of how HarperDB is collapsing the stack <a target="_blank" href="https://harperdb.io/blog/mean-stack-alternative/">in this article</a>.</p>
<h2 id="heading-a-challenge-for-you">A challenge for you 💪</h2>
<p>If you refresh the Chat page, the user's username and room will be lost. See if you can prevent this info from being lost when the user refreshes the page. Clue: <a target="_blank" href="https://www.w3schools.com/html/html5_webstorage.asp">local storage</a> could be useful!</p>
<h2 id="heading-thank-you-for-reading"><strong>Thank you for reading!</strong></h2>
<p>If you found this article useful, you can:</p>
<ul>
<li><a target="_blank" href="https://www.youtube.com/channel/UC0URylW_U4i26wN231yRqvA">Subscribe to my YouTube channel</a>. I will be uploading in-depth tutorials and project videos on React/NextJS/Node/Express.</li>
<li><a target="_blank" href="https://twitter.com/doabledanny">Follow me on Twitter</a> where I tweet about my freelancing journey, side projects, and current learnings.</li>
<li><a target="_blank" href="https://doabledanny.gumroad.com/">Checkout my Gumroad store</a> where I make useful and popular (8000 downloads as of writing) cheatsheets and posters.</li>
<li><a target="_blank" href="https://www.doabledanny.com/blog/">Checkout my web dev blog</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use GitHub Discussions as Your Blog's Chat System ]]>
                </title>
                <description>
                    <![CDATA[ By Rakesh Potnuru If you have a personal blog, you might be using a comment system to manage your blog's discussions and comments. If so, it's time to think about updating your comment system.  You can take your blog's comments to the next level with... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/github-discussions-as-chat-system/</link>
                <guid isPermaLink="false">66d460c78812486a37369d42</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 13 May 2022 15:48:14 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/05/GitHub-Discussions-as-chat-system-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Rakesh Potnuru</p>
<p>If you have a personal blog, you might be using a comment system to manage your blog's discussions and comments. If so, it's time to think about updating your comment system. </p>
<p>You can take your blog's comments to the next level with the help of GitHub Discussions. In this article, I am going to explain how to integrate GitHub discussions into your blog and make most out of it.</p>
<h2 id="heading-what-is-github-discussions">What is "GitHub Discussions"?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/screely-1652334880222-1.png" alt="GitHub discussions page" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://github.com/facebook/create-react-app/discussions">GitHub Discussions</a></em></p>
<p><a target="_blank" href="https://docs.github.com/en/discussions">GitHub discussions</a> is a forum that can be enabled on every GitHub repository. It makes it easy for developers to discuss new features, get feedback from the community, create polls, make announcements, and more. </p>
<p>GitHub Discussions is a one stop collaboration place for developers and community members. </p>
<h2 id="heading-how-to-use-github-discussions-as-chat-system">How to Use GitHub Discussions as Chat System</h2>
<p>To integrate GitHub Discussions into your blog, we are going to use <strong><a target="_blank" href="https://giscus.app/">giscus</a></strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/screely-1652335682833.png" alt="giscus website" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://giscus.app/">giscus</a></em></p>
<p>giscus is a GitHub Discussions-powered comment system. It lets you integrate discussions from your repository into your blog. </p>
<p>Your readers can leave comments on your blog, which will appear on both the blog and the discussions page of your repo.</p>
<h3 id="heading-advantages-of-using-discussions-as-your-blog-chat-system">Advantages of using Discussions as your blog chat system</h3>
<ul>
<li>It's completely free</li>
<li>There are no ads or tracking</li>
<li>It's super powerful</li>
<li>You have complete control over comments and full moderation authority.</li>
<li>There are lots of themes</li>
<li>It's quite customizable</li>
<li>You can self-host on your own servers</li>
</ul>
<p>Just keep in mind that this tool is mainly suited for dev blogs because mostly developers use GitHub.</p>
<h2 id="heading-how-to-integrate-giscus-in-your-blog">How to Integrate giscus in Your Blog</h2>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li>A blog (you must have access to the source code)</li>
<li>A <a target="_blank" href="https://github.com/">GitHub account</a></li>
<li>Your selected repository must be public</li>
</ul>
<p>First, you'll need to enable discussions for your repo.</p>
<p>Go to the repo <strong>Settings</strong> -&gt; Under the <strong>Features</strong> section -&gt; Check the Discussions box.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-123606.png" alt="Discussions enabled" width="600" height="400" loading="lazy">
<em>Discussions enabled</em></p>
<p>Next, install the giscus app in your repo.</p>
<p>Go to <a target="_blank" href="https://github.com/apps/giscus">https://github.com/apps/giscus</a>, follow the prompts, and give access to only the selected repo.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-124524.png" alt="selecting repository access" width="600" height="400" loading="lazy">
<em>Repository access</em></p>
<p>Now the important part: we need to configure the giscus widget.</p>
<p>First, go to giscus <a target="_blank" href="https://giscus.app/">homepage</a> and scroll to <strong>Configuration</strong> section.</p>
<p>Select your widget language. This is the language in which you want to display your widget.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-125354.png" alt="Selecting language" width="600" height="400" loading="lazy">
<em>Selecting language</em></p>
<p>Then type your <strong>repo name</strong> along with your <strong>username</strong> like <code>username/reponame</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-125933.png" alt="Typing repository name along with username" width="600" height="400" loading="lazy">
<em>Typing repository name along with username</em></p>
<p>For <strong>Page ↔️ Discussions Mapping</strong>, I would recommended choosing "Discussion title contains page <code>URL</code>". But depending upon your needs choose the one that best suits you.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-130653.png" alt="Page ↔️ Discussions Mapping" width="600" height="400" loading="lazy">
<em>Page ↔️ Discussions Mapping</em></p>
<p>Next, create a category in your discussions page on your GitHub repo – something like "Comments" – or choose existing category.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-131045.png" alt="Creating a discussion category" width="600" height="400" loading="lazy">
<em>Creating a discussion category</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot-2022-05-12-131542.png" alt="Choosing category" width="600" height="400" loading="lazy">
<em>Choosing a category</em></p>
<p>Then enable the optional <strong>Features</strong> if you want.</p>
<p>Next, select the theme. And don't worry – you can programmatically toggle different themes.</p>
<p>Finally, copy and paste in the generated code.</p>
<p>Giscus will generate a script tag based on your settings, which you can paste into your code. But we will see how to use the giscus component.</p>
<h3 id="heading-how-to-use-giscus-component">How to use giscus-component</h3>
<p>If your blog is built with React/Vue/Svelte or Web components then you can install the giscus component.</p>
<p>For example, to integrate giscus in React, do the following:</p>
<ul>
<li>Install the giscus package.</li>
</ul>
<pre><code class="lang-bash">npm i @giscus/react

or

yarn add <span class="hljs-variable">$giscus</span>/react
</code></pre>
<ul>
<li>Then import <code>giscus</code> in your component and use it. Copy the attributes which we got in the last step, remove <code>data-</code> from all the attributes, and covert attributes into valid <code>jsx</code> attributes.</li>
</ul>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> Giscus <span class="hljs-keyword">from</span> <span class="hljs-string">'@giscus/react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Giscus</span>
      <span class="hljs-attr">id</span>=<span class="hljs-string">"comments"</span>
      <span class="hljs-attr">repo</span>=<span class="hljs-string">"giscus/giscus-component"</span>
      <span class="hljs-attr">repoId</span>=<span class="hljs-string">"MDEwOlJlcG9zaXRvcnkzOTEzMTMwMjA="</span>
      <span class="hljs-attr">category</span>=<span class="hljs-string">"Announcements"</span>
      <span class="hljs-attr">categoryId</span>=<span class="hljs-string">"DIC_kwDOF1L2fM4B-hVS"</span>
      <span class="hljs-attr">mapping</span>=<span class="hljs-string">"specific"</span>
      <span class="hljs-attr">term</span>=<span class="hljs-string">"Welcome to @giscus/react component!"</span>
      <span class="hljs-attr">reactionsEnabled</span>=<span class="hljs-string">"1"</span>
      <span class="hljs-attr">emitMetadata</span>=<span class="hljs-string">"0"</span>
      <span class="hljs-attr">inputPosition</span>=<span class="hljs-string">"top"</span>
      <span class="hljs-attr">theme</span>=<span class="hljs-string">"light"</span>
      <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>
      <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span>
    /&gt;</span></span>
  );
}
</code></pre>
<p>It's basically the same procedure for other frameworks.</p>
<h2 id="heading-heres-the-final-result">Here's the Final Result</h2>
<p>This is how the whole thing works:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/ezgif-4-4662469443-2.gif" alt="Final result" width="600" height="400" loading="lazy">
<em>Final result</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we learnt the benefits of using GitHub discussions as a chat system. We also learned how to create and integrate the giscus widget in our website.</p>
<p>I hope you found this helpful. Make sure to ⭐ the giscus <a target="_blank" href="https://github.com/giscus">GitHub repo</a> to support them.</p>
<p>Follow me on <a target="_blank" href="https://twitter.com/rakesh_at_tweet">Twitter</a> where I share random tips, resources, and my learnings around web development and technical writing.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ RTC Connecting Discord – How to Fix the Server Error ]]>
                </title>
                <description>
                    <![CDATA[ Discord is an instant messaging app that lets you communicate through voice, video, and texts. It is available in a web-based form, a desktop app, and a mobile app. But sometimes, when you're trying to establish a voice call connection, you'll get an... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/rtc-connecting-discord-how-to-fix-the-server-error/</link>
                <guid isPermaLink="false">66adf20af452caf50fb1fe1f</guid>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ discord ]]>
                    </category>
                
                    <category>
                        <![CDATA[ messaging ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Thu, 02 Dec 2021 17:10:47 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/12/Discord.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Discord is an instant messaging app that lets you communicate through voice, video, and texts. It is available in a web-based form, a desktop app, and a mobile app.</p>
<p>But sometimes, when you're trying to establish a voice call connection, you'll get an error that says “RTC Connecting Discord”. This message will keep showing without any meaningful progress.</p>
<p>So what does this error mean?</p>
<p>Discord uses the Real-time Chat protocol (RTC) to run concurrent communication. So if you are experiencing the “RTC Connecting Discord” problem, it is a network issue.</p>
<p>In this article, I will show you 5 changes you can make to your network configurations to fix the RTC Connecting Discord issue. 3 of the fixes involve your computer network settings, while the remaining 2 happen right in your Discord app. </p>
<h2 id="heading-how-to-fix-rtc-connecting-discord-by-updating-your-network-driver">How to Fix RTC Connecting Discord by Updating your Network Driver</h2>
<p>If your device relies on an outdated network adapter driver for internet connections, it could have a negative effect on your internet experience – and could cause this issue as well. </p>
<p>So, updating your network driver can fix the issue.</p>
<p><strong>The steps below take you through how you can update your network adapter driver.</strong></p>
<p><strong>Step 1</strong>: Click on Start (Windows logo) and search for "device manager". Hit <code>ENTER</code> to open the first search result – which is always Device Manager. You can click on the "Device Manager" search result, too.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-1.jpg" alt="ss-1" width="600" height="400" loading="lazy"></p>
<p><strong>Step 2</strong>: Expand "Network Adapters."</p>
<p><strong>Step 3</strong>: Look for the adapter in use, right-click on it and select "update driver".
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-2.jpg" alt="ss-2" width="600" height="400" loading="lazy"></p>
<p><strong>Step 4</strong>: Select "Search automatically for drivers".
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-3.jpg" alt="ss-3" width="600" height="400" loading="lazy"></p>
<p>Windows will now search the internet for an updated driver and install it for you.</p>
<h2 id="heading-how-to-fix-rtc-connecting-discord-by-changing-your-domain-name-server">How to Fix RTC Connecting Discord by Changing your Domain Name Server</h2>
<p>A domain name server (DNS) is assigned to you by your ISP (Internet Service Provider). Domain name servers make it possible to reach websites by typing addresses (URLs) to the browser instead of some unreadable numbers.</p>
<p>Changing this DNS to a widely used DNS like that of Google or Cloudflare can help you fix the RTC Connecting Discord issue.</p>
<p><strong>To change your DNS to Google’s, follow the steps below.</strong></p>
<p><strong>Step 1:</strong> Right-click on Start and choose “Run” to open the Run dialogue.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-4.jpg" alt="ss-4" width="600" height="400" loading="lazy"></p>
<p><strong>Step 2</strong>: Type in <code>“Control ncpa.cpl”</code> (without quotes) and hit <code>ENTER</code>. This will open up your network connection devices.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-5-2.png" alt="ss-5-2" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: Right-click on your current network and select “Properties”.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-6-1.jpg" alt="ss-6-1" width="600" height="400" loading="lazy"></p>
<p><strong>Step 4</strong>: Look for Internet Protocol Version 4 (TCP/IPv4) and double-click it.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-7.jpg" alt="ss-7" width="600" height="400" loading="lazy"></p>
<p><strong>Step 5</strong>: Click on the “Use the following DNS server address” radio button and type in the following values:</p>
<ul>
<li>8.8.8.8 for Preferred DNS Server</li>
<li>8.8.4.4 for Alternate DNS Server</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-8.jpg" alt="ss-8" width="600" height="400" loading="lazy"></p>
<p><strong>Step 6</strong>: Click Ok.</p>
<p>Complete the setup with the next fix.</p>
<h2 id="heading-how-to-fix-rtc-connecting-discord-by-clearing-your-computer-network-cache-in-the-command-line">How to Fix RTC Connecting Discord by Clearing your Computer Network Cache in the Command Line</h2>
<p>If you’re using the web-based Discord, this fix can work for you.</p>
<p>You can clear your network cache on your browser, but a more effective way to do it is to clear it right on your Windows 10 computer in the command line.</p>
<p>The steps below show you how to do it.</p>
<p><strong>Step 1</strong>: Hit the <code>WIN</code> (Windows logo) key on your keyboard and search for "cmd". </p>
<p>You have to use the Command Prompt as an administrator, so you should select "Run as Administrator" on the right instead of just hitting <code>ENTER</code> to open it.</p>
<p><strong>Step 2</strong>: Enter and execute the following commands one after the other:</p>
<ul>
<li><code>ipconfig /release</code></li>
<li><code>ipconfig /flushdns</code></li>
<li><code>ipconfig /renew</code></li>
</ul>
<p><strong>Step 3</strong>: Restart your computer.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-9.png" alt="ss-9" width="600" height="400" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-10.png" alt="ss-10" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-fix-rtc-connecting-discord-by-disabling-qos">How to Fix RTC Connecting Discord by Disabling QoS</h2>
<p>Discord’s QoS (Quality of Service) communicates to your router that the units of data being transmitted are of high priority. This could make your router misbehave, and cause the RTC Connecting Discord issue. </p>
<p>So, disabling the QoS might fix it for you in case you have it enabled.</p>
<p><strong>Follow the steps below to disable QoS on Discord</strong>.</p>
<p><strong>Step 1</strong>: Launch Discord, then click on Settings on the bottom left corner. 
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-11.jpg" alt="ss-11" width="600" height="400" loading="lazy"></p>
<p><strong>Step 2</strong>: Select Voice and Video on the left panel.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-12.jpg" alt="ss-12" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: Scroll down to the QoS section and disable it.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-13.jpg" alt="ss-13" width="600" height="400" loading="lazy"></p>
<p><strong>Step 4</strong>: Restart the Discord app.</p>
<h2 id="heading-how-to-fix-rtc-connecting-discord-by-changing-the-audio-subsystem-in-discord">How to Fix RTC Connecting Discord by Changing the Audio Subsystem in Discord</h2>
<p>In Discord, the Legacy audio subsystem has always been suggested as the best because it is the highest quality compared to the Standard and Experimental audio subsystems.</p>
<p>Changing your Audio subsystem to Legacy can make you establish quality audio – which can end up fixing the RTC Connecting Discord issue.</p>
<p><strong>These steps take you through how to change your Audio subsystem to Legacy</strong>.</p>
<p><strong>Step 1</strong>: Open Discord and click on Settings on the bottom left corner.<br><img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-11.jpg" alt="ss-11" width="600" height="400" loading="lazy"></p>
<p><strong>Step 2</strong>: Select Voice and Video.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-12.jpg" alt="ss-12" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: Scroll down to the Audio Subsystem dropdown and select “Legacy”.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/ss-14.jpg" alt="ss-14" width="600" height="400" loading="lazy"></p>
<p><strong>Step 4</strong>: Restart Discord. </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This article showed you how you can fix the RTC Connecting Discord problem you might experience when you are trying to use Discord's audio call feature.</p>
<p>Apart from the solutions explained in this article, you can try other minor fixes such as: </p>
<ul>
<li>Restarting your devices – computer and router</li>
<li>Double-checking internet connection</li>
<li>Using a VPN</li>
</ul>
<p>Thanks a lot for reading this article. If you find it helpful, share it with your friends and loved ones.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Discord Won't Open on my PC [Solved in Windows 10] ]]>
                </title>
                <description>
                    <![CDATA[ Discord is an instant messaging app you can use to communicate through text messages, voice calls, and video calls. You can also use it to share files.  Discord was originally created for gamers, but many other people now use it these days. It has be... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/discord-wont-open-on-my-pc-solved-in-windows-10/</link>
                <guid isPermaLink="false">66adf0b6febac312b73075a8</guid>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ discord ]]>
                    </category>
                
                    <category>
                        <![CDATA[ how-to ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Problem Solving ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Tue, 09 Nov 2021 14:57:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/11/Discord.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Discord is an instant messaging app you can use to communicate through text messages, voice calls, and video calls. You can also use it to share files. </p>
<p>Discord was originally created for gamers, but many other people now use it these days. It has become an alternative to Slack for many users – especially for those who want to have an online community.</p>
<p>But sometimes, Discord might not open when you launch it. This could be due to pending updates, running games, and other causes.</p>
<p>In this article, I will show you 5 quick ways you can make Discord open again on a Windows 10 computer.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-how-to-fix-discord-not-opening-in-the-command-line">How to Fix Discord Not Opening in the Command Line</a></li>
<li><a class="post-section-overview" href="#heading-how-to-fix-discord-not-opening-by-clearing-appdata">How to Fix Discord Not Opening by Clearing AppData</a></li>
<li><a class="post-section-overview" href="#heading-how-to-fix-discord-not-opening-by-clearing-localappdata">How to Fix Discord Not Opening by Clearing LocalAppData</a></li>
<li><a class="post-section-overview" href="#heading-how-to-fix-discord-not-opening-by-closing-background-applications">How to Fix Discord Not Opening by Closing Background Applications</a></li>
<li><a class="post-section-overview" href="#heading-how-to-fix-discord-not-opening-with-the-task-manager">How to Fix Discord Not Opening with the Task Manager</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-how-to-fix-discord-not-opening-in-the-command-line">How to Fix Discord Not Opening in the Command Line</h2>
<p>The number one fix I would recommend to make Discord open again is to kill the Discord task with the Command Prompt.</p>
<p>This is how to do it:</p>
<p><strong>Step 1</strong>: Click on Start or press <code>WIN</code> (Windows key) on your keyboard, then search for "cmd".</p>
<p><strong>Step 2</strong>: Hit <code>ENTER</code> or select the first search result to open the Command Prompt.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-1-5.jpg" alt="ss-1-5" width="600" height="400" loading="lazy"></p>
<p><strong>Step</strong>: Type in <code>taskkill /F /IM discord.exe</code> and hit <code>ENTER</code>.</p>
<p>You should get a message that the Discord process has been terminated.
<img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-2-1.png" alt="ss-2-1" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-fix-discord-not-opening-by-clearing-appdata">How to Fix Discord Not Opening by Clearing AppData</h2>
<p>When you clear an app's AppData, all cache files are cleared – which can fix some problems, including loading.</p>
<p>Go through the following steps to clear your Discord's AppData:</p>
<p><strong>Step 1</strong>: Press <code>WIN</code> (Windows key) + R to open the Run dialogue.</p>
<p><strong>Step 2</strong>: Type in "%appdata%" (without quotes) and press <code>ENTER</code> on your keyboard. This will open up the AppData folder.
<img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-3-1.png" alt="ss-3-1" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: Locate the Discord folder and delete it. Delete it from your Recycle Bin too.
<img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-4-6.jpg" alt="ss-4-6" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-fix-discord-not-opening-by-clearing-localappdata">How to Fix Discord Not Opening by Clearing LocalAppData</h2>
<p>Clearing the Discord's LocalAppData can make it open again. It can also fix the common JavaScript error associated with Discord as well. </p>
<p>To clear LocalAppData, follow these steps:</p>
<p><strong>Step 1</strong>: Press <code>WIN</code> (Windows key) + R to open the Run dialogue.</p>
<p><strong>Step 2</strong>: Type in "%localappdata%" (without quotes) and press <code>ENTER</code> on your keyboard. This will open up the LocalAppData folder.
<img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-5.png" alt="ss-5" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: Look for the Discord folder and delete it. Go to your Recycle Bin and delete it too.
<img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-6-5.jpg" alt="ss-6-5" width="600" height="400" loading="lazy"></p>
<p>Note that you might have to reinstall Discord to get it running again after clearing its LocalAppData. I can attest that this solves the issue as I recently had to do it.</p>
<h2 id="heading-how-to-fix-discord-not-opening-by-closing-background-applications">How to Fix Discord Not Opening by Closing Background Applications</h2>
<p>A lot of games run in the background and this could have a negative effect on your Discord app.</p>
<p>Use the steps below to fix the issue only if you don't have useful apps running in the background.</p>
<p><strong>Step 1</strong>: Click on Start or press <code>WIN</code> (Windows key) on your keyboard and select Settings.
<img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-7-1.jpg" alt="ss-7-1" width="600" height="400" loading="lazy"></p>
<p><strong>Step 2</strong>: Select "Privacy".
<img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-8-2.jpg" alt="ss-8-2" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: Click on Background apps on the left, then turn off the toggle under "Let apps run in the background".
<img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-9-1.jpg" alt="ss-9-1" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-fix-discord-not-opening-with-the-task-manager">How to Fix Discord Not Opening with the Task Manager</h2>
<p>Stopping the Discord process with the Task Manager can make Discord open again as this refreshes the app.</p>
<p><strong>Step 1</strong>: Press <code>CTRL</code> + <code>SHIFT</code> + <code>ESC</code> to open the Task Manager</p>
<p><strong>Step 2</strong>: Make sure you are under the <code>Processes</code>. Right-click on Discord and select "End Task". 
<img src="https://www.freecodecamp.org/news/content/images/2021/11/ss-10-2.jpg" alt="ss-10-2" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you learned how to fix Discord when it won't open in a few different ways.</p>
<p>Apart from the methods discussed in this guide, you can also fix the issue by uninstalling and reinstalling Discord. </p>
<p>Note that you can also fix another popular issue with Discord – JavaScript error – by using one of the fixes suggested in this article – clearing LocalAppData. I've had to do this myself.</p>
<p>Thanks a lot for reading. If you find this article helpful, consider sharing it with your friends. That's very much appreciated.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Design an iMessage-like Chat Bubble in React Native ]]>
                </title>
                <description>
                    <![CDATA[ By Prajwal Kulkarni Whether you're an Apple fan or not, you'll likely agree that Apple sure does have a groundbreaking UI. And iMessage is definitely an important part of that design.  The curved arrow is something that I have always really liked and... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/design-imessage-like-chat-bubble-react-native/</link>
                <guid isPermaLink="false">66d4608be39d8b5612bc0deb</guid>
                
                    <category>
                        <![CDATA[ app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React Native ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 24 Mar 2021 18:50:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/03/feature_image_freecodecamp-3.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Prajwal Kulkarni</p>
<p>Whether you're an Apple fan or not, you'll likely agree that Apple sure does have a groundbreaking UI. And iMessage is definitely an important part of that design. </p>
<p>The curved arrow is something that I have always really liked and have wanted to replicate for a long time.</p>
<p>After a lot of trial and error, I was finally able to find a workaround to build a similar version of iMessage's chat bubble. In this article, I'll take you through the steps required to build a chat bubble that looks like Apple's iMessage.</p>
<p>If you're building a chat application or intending to display information in the form of a message, I'd definitely recommend that you try out this style as it makes your app look cool and professional.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>This article assumes that you know the basics of:</p>
<ul>
<li>JSX </li>
<li>React Native </li>
<li>HTML &amp; CSS</li>
</ul>
<h2 id="heading-what-is-a-chat-bubble">What is a Chat Bubble?</h2>
<p>A chat bubble is basically a container that holds text. Chat bubbles are mostly used in Instant Messaging apps to display chat logs effectively. </p>
<p>The conventional method is that sent messages are displayed on the right side of the screen and received messages are shown on the left, with different colors used to differentiate between sent and received message(s).</p>
<p>Most messaging apps have a basic chat bubble which is a regular container with rounded corners. The main differentiating factor between other apps and iMessage is the presence of a small curved-like arrow anchoring the text container, which looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/freecodecamp-1.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-build-a-chat-bubble-that-looks-like-imessage">How to Build a Chat Bubble that Looks like iMessage</h2>
<p>If we look at the above image carefully, we can see that the iMessage chat bubble is a combination of a regular chat bubble with an added arrow in the corner.</p>
<p>The main challenge lies in tying the arrow to the text container.</p>
<p>Before directly hopping into the code, I'd want you to know how this arrow-like element is created and added.</p>
<p>First, check out <a target="_blank" href="https://codepen.io/samuelkraft/pen/Farhl">this</a> code, which shows how to implementation this arrow using HTML &amp; CSS. Below is its code snippet.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">p</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">255px</span>;
  <span class="hljs-attribute">word-wrap</span>: break-word;
  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">12px</span>;
  <span class="hljs-attribute">line-height</span>: <span class="hljs-number">24px</span>;
  <span class="hljs-attribute">position</span>: relative;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">25px</span>;

  &amp;:before, &amp;:after {
    <span class="hljs-attribute">content</span>: <span class="hljs-string">""</span>;
        <span class="hljs-attribute">position</span>: absolute;
    <span class="hljs-attribute">bottom</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">25px</span>;
  }
}

<span class="hljs-selector-class">.from-me</span> {
    <span class="hljs-attribute">color</span>: white; 
    <span class="hljs-attribute">background</span>: <span class="hljs-number">#0B93F6</span>;
    <span class="hljs-attribute">align-self</span>: flex-end;

    &amp;:before {
        <span class="hljs-attribute">right</span>: -<span class="hljs-number">7px</span>;
    <span class="hljs-attribute">width</span>: <span class="hljs-number">20px</span>;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#0B93F6</span>;
        <span class="hljs-attribute">border-bottom-left-radius</span>: <span class="hljs-number">16px</span> <span class="hljs-number">14px</span>;
    }

    &amp;<span class="hljs-selector-pseudo">:after</span> {
        <span class="hljs-attribute">right</span>: -<span class="hljs-number">26px</span>;
    <span class="hljs-attribute">width</span>: <span class="hljs-number">26px</span>;
    <span class="hljs-attribute">background-color</span>: white;
        <span class="hljs-attribute">border-bottom-left-radius</span>: <span class="hljs-number">10px</span>;
    }
}
<span class="hljs-selector-class">.from-them</span> {
    <span class="hljs-attribute">background</span>: <span class="hljs-number">#E5E5EA</span>;
    <span class="hljs-attribute">color</span>: black;
  <span class="hljs-attribute">align-self</span>: flex-start;

    &amp;:before {
        <span class="hljs-attribute">left</span>: -<span class="hljs-number">7px</span>;
    <span class="hljs-attribute">width</span>: <span class="hljs-number">20px</span>;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#E5E5EA</span>;
        <span class="hljs-attribute">border-bottom-right-radius</span>: <span class="hljs-number">16px</span>;
    }

    &amp;<span class="hljs-selector-pseudo">:after</span> {
        <span class="hljs-attribute">left</span>: -<span class="hljs-number">26px</span>;
    <span class="hljs-attribute">width</span>: <span class="hljs-number">26px</span>;
    <span class="hljs-attribute">background-color</span>: white;
        <span class="hljs-attribute">border-bottom-right-radius</span>: <span class="hljs-number">10px</span>;
    }
}
</code></pre>
<p>If you just browse straight through the code it might seem quite awful. So let's break it down at an atomic level and later glue it all back together.</p>
<p>The <code>&lt;**p&gt;**</code> tag includes style constraints such as margin-bottom, position, padding and so on. Note that the max-width used here is 255px, which is a static value. But we'll be using a dynamic approach, as the chat bubbles need to be responsive across various screen sizes.</p>
<p>The <code>&amp;:before</code> and <code>&amp;:after</code> within the <strong><code>&lt;p&gt;</code></strong> style defines two elements with no content in it. They're positioned absolute with respect to the <strong><code>&lt;p&gt;</code></strong> tag (text container), and are placed at the bottom. They have a height of 25px (the height of the arrow).</p>
<p>Going further, the <code>.from-me</code> (sent messages) style defines that the text be white, background blue (#0b936f), and that it's placed at the right side of the screen (align-self: flex-end). </p>
<p>Now comes the important part – extending the <strong><code>&amp;:before</code></strong> and <strong><code>&amp;:after</code>,</strong> which is the actual implementation of the arrow.</p>
<p>The <strong><code>&amp;:before</code></strong> has a width of 20px and is placed at 7 pixels negative to the right. It has a border-bottom-left radius of 16px, which is what gives the curved look to the arrow.</p>
<p>Similarly, the <strong><code>&amp;:after</code></strong> has a width of 26px and is placed at 26 pixels negative to the right. Since -7px &gt; -26px, <code>&amp;:after</code> is placed right side of the <code>&amp;:before</code> element and partially overlaps it.</p>
<p>If you still feel confused, don't worry – just refer to the images below to gain a clearer insight into what I've been talking about.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/fcc1.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>&amp;:before with black background and bottom-left-radius</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/fcc2.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>&amp;:after overlapping &amp;:before with green background</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/fcc3.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>&amp;:after background changed to white to match the background of the chat screen.</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/fcc4.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>&amp;:before background updated to #0b93f6 to match chat bubble color.</em></p>
<p>So basically the arrowhead is created by overlapping two elements at the bottom corner of the chat bubble and adjusting the background colors to match those of the chat bubble and chat screen.</p>
<p>Further ahead, the translation of CSS and HTML into JSX is fairly simple, as most of the things are quite straightforward.</p>
<h2 id="heading-how-to-build-the-react-native-version">How to Build the React Native Version</h2>
<p>Before starting, I want to note that this works best with FlatList, and I recommend that you use it, and not other components or functions such as map (which lacked consistency across different screens and devices). </p>
<p>The three steps we're going to follow here are:</p>
<ol>
<li>Create chat bubble with arrow head</li>
<li>Add styles to chat bubble and arrow head</li>
<li>Embed chat bubble in FlatList</li>
</ol>
<p>So let's get started.</p>
<p>First, we'll create the chat bubble with the arrow head, like this:</p>
<pre><code>&lt;View style={{
                    <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">"#0078fe"</span>,
                    <span class="hljs-attr">padding</span>:<span class="hljs-number">10</span>,
                    <span class="hljs-attr">marginLeft</span>: <span class="hljs-string">'45%'</span>,
                    <span class="hljs-attr">borderRadius</span>: <span class="hljs-number">5</span>,
                    <span class="hljs-comment">//marginBottom: 15,</span>
                    <span class="hljs-attr">marginTop</span>: <span class="hljs-number">5</span>,
                    <span class="hljs-attr">marginRight</span>: <span class="hljs-string">"5%"</span>,
                    <span class="hljs-attr">maxWidth</span>: <span class="hljs-string">'50%'</span>,
                    <span class="hljs-attr">alignSelf</span>: <span class="hljs-string">'flex-end'</span>,
                    <span class="hljs-comment">//maxWidth: 500,</span>

                    <span class="hljs-attr">borderRadius</span>: <span class="hljs-number">20</span>,
                  }} key={index}&gt;


                    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Text</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">fontSize:</span> <span class="hljs-attr">16</span>, <span class="hljs-attr">color:</span> "#<span class="hljs-attr">fff</span>", }} <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span>{item.text}<span class="hljs-tag">&lt;/<span class="hljs-name">Text</span>&gt;</span></span>

                      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.rightArrow}</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span></span>

                      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.rightArrowOverlap}</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span></span>



&lt;/View&gt;


<span class="hljs-comment">//Recevied Message</span>
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
                    <span class="hljs-attr">backgroundColor:</span> "#<span class="hljs-attr">dedede</span>",
                    <span class="hljs-attr">padding:10</span>,
                    <span class="hljs-attr">borderRadius:</span> <span class="hljs-attr">5</span>,
                    <span class="hljs-attr">marginTop:</span> <span class="hljs-attr">5</span>,
                    <span class="hljs-attr">marginLeft:</span> "<span class="hljs-attr">5</span>%",
                    <span class="hljs-attr">maxWidth:</span> '<span class="hljs-attr">50</span>%',
                    <span class="hljs-attr">alignSelf:</span> '<span class="hljs-attr">flex-start</span>',
                    //<span class="hljs-attr">maxWidth:</span> <span class="hljs-attr">500</span>,
                    //<span class="hljs-attr">padding:</span> <span class="hljs-attr">14</span>,

                    //<span class="hljs-attr">alignItems:</span>"<span class="hljs-attr">center</span>",
                    <span class="hljs-attr">borderRadius:</span> <span class="hljs-attr">20</span>,
                  }} <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span>



                      <span class="hljs-tag">&lt;<span class="hljs-name">Text</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">fontSize:</span> <span class="hljs-attr">16</span>, <span class="hljs-attr">color:</span> "#<span class="hljs-attr">000</span>",<span class="hljs-attr">justifyContent:</span>"<span class="hljs-attr">center</span>" }} <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span> {item.text}<span class="hljs-tag">&lt;/<span class="hljs-name">Text</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.leftArrow}</span>&gt;</span>

                      <span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.leftArrowOverlap}</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span>



                    <span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span></span>
</code></pre><p>The Outermost <strong><code>&lt;View&gt;</code></strong> tag acts as the 'p' tag in comparison with the HTML version. The remaining two <strong><code>&lt;View&gt;</code></strong> tags act as <code>&amp;:before</code> and <code>&amp;:after</code>.</p>
<p>Next, we'll add styles to the chat bubble and arrow head like this:</p>
<pre><code><span class="hljs-keyword">const</span> styles = StyleSheet.create({
<span class="hljs-attr">rightArrow</span>: {
  <span class="hljs-attr">position</span>: <span class="hljs-string">"absolute"</span>,
  <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">"#0078fe"</span>,
  <span class="hljs-comment">//backgroundColor:"red",</span>
  <span class="hljs-attr">width</span>: <span class="hljs-number">20</span>,
  <span class="hljs-attr">height</span>: <span class="hljs-number">25</span>,
  <span class="hljs-attr">bottom</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">borderBottomLeftRadius</span>: <span class="hljs-number">25</span>,
  <span class="hljs-attr">right</span>: <span class="hljs-number">-10</span>
},

<span class="hljs-attr">rightArrowOverlap</span>: {
  <span class="hljs-attr">position</span>: <span class="hljs-string">"absolute"</span>,
  <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">"#eeeeee"</span>,
  <span class="hljs-comment">//backgroundColor:"green",</span>
  <span class="hljs-attr">width</span>: <span class="hljs-number">20</span>,
  <span class="hljs-attr">height</span>: <span class="hljs-number">35</span>,
  <span class="hljs-attr">bottom</span>: <span class="hljs-number">-6</span>,
  <span class="hljs-attr">borderBottomLeftRadius</span>: <span class="hljs-number">18</span>,
  <span class="hljs-attr">right</span>: <span class="hljs-number">-20</span>

},

<span class="hljs-comment">/*Arrow head for recevied messages*/</span>
<span class="hljs-attr">leftArrow</span>: {
    <span class="hljs-attr">position</span>: <span class="hljs-string">"absolute"</span>,
    <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">"#dedede"</span>,
    <span class="hljs-comment">//backgroundColor:"red",</span>
    <span class="hljs-attr">width</span>: <span class="hljs-number">20</span>,
    <span class="hljs-attr">height</span>: <span class="hljs-number">25</span>,
    <span class="hljs-attr">bottom</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">borderBottomRightRadius</span>: <span class="hljs-number">25</span>,
    <span class="hljs-attr">left</span>: <span class="hljs-number">-10</span>
},

<span class="hljs-attr">leftArrowOverlap</span>: {
    <span class="hljs-attr">position</span>: <span class="hljs-string">"absolute"</span>,
    <span class="hljs-attr">backgroundColor</span>: <span class="hljs-string">"#eeeeee"</span>,
    <span class="hljs-comment">//backgroundColor:"green",</span>
    <span class="hljs-attr">width</span>: <span class="hljs-number">20</span>,
    <span class="hljs-attr">height</span>: <span class="hljs-number">35</span>,
    <span class="hljs-attr">bottom</span>: <span class="hljs-number">-6</span>,
    <span class="hljs-attr">borderBottomRightRadius</span>: <span class="hljs-number">18</span>,
    <span class="hljs-attr">left</span>: <span class="hljs-number">-20</span>

},
})
</code></pre><p>Then we'll embed it into FlatList:</p>
<pre><code>&lt;FlatList
        <span class="hljs-comment">//inverted</span>
        style={{<span class="hljs-attr">backgroundColor</span>:<span class="hljs-string">"#eeeeee"</span>}}
        data={<span class="hljs-built_in">this</span>.state.chat_log}
        ref={<span class="hljs-function"><span class="hljs-params">ref</span> =&gt;</span> (<span class="hljs-built_in">this</span>.FlatListRef = ref)} <span class="hljs-comment">// assign the flatlist's ref to your component's FlatListRef...</span>


        renderItem = {<span class="hljs-function">(<span class="hljs-params">{item,index}</span>)=&gt;</span>{

          rowId={index}

            <span class="hljs-keyword">if</span> (SENT_MESSAGE) { <span class="hljs-comment">//change as per your code logic</span>



                <span class="hljs-keyword">return</span> (

                  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
                    <span class="hljs-attr">backgroundColor:</span> "#<span class="hljs-attr">0078fe</span>",
                    <span class="hljs-attr">padding:10</span>,
                    <span class="hljs-attr">marginLeft:</span> '<span class="hljs-attr">45</span>%',
                    <span class="hljs-attr">borderRadius:</span> <span class="hljs-attr">5</span>,

                    <span class="hljs-attr">marginTop:</span> <span class="hljs-attr">5</span>,
                    <span class="hljs-attr">marginRight:</span> "<span class="hljs-attr">5</span>%",
                    <span class="hljs-attr">maxWidth:</span> '<span class="hljs-attr">50</span>%',
                    <span class="hljs-attr">alignSelf:</span> '<span class="hljs-attr">flex-end</span>',
                    <span class="hljs-attr">borderRadius:</span> <span class="hljs-attr">20</span>,
                  }} <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span>


                    <span class="hljs-tag">&lt;<span class="hljs-name">Text</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">fontSize:</span> <span class="hljs-attr">16</span>, <span class="hljs-attr">color:</span> "#<span class="hljs-attr">fff</span>", }} <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span> {item.text}<span class="hljs-tag">&lt;/<span class="hljs-name">Text</span>&gt;</span>

                      <span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.rightArrow}</span>&gt;</span>

                      <span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.rightArrowOverlap}</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span>



                  <span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span></span>
                )




            } <span class="hljs-keyword">else</span> {


                <span class="hljs-keyword">return</span> (
                  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
                    <span class="hljs-attr">backgroundColor:</span> "#<span class="hljs-attr">dedede</span>",
                    <span class="hljs-attr">padding:10</span>,
                    <span class="hljs-attr">borderRadius:</span> <span class="hljs-attr">5</span>,
                    <span class="hljs-attr">marginTop:</span> <span class="hljs-attr">5</span>,
                    <span class="hljs-attr">marginLeft:</span> "<span class="hljs-attr">5</span>%",
                    <span class="hljs-attr">maxWidth:</span> '<span class="hljs-attr">50</span>%',
                    <span class="hljs-attr">alignSelf:</span> '<span class="hljs-attr">flex-start</span>',
                    //<span class="hljs-attr">maxWidth:</span> <span class="hljs-attr">500</span>,
                    //<span class="hljs-attr">padding:</span> <span class="hljs-attr">14</span>,

                    //<span class="hljs-attr">alignItems:</span>"<span class="hljs-attr">center</span>",
                    <span class="hljs-attr">borderRadius:</span> <span class="hljs-attr">20</span>,
                  }} <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span>



                      <span class="hljs-tag">&lt;<span class="hljs-name">Text</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">fontSize:</span> <span class="hljs-attr">16</span>, <span class="hljs-attr">color:</span> "#<span class="hljs-attr">000</span>",<span class="hljs-attr">justifyContent:</span>"<span class="hljs-attr">center</span>" }} <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span> {item.text}<span class="hljs-tag">&lt;/<span class="hljs-name">Text</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.leftArrow}</span>&gt;</span>

                      <span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">View</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.leftArrowOverlap}</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span>



                  <span class="hljs-tag">&lt;/<span class="hljs-name">View</span>&gt;</span></span>
                )


            }



        }

        keyExtractor={<span class="hljs-function">(<span class="hljs-params">item,index</span>)=&gt;</span>index.toString()}
        /&gt;
</code></pre><p>Values such as <strong>borderRadius, padding, margin,</strong> and <strong>backgroundColor</strong> are arbitrary values and can be changed if you wish. So feel free to play around and make those changes to best fit your requirements.</p>
<p>The result of the above code looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/IMG_20210324_160111.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Chat bubble appearance, as tested on several devices(Android).</em></p>
<p>Looks cool, doesn't it ? ;)</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congratulations! You've built a chat bubble that looks just like the one iMessage uses.</p>
<p>I hope you found this article helpful. If so, do share it with your friends and colleagues.</p>
<p>Still have questions? Don't hesitate to get in touch with me, and I'll respond to you as soon as possible.</p>
<p>You could also connect with me on <a target="_blank" href="https://in.linkedin.com/in/prajwal-kulkarni">LinkedIn</a> / <a target="_blank" href="https://instagram.com/prajwalkulkarni_">Instagram</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Flutter UI Tutorial – How to Build a Chat App with Stories Using the Flutter SDK ]]>
                </title>
                <description>
                    <![CDATA[ By Krissanawat Chat applications have become one of the easiest ways to communicate over the internet. As such, many applications incorporate chat features in them so that users can interact and engage in social communications.  These applications ha... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/flutter-messenger-clone/</link>
                <guid isPermaLink="false">66d4601b55db48792eed3f77</guid>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Facebook Messenger ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 11 Feb 2021 00:42:18 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/60228d650a2838549dcc1f0b.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Krissanawat</p>
<p>Chat applications have become one of the easiest ways to communicate over the internet. As such, many applications incorporate chat features in them so that users can interact and engage in social communications. </p>
<p>These applications have made the world smaller with their powerful end to end audio/video and text-based communication. On top of that, other features such as sharing stories, sending attachments, and more have made these apps even more engaging and useful. </p>
<p>Most apps also have a stories feature. It lets users share their experiences in brief and the story disappears after a certain amount of time. These features get users hooked to that app and make them want to share their thoughts, ideas, memories, and experiences.</p>
<p>Facebook Messenger is one of the most widely-used chat applications. It is just behind WhatsApp in overall usage across the world. </p>
<p>In this tutorial, we are going to replicate the Messenger UI using the Flutter mobile application development framework. We'll also explore widget-based UI development using Flutter coding. This will get us familiar with the Flutter ecosystem as well as best practices for writing Flutter code. </p>
<p>Here, we are going to implement the main conversation home screen of the messenger app which will contain a top app bar, a search bar, a stories section, and a conversation list section. </p>
<p>Through this process, we will see how Flutter makes the UI development easier and we'll get a messenger clone chat application out of it.</p>
<p>So, let's get started!</p>
<h2 id="heading-create-a-new-flutter-project">Create a new Flutter project</h2>
<p>First, we need to create a new Flutter project. To do that, make sure that the Flutter SDK and other Flutter app development-related requirements are properly installed. </p>
<p>If everything is properly set up, then to create a project we can simply run the following command in the desired local directory:</p>
<pre><code class="lang-bash">flutter create messengerUI
</code></pre>
<p>After the project has been set up, we can navigate inside the project directory and execute the following command in the terminal to run the project in either an available emulator or an actual device:</p>
<pre><code class="lang-bash">flutter run
</code></pre>
<p>After a successful build, we will get the following result in the emulator screen:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/tip1.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, we need to replace the default template with our own project structure template. </p>
<p>First, we need to create a folder called <strong>./screens</strong> inside the <strong>./lib</strong> folder. Then, inside the ./lib/screens folder, we need to create a new file called <strong>conversations.dart</strong>. </p>
<p>Inside <strong>conversation.dart</strong>, we are going to implement a simple Stateful widget class returning a <code>Scaffold</code> widget with a basic App bar and an empty <code>Container</code> body. The code for <strong>conversations.dart</strong> is shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Conversations</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  _ConversationsState createState() =&gt; _ConversationsState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ConversationsState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">Conversations</span>&gt; </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: Text(<span class="hljs-string">"Chat"</span>),
      ),
      body: Container(),
    );
  }
}
</code></pre>
<p>Now, we need to replace the default template in the <strong>main.dar</strong>t file and call the <code>Conversations</code> screen in the <code>home</code> option of <code>MaterialApp</code> widget as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:messangerUI/screens/conversations.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(MyApp());
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Messenger Demo'</span>,
      debugShowCheckedModeBanner: <span class="hljs-keyword">false</span>,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Conversations(),
    );
  }
}
</code></pre>
<p>We get the result as shown in the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/mess1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-add-the-appbar">How to Add the AppBar</h2>
<p>Now, we are going to customize the App bar at the top. Since the app bar needs to be scrollable, we are <strong>not</strong> going to use the <code>appBar</code> option provided by <code>Scaffold</code> widget. We are simply going to use the <code>ListView</code> widget in the body option of <code>Scaffold</code> and keep all other widgets as children of the <code>ListView</code> widget. </p>
<p>The overall implementation of the custom app bar is provided in the code snippet below:</p>
<pre><code class="lang-dart">Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: SafeArea(
          child: Container(
        padding: EdgeInsets.only(left: <span class="hljs-number">20</span>, right: <span class="hljs-number">20</span>, top: <span class="hljs-number">15</span>),
        child: ListView(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: &lt;Widget&gt;[
                Container(
                  width: <span class="hljs-number">40</span>,
                  height: <span class="hljs-number">40</span>,
                  decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      image: DecorationImage(
                          image: NetworkImage(
                              <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/11.jpg&gt;"</span>),
                          fit: BoxFit.cover)),
                ),
                Text(
                  <span class="hljs-string">"Chats"</span>,
                  style: TextStyle(fontSize: <span class="hljs-number">22</span>, fontWeight: FontWeight.bold),
                ),
                Icon(Icons.edit)
              ],
            ),
          ],
        ),
      )),
    );
  }
</code></pre>
<p>For the app bar UI, we have used a <code>Row</code> widget inside the <code>ListView</code> widget. Inside the Row widget, we have placed a <code>Container</code> widget with <code>NetworkImage</code> as a child, a <code>Text</code> widget, and an <code>Icon</code> widget.</p>
<p>Hence, we will get the result as shown in the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/mess2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-add-the-search-bar">How to Add the Search Bar</h2>
<p>Now, we are going to add a search input field just below the app bar. It will contain an <code>InputField</code> widget decorated with styles and a search icon. </p>
<p>Since we need a text controller for the <code>InputField</code> widget we need to initialize it first as shown in the code snippet below:</p>
<pre><code class="lang-dart">TextEditingController _searchController = <span class="hljs-keyword">new</span> TextEditingController();
</code></pre>
<p>Now, we are going to implement the UI for the search bar just below the <code>Row</code> widget which is inside the parent <code>ListView</code> widget. We'll use the <code>SizedBox</code> widget to give a tiny separation between the two sections. </p>
<p>The overall implementation of search bar using a <code>TextField</code> widget inside a <code>Container</code> widget with decoration is shown in the code snippet below:</p>
<pre><code class="lang-dart">body: SafeArea(
          child: Container(
        padding: EdgeInsets.only(left: <span class="hljs-number">20</span>, right: <span class="hljs-number">20</span>, top: <span class="hljs-number">15</span>),
        child: ListView(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: &lt;Widget&gt;[
                Container(
                  width: <span class="hljs-number">40</span>,
                  height: <span class="hljs-number">40</span>,
                  decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      image: DecorationImage(
                          image: NetworkImage(
                              <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/11.jpg&gt;"</span>),
                          fit: BoxFit.cover)),
                ),
                Text(
                  <span class="hljs-string">"Chats"</span>,
                  style: TextStyle(fontSize: <span class="hljs-number">22</span>, fontWeight: FontWeight.bold),
                ),
                Icon(Icons.edit)
              ],
            ),
            SizedBox(
              height: <span class="hljs-number">15</span>,
            ),
            Container(
              width: <span class="hljs-built_in">double</span>.infinity,
              height: <span class="hljs-number">40</span>,
              decoration: BoxDecoration(
                  color: Color(<span class="hljs-number">0xFFe9eaec</span>),
                  borderRadius: BorderRadius.circular(<span class="hljs-number">15</span>)),
              child: TextField(
                cursorColor: Color(<span class="hljs-number">0xFF000000</span>),
                controller: _searchController,
                decoration: InputDecoration(
                    prefixIcon: Icon(
                      Icons.search,
                      color: Color(<span class="hljs-number">0xFF000000</span>).withOpacity(<span class="hljs-number">0.5</span>),
                    ),
                    hintText: <span class="hljs-string">"Search"</span>,
                    border: InputBorder.none),
              ),
            ),
          ],
        ),
      )),
    );
</code></pre>
<p>Hence, we will get the search bar as shown in the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/mess3.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-implement-the-stories-section">How to Implement the Stories Section</h2>
<p>Now it is time to implement the stories section. Stories are popular feature in every social app these days. We are going to implement it just below the search bar. </p>
<p>This section will contain an image of the user with their name at the bottom. The users who have stories will have a blue circular ring around their image whereas others don't.</p>
<p>But first, we need to prepare a list of mock users to show them in the stories section. </p>
<p>For that, we are going to initialize a List called <code>storyList</code>. We'll keep some objects containing the user's information such as <code>name</code>, <code>imageUrl</code>, <code>isOnline</code> (to check if the user is online), and <code>hasStory</code> (to check it the user has a story). </p>
<p>The mock list data is provided in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-built_in">List</span> storyList = [
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Novac"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/31.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">true</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">true</span>,
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Derick"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/81.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">false</span>,
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Mevis"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/women/49.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">true</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">false</span>,
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Emannual"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/35.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">true</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">true</span>,
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Gracy"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/women/56.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">false</span>,
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Robert"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/36.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">true</span>,
    }
  ];
</code></pre>
<p>Now, we are going to implement a separate function that returns the overall UI for the Stories section. The implementation is provided in the code snippet below:</p>
<pre><code class="lang-dart">_stories() {
    <span class="hljs-keyword">return</span> SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: Row(
        children: &lt;Widget&gt;[
          Padding(
            padding: EdgeInsets.only(right: <span class="hljs-number">20</span>),
            child: Column(
              children: &lt;Widget&gt;[
                Container(
                  width: <span class="hljs-number">60</span>,
                  height: <span class="hljs-number">60</span>,
                  decoration: BoxDecoration(
                      shape: BoxShape.circle, color: Color(<span class="hljs-number">0xFFe9eaec</span>)),
                  child: Center(
                    child: Icon(
                      Icons.add,
                      size: <span class="hljs-number">33</span>,
                    ),
                  ),
                ),
                SizedBox(
                  height: <span class="hljs-number">10</span>,
                ),
                SizedBox(
                  width: <span class="hljs-number">75</span>,
                  child: Align(
                      child: Text(
                    <span class="hljs-string">'Your Story'</span>,
                    overflow: TextOverflow.ellipsis,
                  )),
                )
              ],
            ),
          ),
          Row(
              children: <span class="hljs-built_in">List</span>.generate(storyList.length, (index) {
            <span class="hljs-keyword">return</span> Padding(
              padding: <span class="hljs-keyword">const</span> EdgeInsets.only(right: <span class="hljs-number">20</span>),
              child: Column(
                children: &lt;Widget&gt;[
                  Container(
                    width: <span class="hljs-number">60</span>,
                    height: <span class="hljs-number">60</span>,
                    child: Stack(
                      children: &lt;Widget&gt;[
                        storyList[index][<span class="hljs-string">'hasStory'</span>]
                            ? Container(
                                decoration: BoxDecoration(
                                    shape: BoxShape.circle,
                                    border: Border.all(
                                        color: Colors.blueAccent, width: <span class="hljs-number">3</span>)),
                                child: Padding(
                                  padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">3.0</span>),
                                  child: Container(
                                    width: <span class="hljs-number">75</span>,
                                    height: <span class="hljs-number">75</span>,
                                    decoration: BoxDecoration(
                                        shape: BoxShape.circle,
                                        image: DecorationImage(
                                            image: NetworkImage(
                                                storyList[index][<span class="hljs-string">'imageUrl'</span>]),
                                            fit: BoxFit.cover)),
                                  ),
                                ),
                              )
                            : Container(
                                width: <span class="hljs-number">70</span>,
                                height: <span class="hljs-number">70</span>,
                                decoration: BoxDecoration(
                                    shape: BoxShape.circle,
                                    image: DecorationImage(
                                        image: NetworkImage(
                                            storyList[index][<span class="hljs-string">'imageUrl'</span>]),
                                        fit: BoxFit.cover)),
                              ),
                        storyList[index][<span class="hljs-string">'isOnline'</span>]
                            ? Positioned(
                                top: <span class="hljs-number">38</span>,
                                left: <span class="hljs-number">42</span>,
                                child: Container(
                                  width: <span class="hljs-number">20</span>,
                                  height: <span class="hljs-number">20</span>,
                                  decoration: BoxDecoration(
                                      color: Color(<span class="hljs-number">0xFF66BB6A</span>),
                                      shape: BoxShape.circle,
                                      border: Border.all(
                                          color: Color(<span class="hljs-number">0xFFFFFFFF</span>), width: <span class="hljs-number">3</span>)),
                                ),
                              )
                            : Container()
                      ],
                    ),
                  ),
                  SizedBox(
                    height: <span class="hljs-number">10</span>,
                  ),
                  SizedBox(
                    width: <span class="hljs-number">75</span>,
                    child: Align(
                        child: Text(
                      storyList[index][<span class="hljs-string">'name'</span>],
                      overflow: TextOverflow.ellipsis,
                    )),
                  )
                ],
              ),
            );
          }))
        ],
      ),
    );
  }
</code></pre>
<p>Here, we returned a <code>SingleChildScrollView</code> as a parent widget with an option for horizontal scrolling. </p>
<p>Then, we used the <code>List.generate</code> widget inside the <code>Row</code> widget to iterate through our <code>storyList</code> array. For each item in the list, a template inside the <code>List.generate</code> is returned. The conditional rendering is used for online users and those users who have stories.</p>
<p>Now, we need to call the function inside the <code>ListView</code> children just below the <code>InputField</code> making a separation using the <code>SizedBox</code> widget as shown in the code snippet below:</p>
<pre><code class="lang-dart">body: SafeArea(
          child: Container(
        padding: EdgeInsets.only(left: <span class="hljs-number">20</span>, right: <span class="hljs-number">20</span>, top: <span class="hljs-number">15</span>),
        child: ListView(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: &lt;Widget&gt;[
                Container(
                  width: <span class="hljs-number">40</span>,
                  height: <span class="hljs-number">40</span>,
                  decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      image: DecorationImage(
                          image: NetworkImage(
                              <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/11.jpg&gt;"</span>),
                          fit: BoxFit.cover)),
                ),
                Text(
                  <span class="hljs-string">"Chats"</span>,
                  style: TextStyle(fontSize: <span class="hljs-number">22</span>, fontWeight: FontWeight.bold),
                ),
                Icon(Icons.edit)
              ],
            ),
            SizedBox(
              height: <span class="hljs-number">15</span>,
            ),
            Container(
              width: <span class="hljs-built_in">double</span>.infinity,
              height: <span class="hljs-number">40</span>,
              decoration: BoxDecoration(
                  color: Color(<span class="hljs-number">0xFFe9eaec</span>),
                  borderRadius: BorderRadius.circular(<span class="hljs-number">15</span>)),
              child: TextField(
                cursorColor: Color(<span class="hljs-number">0xFF000000</span>),
                controller: _searchController,
                decoration: InputDecoration(
                    prefixIcon: Icon(
                      Icons.search,
                      color: Color(<span class="hljs-number">0xFF000000</span>).withOpacity(<span class="hljs-number">0.5</span>),
                    ),
                    hintText: <span class="hljs-string">"Search"</span>,
                    border: InputBorder.none),
              ),
            ),
            SizedBox(
              height: <span class="hljs-number">20</span>,
            ),
            _stories(),
          ],
        ),
      )),
    );
</code></pre>
<p>We will get the result as shown in the demo below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/messGIF1.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>As you can see, the stories section is scrollable horizontally.</p>
<h2 id="heading-how-to-create-the-conversation-list-section">How to Create the Conversation List Section</h2>
<p>Now, we are going to create a list of conversations just below the Stories section. It will contain an image of the user, their name, message, and time. </p>
<p>For the conversation list as well we are going to create some mock data. The list is similar to <code>storiesList</code> but has two extra pieces of information for <code>message</code> and <code>time</code>. The mock data list <code>conversationList</code> is shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-built_in">List</span> conversationList = [
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Novac"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/31.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">true</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">true</span>,
      <span class="hljs-string">"message"</span>: <span class="hljs-string">"Where are you?"</span>,
      <span class="hljs-string">"time"</span>: <span class="hljs-string">"5:00 pm"</span>
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Derick"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/81.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"message"</span>: <span class="hljs-string">"It's good!!"</span>,
      <span class="hljs-string">"time"</span>: <span class="hljs-string">"7:00 am"</span>
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Mevis"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/women/49.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">true</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"message"</span>: <span class="hljs-string">"I love You too!"</span>,
      <span class="hljs-string">"time"</span>: <span class="hljs-string">"6:50 am"</span>
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Emannual"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/35.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">true</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">true</span>,
      <span class="hljs-string">"message"</span>: <span class="hljs-string">"Got to go!! Bye!!"</span>,
      <span class="hljs-string">"time"</span>: <span class="hljs-string">"yesterday"</span>
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Gracy"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/women/56.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"message"</span>: <span class="hljs-string">"Sorry, I forgot!"</span>,
      <span class="hljs-string">"time"</span>: <span class="hljs-string">"2nd Feb"</span>
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Robert"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/36.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">true</span>,
      <span class="hljs-string">"message"</span>: <span class="hljs-string">"No, I won't go!"</span>,
      <span class="hljs-string">"time"</span>: <span class="hljs-string">"28th Jan"</span>
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Lucy"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/women/56.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"message"</span>: <span class="hljs-string">"Hahahahahaha"</span>,
      <span class="hljs-string">"time"</span>: <span class="hljs-string">"25th Jan"</span>
    },
    {
      <span class="hljs-string">"name"</span>: <span class="hljs-string">"Emma"</span>,
      <span class="hljs-string">"imageUrl"</span>: <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/women/56.jpg&gt;"</span>,
      <span class="hljs-string">"isOnline"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"hasStory"</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">"message"</span>: <span class="hljs-string">"Been a while!"</span>,
      <span class="hljs-string">"time"</span>: <span class="hljs-string">"15th Jan"</span>
    }
  ];
</code></pre>
<p>Similar to the stories section, we are going to build the conversation list section as a separate function. </p>
<p>As a parent widget, we have returned the <code>Column</code> widget. The child of the <code>Column</code> widget contains the <code>List.generate</code> widget that iterates through the <code>conversationList</code> array and provides the UI for each item in the conversation list. </p>
<p>We use conditional rendering for online users and those users who have stories. The overall implementation of the function is provided in the code snippet below:</p>
<pre><code class="lang-dart">_conversations(BuildContext context) {
    <span class="hljs-keyword">return</span> Column(
      children: <span class="hljs-built_in">List</span>.generate(conversationList.length, (index) {
        <span class="hljs-keyword">return</span> InkWell(
          child: Padding(
            padding: <span class="hljs-keyword">const</span> EdgeInsets.only(bottom: <span class="hljs-number">20</span>),
            child: Row(
              children: &lt;Widget&gt;[
                Container(
                  width: <span class="hljs-number">60</span>,
                  height: <span class="hljs-number">60</span>,
                  child: Stack(
                    children: &lt;Widget&gt;[
                      conversationList[index][<span class="hljs-string">'hasStory'</span>] ? 
                      Container(
                        decoration: BoxDecoration(
                            shape: BoxShape.circle,
                            border:
                                Border.all(color: Colors.blueAccent, width: <span class="hljs-number">3</span>)),
                        child: Padding(
                          padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">3.0</span>),
                          child: Container(
                            width: <span class="hljs-number">75</span>,
                            height: <span class="hljs-number">75</span>,
                            decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                image: DecorationImage(
                                    image: NetworkImage(
                                        conversationList[index][<span class="hljs-string">'imageUrl'</span>]),
                                    fit: BoxFit.cover)),
                          ),
                        ),
                      )
                      : Container(
                        width: <span class="hljs-number">70</span>,
                        height: <span class="hljs-number">70</span>,
                        decoration: BoxDecoration(
                            shape: BoxShape.circle,
                            image: DecorationImage(
                                image: NetworkImage(
                                    conversationList[index][<span class="hljs-string">'imageUrl'</span>]),
                                fit: BoxFit.cover)),
                      ),
                      conversationList[index][<span class="hljs-string">'isOnline'</span>]
                          ? Positioned(
                              top: <span class="hljs-number">38</span>,
                              left: <span class="hljs-number">42</span>,
                              child: Container(
                                width: <span class="hljs-number">20</span>,
                                height: <span class="hljs-number">20</span>,
                                decoration: BoxDecoration(
                                    color: Color(<span class="hljs-number">0xFF66BB6A</span>),
                                    shape: BoxShape.circle,
                                    border: Border.all(color: Color(<span class="hljs-number">0xFFFFFFFF</span>), width: <span class="hljs-number">3</span>)),
                              ),
                            )
                          : Container()
                    ],
                  ),
                ),
                SizedBox(
                  width: <span class="hljs-number">20</span>,
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: &lt;Widget&gt;[
                    Text(
                      conversationList[index][<span class="hljs-string">'name'</span>],
                      style:
                          TextStyle(fontSize: <span class="hljs-number">17</span>, fontWeight: FontWeight.w500),
                    ),
                    SizedBox(
                      height: <span class="hljs-number">5</span>,
                    ),
                    SizedBox(
                      width: MediaQuery.of(context).size.width - <span class="hljs-number">135</span>,
                      child: Text(
                        conversationList[index][<span class="hljs-string">'message'</span>] +
                            <span class="hljs-string">" - "</span> +
                            conversationList[index][<span class="hljs-string">'time'</span>],
                        style: TextStyle(
                            fontSize: <span class="hljs-number">15</span>, color: Color(<span class="hljs-number">0xFF000000</span>).withOpacity(<span class="hljs-number">0.7</span>)),
                        overflow: TextOverflow.ellipsis,
                      ),
                    )
                  ],
                )
              ],
            ),
          ),
        );
      }),
    );
  }
</code></pre>
<p>Now we need to call the <code>_converstions()</code> function in the in <code>ListView</code> of <code>Scaffold</code> just below the Stories function as shown in the code snippet below:</p>
<pre><code class="lang-dart">body: SafeArea(
          child: Container(
        padding: EdgeInsets.only(left: <span class="hljs-number">20</span>, right: <span class="hljs-number">20</span>, top: <span class="hljs-number">15</span>),
        child: ListView(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: &lt;Widget&gt;[
                Container(
                  width: <span class="hljs-number">40</span>,
                  height: <span class="hljs-number">40</span>,
                  decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      image: DecorationImage(
                          image: NetworkImage(
                              <span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/11.jpg&gt;"</span>),
                          fit: BoxFit.cover)),
                ),
                Text(
                  <span class="hljs-string">"Chats"</span>,
                  style: TextStyle(fontSize: <span class="hljs-number">22</span>, fontWeight: FontWeight.bold),
                ),
                Icon(Icons.edit)
              ],
            ),
            SizedBox(
              height: <span class="hljs-number">15</span>,
            ),
            Container(
              width: <span class="hljs-built_in">double</span>.infinity,
              height: <span class="hljs-number">40</span>,
              decoration: BoxDecoration(
                  color: Color(<span class="hljs-number">0xFFe9eaec</span>),
                  borderRadius: BorderRadius.circular(<span class="hljs-number">15</span>)),
              child: TextField(
                cursorColor: Color(<span class="hljs-number">0xFF000000</span>),
                controller: _searchController,
                decoration: InputDecoration(
                    prefixIcon: Icon(
                      Icons.search,
                      color: Color(<span class="hljs-number">0xFF000000</span>).withOpacity(<span class="hljs-number">0.5</span>),
                    ),
                    hintText: <span class="hljs-string">"Search"</span>,
                    border: InputBorder.none),
              ),
            ),
            SizedBox(
              height: <span class="hljs-number">20</span>,
            ),
            _stories(),
            SizedBox(
              height: <span class="hljs-number">20</span>,
            ),
            **_conversations(context)**
          ],
        ),
      )),
    );
  }
</code></pre>
<p>We will get the result as shown in the demo below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/messGIF2.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>As you can see, the overall screen is scrollable vertically with Conversation List view and the Stories section is scrollable horizontally.</p>
<p>Finally, we have successfully created the home screen of a messenger app using Flutter.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The main objective of this tutorial was to show you how to build a UI like Facebook messenger using the Flutter ecosystem. </p>
<p>If you look closely at the code, you'll see that most of the implementation was pretty easy because of the flexibility and structured layout build that Flutter provides. </p>
<p>With only a few widgets, we can place every component in the UI in the correct position. Along with this beautiful UI creation, you can also learn basic coding patterns for Flutter development. </p>
<p>Separating large sections of code into separate functions helps simplify and clean up our code. This demonstrated a best coding practice for UI development in Flutter. </p>
<p>The tutorial also highlights how some widgets in Flutter make things easier for us, such as horizontal scrolling and placement icons and images with styles. You can definitely take this information and use it to build your own chat application in the future.</p>
<p>Moreover, you can also get inspiration from state of the art <a target="_blank" href="https://www.instaflutter.com/app-templates/flutter-chat-app/">Flutter chat app templates</a> out there that provide beautiful UIs as well as powerful features. And in case you want to check out chat application templates built using other mobile application development frameworks, you peruse these <a target="_blank" href="https://www.instamobile.io/app-templates/react-native-chat-app-template/">React Native Chat app</a> templates as well.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Chat App UI With Flutter and Dart ]]>
                </title>
                <description>
                    <![CDATA[ By Krissanawat These days, many people use chat applications to communicate with team members, friends, and family via their smart phones. This makes these messaging applications an essential medium of communication.  There is also high demand for in... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-chat-app-ui-with-flutter/</link>
                <guid isPermaLink="false">66d46015a326133d12440a25</guid>
                
                    <category>
                        <![CDATA[ application ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 20 Jan 2021 22:14:57 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/6006d9e30a2838549dcb4bb3.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Krissanawat</p>
<p>These days, many people use chat applications to communicate with team members, friends, and family via their smart phones. This makes these messaging applications an essential medium of communication. </p>
<p>There is also high demand for intuitive and powerful user interfaces with state of the art features. The user interface (or UI) is the most impactful aspect of the overall user experience, so it's important to get right.</p>
<p>Flutter app development has taken the world by storm in terms of cross-platform mobile application development. You can use it to create pixel-perfect UIs, and many development companies use Flutter today. </p>
<p>In this tutorial, I am going to introduce you to a mix of both: we're going to build a chat app UI entirely on the Flutter/Dart coding environment. Along with learning the awesome Chat UI implementation in Flutter, we will also learn how its coding workflows and structures work.</p>
<p>So, let's get started!</p>
<h2 id="heading-how-to-create-a-new-flutter-project">How to Create a New Flutter Project</h2>
<p>First, we need to create a new Flutter project. For that, make sure that you've installed the Flutter SDK and other Flutter app development-related requirements. </p>
<p>If everything is properly set up, then in order to create a project we can simply run the following command in our desired local directory:</p>
<pre><code class="lang-bash">flutter create ChatApp
</code></pre>
<p>After we've set up the project, we can navigate inside the project directory and execute the following command in the terminal to run the project in either an available emulator or an actual device:</p>
<pre><code class="lang-bash">flutter run
</code></pre>
<p>After it's been successfully built, we will get the following result in the emulator screen:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-105.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-create-the-main-home-screen-ui"><strong>How to Create the Main Home Screen UI</strong></h2>
<p>Now, we are going to start building the UI for our chat application. The Main Home Screen will contain 2 sections: </p>
<ul>
<li>the conversation screen, which we are going to implement as a separate page, and </li>
<li>a bottom navigation bar.</li>
</ul>
<p>First, we need to make some simple configurations to the default boilerplate code in the <strong>main.dart</strong> file. We'll remove some default code and add the simple <code>MaterialApp</code> pointing to the empty <code>Container</code> as a home page for now:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(MyApp());
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Flutter Demo'</span>,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: <span class="hljs-keyword">false</span>,
      home: Container(),
    );
  }
}
</code></pre>
<p>Now in place of the empty Container widget, we are going to call the <code>HomePage</code> screen widget. But first, we need to implement the screen.</p>
<h3 id="heading-how-to-build-the-main-home-screen">How to Build the Main Home Screen</h3>
<p>Inside the <strong>./lib</strong> directory of our root project folder, we need to create a folder called <strong>./screens</strong>. This folder will hold all the dart files for different screens.</p>
<p>Inside <strong>./lib/screens/</strong> directory, we need to create a file called <strong>homePage.dart</strong>. Inside the <strong>homePage.dart</strong> file, we need to add the basic Stateless widget code as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span></span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: Container(
        child: Center(child: Text(<span class="hljs-string">"Chat"</span>)),
      ),

    );
  }
}
</code></pre>
<p>Now, we need to call the <code>HomePage</code> class widget in the <strong>main.dart</strong> file as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Flutter Demo'</span>,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: <span class="hljs-keyword">false</span>,
      home: HomePage(),
    );
  }
}
</code></pre>
<p>Now we will get the result as shown in the emulator screenshot below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-106.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-build-the-bottom-navigator-bar">How to Build the Bottom Navigator Bar</h3>
<p>Now, we are going to place a Bottom Navigation menu on the <code>HomePage</code> screen. For that, we are going to use the <code>BottomNavigationBar</code> widget in the <code>bottomNavigationBar</code> parameter provided by the <code>Scaffold</code> widget. </p>
<p>Here's the overall code implementation:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> Scaffold(
      body: Container(
        child: Center(child: Text(<span class="hljs-string">"Chat"</span>)),
      ),
      bottomNavigationBar: BottomNavigationBar(
        selectedItemColor: Colors.red,
        unselectedItemColor: Colors.grey.shade600,
        selectedLabelStyle: TextStyle(fontWeight: FontWeight.w600),
        unselectedLabelStyle: TextStyle(fontWeight: FontWeight.w600),
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            title: Text(<span class="hljs-string">"Chats"</span>),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.group_work),
            title: Text(<span class="hljs-string">"Channels"</span>),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.account_box),
            title: Text(<span class="hljs-string">"Profile"</span>),
          ),
        ],
      ),
    );
</code></pre>
<p>Here, we have configured <code>BottomNavigationBar</code> with various style parameters and kept our Navigation menu item in the <code>items</code> parameter. For the <code>body</code> parameter, we have just used a simple <code>Container</code> with a <code>Text</code> widget.</p>
<p>Now we will get the bottom navbar as shown in the emulator screenshot below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-107.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now that the bottom navigation is done, we can go ahead and implement the conversation list section just above the bottom navbar.</p>
<h2 id="heading-how-to-build-the-conversation-list-screen"><strong>How to Build the Conversation List Screen</strong></h2>
<p>Here, we are going to create the conversation list section which will contain a header section, a search bar, and the conversation list view.</p>
<p>First, inside the <strong>./lib/screens</strong> folder, we need to create a new dart file called <strong>chatPage.dart</strong>. Then, add a simple stateful widget class template inside as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChatPage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  _ChatPageState createState() =&gt; _ChatPageState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ChatPageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ChatPage</span>&gt; </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: SingleChildScrollView(
        child: Center(child: Text(<span class="hljs-string">"Chat"</span>)),
      ),
    );
  }
}
</code></pre>
<p>Now, we need to call the <code>chatPage</code> class widget in place of the <code>Container</code> widget in <strong>homePage.dart</strong> as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> Scaffold(
      body: ChatPage(),
      bottomNavigationBar: BottomNavigationBar(
</code></pre>
<p>This will give us the following result as shown in the emulator below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-108.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-build-a-conversation-list-page-header">How to Build a Conversation List Page Header</h3>
<p>Now, we are going to add the header to the Conversation list section which will have a text header and a button. The complete UI implementation code is provided in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> Scaffold(
      body: SingleChildScrollView(
        physics: BouncingScrollPhysics(),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: &lt;Widget&gt;[
            SafeArea(
              child: Padding(
                padding: EdgeInsets.only(left: <span class="hljs-number">16</span>,right: <span class="hljs-number">16</span>,top: <span class="hljs-number">10</span>),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: &lt;Widget&gt;[
                    Text(<span class="hljs-string">"Conversations"</span>,style: TextStyle(fontSize: <span class="hljs-number">32</span>,fontWeight: FontWeight.bold),),
                    Container(
                      padding: EdgeInsets.only(left: <span class="hljs-number">8</span>,right: <span class="hljs-number">8</span>,top: <span class="hljs-number">2</span>,bottom: <span class="hljs-number">2</span>),
                      height: <span class="hljs-number">30</span>,
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(<span class="hljs-number">30</span>),
                        color: Colors.pink[<span class="hljs-number">50</span>],
                      ),
                      child: Row(
                        children: &lt;Widget&gt;[
                          Icon(Icons.add,color: Colors.pink,size: <span class="hljs-number">20</span>,),
                          SizedBox(width: <span class="hljs-number">2</span>,),
                          Text(<span class="hljs-string">"Add New"</span>,style: TextStyle(fontSize: <span class="hljs-number">14</span>,fontWeight: FontWeight.bold),),
                        ],
                      ),
                    )
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
</code></pre>
<p>Here, we have used the <code>SingleChildScrollView</code> so that the body section of the <strong>chatPage.dart</strong> is entirely scrollable. </p>
<p>Then, we have used the <code>BouncingScrollPhysics</code> instance to give the bouncing effect when a user's scrolling reaches the end or beginning. </p>
<p>Next, we have added a Text widget and a Container to display the bottom at the right-hand side. </p>
<p>Lastly, we have a <code>Column</code> widget as a child of the <code>SingleChildScrollView</code> widget so everything will appear vertically on the screen.</p>
<p>This will give us the following result as shown in the emulator below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-109.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, we are going to add a search bar just below the Header section.</p>
<h3 id="heading-how-to-add-the-search-bar"><strong>How to Add the Search Bar</strong></h3>
<p>In the <code>Column</code> widget from before, we are going to add a Search bar widget just below the Header UI section. So, as a second child of the <code>Column</code> widget, we need to insert the following code provided in the code snippet below:</p>
<pre><code class="lang-dart">Padding(
  padding: EdgeInsets.only(top: <span class="hljs-number">16</span>,left: <span class="hljs-number">16</span>,right: <span class="hljs-number">16</span>),
  child: TextField(
    decoration: InputDecoration(
      hintText: <span class="hljs-string">"Search..."</span>,
      hintStyle: TextStyle(color: Colors.grey.shade600),
      prefixIcon: Icon(Icons.search,color: Colors.grey.shade600, size: <span class="hljs-number">20</span>,),
      filled: <span class="hljs-keyword">true</span>,
      fillColor: Colors.grey.shade100,
      contentPadding: EdgeInsets.all(<span class="hljs-number">8</span>),
      enabledBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(<span class="hljs-number">20</span>),
          borderSide: BorderSide(
              color: Colors.grey.shade100
          )
      ),
    ),
  ),
),
</code></pre>
<p>This will give us the following result as shown in the emulator below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-110.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-build-the-conversation-list"><strong>How to Build the Conversation List</strong></h3>
<p>Now that we have the Header section and a Search bar, we are going to implement the conversation list section.</p>
<p>For that, we need to implement a class object model to store the instances of conversation lists. </p>
<p>So inside the <strong>./lib</strong> folder, we need to create a new folder called <strong>./models</strong>. Inside <strong>./models</strong>, we need to create a file called <strong>chatUsersModel.dart</strong>. </p>
<p>In the model file, we need to create a model object class as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/cupertino.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChatUsers</span></span>{
  <span class="hljs-built_in">String</span> name;
  <span class="hljs-built_in">String</span> messageText;
  <span class="hljs-built_in">String</span> imageURL;
  <span class="hljs-built_in">String</span> time;
  ChatUsers({<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.name,<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.messageText,<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.imageURL,<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.time});
}
</code></pre>
<p>The object will house the user's name, text message, image URL, and time.</p>
<p>Next we need to create a list of users inside the <strong>chatPage.dart</strong> as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ChatPageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ChatPage</span>&gt; </span>{
  <span class="hljs-built_in">List</span>&lt;ChatUsers&gt; chatUsers = [
    ChatUsers(text: <span class="hljs-string">"Jane Russel"</span>, secondaryText: <span class="hljs-string">"Awesome Setup"</span>, image: <span class="hljs-string">"images/userImage1.jpeg"</span>, time: <span class="hljs-string">"Now"</span>),
    ChatUsers(text: <span class="hljs-string">"Glady's Murphy"</span>, secondaryText: <span class="hljs-string">"That's Great"</span>, image: <span class="hljs-string">"images/userImage2.jpeg"</span>, time: <span class="hljs-string">"Yesterday"</span>),
    ChatUsers(text: <span class="hljs-string">"Jorge Henry"</span>, secondaryText: <span class="hljs-string">"Hey where are you?"</span>, image: <span class="hljs-string">"images/userImage3.jpeg"</span>, time: <span class="hljs-string">"31 Mar"</span>),
    ChatUsers(text: <span class="hljs-string">"Philip Fox"</span>, secondaryText: <span class="hljs-string">"Busy! Call me in 20 mins"</span>, image: <span class="hljs-string">"images/userImage4.jpeg"</span>, time: <span class="hljs-string">"28 Mar"</span>),
    ChatUsers(text: <span class="hljs-string">"Debra Hawkins"</span>, secondaryText: <span class="hljs-string">"Thankyou, It's awesome"</span>, image: <span class="hljs-string">"images/userImage5.jpeg"</span>, time: <span class="hljs-string">"23 Mar"</span>),
    ChatUsers(text: <span class="hljs-string">"Jacob Pena"</span>, secondaryText: <span class="hljs-string">"will update you in evening"</span>, image: <span class="hljs-string">"images/userImage6.jpeg"</span>, time: <span class="hljs-string">"17 Mar"</span>),
    ChatUsers(text: <span class="hljs-string">"Andrey Jones"</span>, secondaryText: <span class="hljs-string">"Can you please share the file?"</span>, image: <span class="hljs-string">"images/userImage7.jpeg"</span>, time: <span class="hljs-string">"24 Feb"</span>),
    ChatUsers(text: <span class="hljs-string">"John Wick"</span>, secondaryText: <span class="hljs-string">"How are you?"</span>, image: <span class="hljs-string">"images/userImage8.jpeg"</span>, time: <span class="hljs-string">"18 Feb"</span>),
  ];
</code></pre>
<p>Now that we have the mock users' conversation list data, we can apply it to the conversation list to create a list view.</p>
<h3 id="heading-how-to-make-a-separate-class-widget-for-individual-conversation"><strong>How to Make a Separate Class Widget for Individual Conversation</strong></h3>
<p>Here, we are going to make a separate component widget for the individual items in the conversation list view.</p>
<p>For that, inside <strong>./lib</strong> create a folder called <strong>./widgets</strong>. And inside <strong>./widgets</strong> we need to create a file called <strong>conversationList.dart.</strong> Inside the new widget file, we can use the code from the following code snippet:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConversationList</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span></span>{
  <span class="hljs-built_in">String</span> name;
  <span class="hljs-built_in">String</span> messageText;
  <span class="hljs-built_in">String</span> imageUrl;
  <span class="hljs-built_in">String</span> time;
  <span class="hljs-built_in">bool</span> isMessageRead;
  ConversationList({<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.name,<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.messageText,<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.imageUrl,<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.time,<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.isMessageRead});
  <span class="hljs-meta">@override</span>
  _ConversationListState createState() =&gt; _ConversationListState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ConversationListState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ConversationList</span>&gt; </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> GestureDetector(
      onTap: (){
      },
      child: Container(
        padding: EdgeInsets.only(left: <span class="hljs-number">16</span>,right: <span class="hljs-number">16</span>,top: <span class="hljs-number">10</span>,bottom: <span class="hljs-number">10</span>),
        child: Row(
          children: &lt;Widget&gt;[
            Expanded(
              child: Row(
                children: &lt;Widget&gt;[
                  CircleAvatar(
                    backgroundImage: NetworkImage(widget.imageUrl),
                    maxRadius: <span class="hljs-number">30</span>,
                  ),
                  SizedBox(width: <span class="hljs-number">16</span>,),
                  Expanded(
                    child: Container(
                      color: Colors.transparent,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: &lt;Widget&gt;[
                          Text(widget.name, style: TextStyle(fontSize: <span class="hljs-number">16</span>),),
                          SizedBox(height: <span class="hljs-number">6</span>,),
                          Text(widget.messageText,style: TextStyle(fontSize: <span class="hljs-number">13</span>,color: Colors.grey.shade600, fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal),),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
            Text(widget.time,style: TextStyle(fontSize: <span class="hljs-number">12</span>,fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal),),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p>This widget file takes the user's name, text message, image URL, time, and a Boolean message type value as parameters. And it returns the template containing the values.</p>
<p>In the <strong>chatPage.dart,</strong> inside the <code>ListView</code> widget, we need to call the <code>ConversationList</code> widget by passing the required parameters as shown in the code snippet below:</p>
<pre><code class="lang-dart">ListView.builder(
  itemCount: chatUsers.length,
  shrinkWrap: <span class="hljs-keyword">true</span>,
  padding: EdgeInsets.only(top: <span class="hljs-number">16</span>),
  physics: NeverScrollableScrollPhysics(),
  itemBuilder: (context, index){
    <span class="hljs-keyword">return</span> ConversationList(
      name: chatUsers[index].name,
      messageText: chatUsers[index].messageText,
      imageUrl: chatUsers[index].imageURL,
      time: chatUsers[index].time,
      isMessageRead: (index == <span class="hljs-number">0</span> || index == <span class="hljs-number">3</span>)?<span class="hljs-keyword">true</span>:<span class="hljs-keyword">false</span>,
    );
  },
),
</code></pre>
<p>Note that this <code>ListView</code> widget is to keep as the first child of the <code>Column</code> widget in the <code>chatPage</code> screen.</p>
<p>This will give us the following result as shown in the emulator below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-111.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This completes the UI implementation of our Conversation List Screen and Main Home page screen as a whole. Now, we'll move on to the implementation of the chat details screen.</p>
<h2 id="heading-how-to-build-a-chat-detail-screen"><strong>How to Build a Chat Detail Screen</strong></h2>
<p>Now, we are going to create a chat detail screen. For that, we need to create a new file called <strong>chatDetailPage.dart</strong> inside the <strong>./lib/screens/</strong> folder. For now, we are just going to add the basic code as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChatDetailPage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span></span>{
  <span class="hljs-meta">@override</span>
  _ChatDetailPageState createState() =&gt; _ChatDetailPageState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_ChatDetailPageState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">ChatDetailPage</span>&gt; </span>{

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: Text(<span class="hljs-string">"Chat Detail"</span>),
      ),
      body: Container()
    );
  }
}
</code></pre>
<p>Here, we have returned a basic <code>AppBar</code> with Text and an empty Container as the <code>body</code> of the <code>Scaffold</code> widget.</p>
<p>Now, we are going to add navigation to <code>ChatDetailPage</code> on the <code>GestureHandler</code> widget's <code>onTap</code> method in the <strong>conversationList.dart</strong> widget file as shown in the code snippet below:</p>
<pre><code class="lang-dart">GestureDetector(
      onTap: (){
        Navigator.push(context, MaterialPageRoute(builder: (context){
          <span class="hljs-keyword">return</span> ChatDetailPage();
        }));
      },
</code></pre>
<p>We can now navigate to the Chat detail screen as shown in the emulator demo below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/2021-01-20-13.40.11.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-build-a-custom-app-bar-for-chat-detail-screen">How to Build a Custom App Bar for Chat Detail Screen</h3>
<p>Here, we are going to add a custom App bar at the top of the chat detail screen. For that, we are going to use the <code>AppBar</code> widget with various parameter configurations as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        elevation: <span class="hljs-number">0</span>,
        automaticallyImplyLeading: <span class="hljs-keyword">false</span>,
        backgroundColor: Colors.white,
        flexibleSpace: SafeArea(
          child: Container(
            padding: EdgeInsets.only(right: <span class="hljs-number">16</span>),
            child: Row(
              children: &lt;Widget&gt;[
                IconButton(
                  onPressed: (){
                    Navigator.pop(context);
                  },
                  icon: Icon(Icons.arrow_back,color: Colors.black,),
                ),
                SizedBox(width: <span class="hljs-number">2</span>,),
                CircleAvatar(
                  backgroundImage: NetworkImage(<span class="hljs-string">"&lt;https://randomuser.me/api/portraits/men/5.jpg&gt;"</span>),
                  maxRadius: <span class="hljs-number">20</span>,
                ),
                SizedBox(width: <span class="hljs-number">12</span>,),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: &lt;Widget&gt;[
                      Text(<span class="hljs-string">"Kriss Benwat"</span>,style: TextStyle( fontSize: <span class="hljs-number">16</span> ,fontWeight: FontWeight.w600),),
                      SizedBox(height: <span class="hljs-number">6</span>,),
                      Text(<span class="hljs-string">"Online"</span>,style: TextStyle(color: Colors.grey.shade600, fontSize: <span class="hljs-number">13</span>),),
                    ],
                  ),
                ),
                Icon(Icons.settings,color: Colors.black54,),
              ],
            ),
          ),
        ),
      ),
      body: Container()
    );
</code></pre>
<p>This will give us the following result as shown in the emulator below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-112.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-implement-the-bottom-text-box"><strong>How to Implement the Bottom Text Box</strong></h3>
<p>At the bottom of the chat detail screen, we need to add a messaging section that will contain a text editor and a button to send the message. </p>
<p>For this, we are going to use the <code>Align</code> widget and align the child inside the widget with the bottom of the screen. The overall code is provided in the code snippet below:</p>
<pre><code class="lang-dart">body: Stack(
        children: &lt;Widget&gt;[
          Align(
            alignment: Alignment.bottomLeft,
            child: Container(
              padding: EdgeInsets.only(left: <span class="hljs-number">10</span>,bottom: <span class="hljs-number">10</span>,top: <span class="hljs-number">10</span>),
              height: <span class="hljs-number">60</span>,
              width: <span class="hljs-built_in">double</span>.infinity,
              color: Colors.white,
              child: Row(
                children: &lt;Widget&gt;[
                  GestureDetector(
                    onTap: (){
                    },
                    child: Container(
                      height: <span class="hljs-number">30</span>,
                      width: <span class="hljs-number">30</span>,
                      decoration: BoxDecoration(
                        color: Colors.lightBlue,
                        borderRadius: BorderRadius.circular(<span class="hljs-number">30</span>),
                      ),
                      child: Icon(Icons.add, color: Colors.white, size: <span class="hljs-number">20</span>, ),
                    ),
                  ),
                  SizedBox(width: <span class="hljs-number">15</span>,),
                  Expanded(
                    child: TextField(
                      decoration: InputDecoration(
                        hintText: <span class="hljs-string">"Write message..."</span>,
                        hintStyle: TextStyle(color: Colors.black54),
                        border: InputBorder.none
                      ),
                    ),
                  ),
                  SizedBox(width: <span class="hljs-number">15</span>,),
                  FloatingActionButton(
                    onPressed: (){},
                    child: Icon(Icons.send,color: Colors.white,size: <span class="hljs-number">18</span>,),
                    backgroundColor: Colors.blue,
                    elevation: <span class="hljs-number">0</span>,
                  ),
                ],

              ),
            ),
          ),
        ],
      ),
</code></pre>
<p>This will give us a messaging section with a text field to type the messages and a button to send the messages:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-113.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We also have a button to the left which we can use to add other menu options for messaging.</p>
<h3 id="heading-how-to-set-up-the-messages-list-section-in-chat-screen"><strong>How to Set Up the Messages List Section in Chat Screen</strong></h3>
<p>Now, we are going to create the UI for messages appearing in the chat detail screen.</p>
<p>First, we need to create a model that reflects the message instance object.</p>
<p>For that, we need to create a file called <strong>chatMessageModel.dart</strong> inside the <strong>./models</strong> folder and define the class object as follows:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/cupertino.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChatMessage</span></span>{
  <span class="hljs-built_in">String</span> messageContent;
  <span class="hljs-built_in">String</span> messageType;
  ChatMessage({<span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.messageContent, <span class="hljs-meta">@required</span> <span class="hljs-keyword">this</span>.messageType});
}
</code></pre>
<p>The class object accepts the message content and message type (whether sender or receiver as instance values.</p>
<p>Now in the <strong>chatDetailPage.dart</strong>, we need to create a list of messages to display as shown in the code snippet below:</p>
<pre><code class="lang-dart"><span class="hljs-built_in">List</span>&lt;ChatMessage&gt; messages = [
    ChatMessage(messageContent: <span class="hljs-string">"Hello, Will"</span>, messageType: <span class="hljs-string">"receiver"</span>),
    ChatMessage(messageContent: <span class="hljs-string">"How have you been?"</span>, messageType: <span class="hljs-string">"receiver"</span>),
    ChatMessage(messageContent: <span class="hljs-string">"Hey Kriss, I am doing fine dude. wbu?"</span>, messageType: <span class="hljs-string">"sender"</span>),
    ChatMessage(messageContent: <span class="hljs-string">"ehhhh, doing OK."</span>, messageType: <span class="hljs-string">"receiver"</span>),
    ChatMessage(messageContent: <span class="hljs-string">"Is there any thing wrong?"</span>, messageType: <span class="hljs-string">"sender"</span>),
  ];
</code></pre>
<p>Next, we are going to create a list view for the messages on top of the <code>Stack</code> widget's children, above the <code>Align</code> widget, as shown in the code snippet below:</p>
<pre><code class="lang-dart">body: Stack(
        children: &lt;Widget&gt;[
          ListView.builder(
            itemCount: messages.length,
            shrinkWrap: <span class="hljs-keyword">true</span>,
            padding: EdgeInsets.only(top: <span class="hljs-number">10</span>,bottom: <span class="hljs-number">10</span>),
            physics: NeverScrollableScrollPhysics(),
            itemBuilder: (context, index){
              <span class="hljs-keyword">return</span> Container(
                padding: EdgeInsets.only(left: <span class="hljs-number">16</span>,right: <span class="hljs-number">16</span>,top: <span class="hljs-number">10</span>,bottom: <span class="hljs-number">10</span>),
                child: Text(messages[index].messageContent),
              );
            },
          ),
          Align(
</code></pre>
<p>Now the messages will appear in list form as shown in the emulator screenshot below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-114.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, we have the messages appearing on the screen but they are not styled the way we want in the chatting screen.</p>
<h3 id="heading-how-to-style-and-position-the-messages-based-on-sender-and-receiver">How to Style and Position the Messages Based on Sender and Receiver</h3>
<p>Now, we are going to style the message list so that it appears as a chat message bubble. We're also going to position them based on the message type using the <code>Align</code> widget as shown in the code snippet below:</p>
<pre><code class="lang-dart">ListView.builder(
  itemCount: messages.length,
  shrinkWrap: <span class="hljs-keyword">true</span>,
  padding: EdgeInsets.only(top: <span class="hljs-number">10</span>,bottom: <span class="hljs-number">10</span>),
  physics: NeverScrollableScrollPhysics(),
  itemBuilder: (context, index){
    <span class="hljs-keyword">return</span> Container(
      padding: EdgeInsets.only(left: <span class="hljs-number">14</span>,right: <span class="hljs-number">14</span>,top: <span class="hljs-number">10</span>,bottom: <span class="hljs-number">10</span>),
      child: Align(
        alignment: (messages[index].messageType == <span class="hljs-string">"receiver"</span>?Alignment.topLeft:Alignment.topRight),
        child: Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(<span class="hljs-number">20</span>),
            color: (messages[index].messageType  == <span class="hljs-string">"receiver"</span>?Colors.grey.shade200:Colors.blue[<span class="hljs-number">200</span>]),
          ),
          padding: EdgeInsets.all(<span class="hljs-number">16</span>),
          child: Text(messages[index].messageContent, style: TextStyle(fontSize: <span class="hljs-number">15</span>),),
        ),
      ),
    );
  },
),
</code></pre>
<p>This will give us the following result as shown in the emulator below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-115.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can see an overall demo of the app's entire UI below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/2021-01-20-13.52.45.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Congrats! We have built an intuitive and modern chat app UI entirely in the Flutter and Dart ecosystem.</p>
<h2 id="heading-recap">Recap</h2>
<p>Social messaging applications are an essential communication medium nowadays. Equipped with state of the art and powerful chat interfaces with audio and video calling, image and file attachments, and many more, these chat applications have made communication much more efficient. These apps have made the world smaller for us. </p>
<p>The major objective of this article was to show you how to develop a simple intuitive UI for chat applications with a modern design in the Flutter ecosystem. The step-by-step implementation provided a detailed showcase of the app's UI and also gave an overview of the Flutter coding environment. </p>
<p>I hope this tutorial helps you create your next chat application using Flutter.</p>
<p>You can also get inspiration for a chat app UI and feature development from top <a target="_blank" href="https://www.instaflutter.com/app-templates/flutter-chat-app/">Flutter chat app</a> templates out there in the market. Make sure to check them out as well.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a RocketChat Chatbot with TypeScript ]]>
                </title>
                <description>
                    <![CDATA[ Today I will show you how to build your own Rocket.Chat bot and test it locally. This is the same process I used to build freeCodeCamp's moderation chat bot for our community's self-hosted chat server. This code is now running in production, and lots... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-rocketchat-bot-with-typescript/</link>
                <guid isPermaLink="false">66ac7f2534f459ab1f112a76</guid>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #chatbots ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Naomi Carrigan ]]>
                </dc:creator>
                <pubDate>Thu, 07 Jan 2021 23:30:51 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5fecb6ff7af2371468bb4b4c.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Today I will show you how to build your own Rocket.Chat bot and test it locally.</p>
<p>This is the same process I used to build freeCodeCamp's <a target="_blank" href="https://github.com/freeCodeCamp/rocketchat-bot">moderation chat bot</a> for our community's self-hosted chat server. This code is now running in production, and lots of people are using it.</p>
<h2 id="heading-how-to-set-up-a-rocketchat-server">How to Set up a Rocket.Chat Server</h2>
<p>Your first step is to get an instance of Rocket.Chat running locally – you will need this to test the bot's functionality. </p>
<p>You can use freeCodeCamp's <a target="_blank" href="https://github.com/freeCodeCamp/chat-config/blob/main/docker-compose.dev.yml">docker file</a>, which will spin up both Rocket.Chat and MongoDB automatically for a development environment. This will save you a lot of time.</p>
<p>You can either clone <a target="_blank" href="https://github.com/freeCodeCamp/chat-config/blob/main/docker-compose.dev.yml">this repository</a>, or manually create your own docker file based on our configuration. This tutorial will assume that you are using our existing docker file.</p>
<blockquote>
<p>Note: If you do not have docker installed, you will need to install it. The installation process is different for each operating system. I personally use Windows 10, so I installed <a target="_blank" href="https://www.docker.com/products/docker-desktop">the Docker desktop client</a> and had to enable <code>hardware virtualisation</code> in my BIOS.</p>
</blockquote>
<p>Within your Rocket.Chat directory, create a <code>.env</code> file and insert the following contents:</p>
<pre><code class="lang-env">COMPOSE_FILE=docker-compose.dev.yml
PORT=3000
ROOT_URL=http://localhost:3000
ROCKETCHAT_VERSION=latest
</code></pre>
<p>Then open your terminal pointed at that same directory and run:</p>
<pre><code class="lang-bash">docker-compose up -d
</code></pre>
<p>You should see three success messages in your terminal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-180.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image showing the console output for the <code>docker-compose up -d</code> command. Three docker images were created, and each shows <code>done</code>.</em></p>
<p>Now if you open your browser and navigate to <code>localhost:3000</code> you should see your local Rocket.Chat instance. The first screen you see will be the Setup Wizard, which will walk you through creating your Admin account. </p>
<p>Most developers use the Admin account for root-level access to configure their chat. Because this is a local instance, your credentials' security is less important than in a live instance. </p>
<p>Fill in your information to create the admin account:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-181.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image showing the Admin Info modal, with inputs for <code>Name</code> set to Nicholas Carrigan, <code>Username</code> set to nhcarrigan, <code>Organization Email</code> set to nick@freecodecamp.org, and <code>Password</code> which is obfuscated. Below the input fields is a button labelled <code>Continue</code>.</em></p>
<p>The next screen is the Organization Info screen. This information is optional. For this tutorial, we will leave this information blank. </p>
<p>Clicking <code>Continue</code> will take you to the Server Info page. Here you set the name for your chat server (which will appear in the <code>title</code> metadata), your default language, the server type, and the 2FA setting.</p>
<p><strong>Be sure to turn off the automatic 2FA setup for your local instance or you could be locked out of your own server.</strong></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-182.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image showing Server Info modal, with inputs for <code>Site Name</code> set to fCC ChatBot tutorial, <code>Language</code> set to Default, <code>Server Type</code> with no selection made, and <code>Auto opt in new users for Two Factor via Email</code> set to No. Below the input fields are buttons labelled "Back" and "Continue".</em></p>
<p>The final step is to optionally register your server and gain access to Rocket.Chat's services such as push notifications. Note that these are paid services.</p>
<p>For the purpose of this tutorial, you can select the <code>Keep standalone</code> option. Then you can decide whether you want any paid services later.</p>
<p>After clicking <code>Continue</code>, you'll see a modal indicating that your workspace is ready to use. Then you should see your new chat room. The default channel created by the Setup Wizard is <code>general</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-183.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image showing Rocket.Chat after completing the setup wizard. Sidebar on the left shows a <code>general</code> channel, and primary windows shows a system message that "nhcarrigan has joined the channel".</em></p>
<p>If you see this, congratulations. You are half way there and now have a functional chat server.</p>
<h2 id="heading-how-to-set-up-a-bot-account-in-rocketchat">How to Set up a Bot Account in Rocket.Chat</h2>
<p>Now we need to create a bot user in our local chat server for our code to connect to.</p>
<p>Select the three dots at the top of the sidebar and choose <code>Administration</code>. Then select <code>Users</code> from the new sidebar that appears, and click the <code>+New</code> button in the top right. This opens a pane for creating a new user account.</p>
<p>Fill in the information and credentials for your bot account. </p>
<p>A few key things to note:</p>
<ul>
<li>Leave  <code>Require password change</code> and <code>Set random password and send by email</code> set to off.</li>
<li>Leave <code>Send welcome email</code> set to off.</li>
<li>Select <code>bot</code> from the <code>Roles</code> dropdown menu.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-185.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image showing the Rocket.Chat settings screen. The left sidebar shows the list of settings - <code>Users</code> has been selected. The center screen shows a list of user accounts - nhcarrigan and Rocket.Cat. The right sidebar shows the Add User interface, with inputs for <code>Name</code> set to Tutorial Bot, <code>Username</code> set to tutorial-bot, <code>Email</code> set to nhcarrigan@gmail.com, <code>Verified</code> toggled off, <code>Status Message</code> with no value, <code>Bio</code> with no value, <code>Nickname</code> with no value, <code>Password</code> which is obfuscated, <code>Require Password Change</code> which is toggled off, <code>Set random password and send by email</code> which is toggled off, <code>Roles</code> with "bot" selected, <code>Join default channels</code> which is toggled on, and <code>Send Welcome Email</code> which is toggled off.</em></p>
<blockquote>
<p>Rocket.cat is a built-in account used for system notifications (i.e. Rocket.Chat updates).</p>
</blockquote>
<p>Save the changes, and your bot account should now be created! Keep a note of the username and password, as we will need these for the code.</p>
<h2 id="heading-how-to-code-your-rocketchat-chatbot">How to Code your Rocket.Chat Chatbot</h2>
<p>Now it's time to create the code. Start with a new, empty folder for your project.</p>
<h3 id="heading-initial-rocketchat-chatbot-project-setup">Initial Rocket.Chat Chatbot Project Setup</h3>
<p>We will begin with initializing a <code>node.js</code> project. You are welcome to use <code>npm init</code> to generate a <code>package.json</code>, or you may create one manually. </p>
<p>Either way, you will need to add some specific values to the <code>scripts</code> section:</p>
<pre><code class="lang-json">  <span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"prebuild"</span>: <span class="hljs-string">"rm -rf ./prod"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"tsc"</span>,
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node ./prod/bot.js"</span>
  },
</code></pre>
<p>Next you will install your necessary dependencies. First, install the development dependencies:</p>
<pre><code class="lang-bash">npm install --save-dev typescript @types/node
</code></pre>
<p>Then, install your primary dependencies:</p>
<pre><code class="lang-bash">npm install @rocket.chat/sdk dotenv
</code></pre>
<p>Your next step is to set up the TypeScript configuration.</p>
<p>If you have installed TypeScript globally, you'll be able to call <code>tsc --init</code> and automatically generate a configuration file. Otherwise, you'll need to manually create a <code>tsconfig.json</code> file in your project's root directory.</p>
<p>Either way, these are the settings you will need for this project:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"ES5"</span>,
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"CommonJS"</span>,
    <span class="hljs-attr">"rootDir"</span>: <span class="hljs-string">"./src"</span>,
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./prod"</span>,
    <span class="hljs-attr">"strict"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"esModuleInterop"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"skipLibCheck"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"forceConsistentCasingInFileNames"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"resolveJsonModule"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"noImplicitAny"</span>: <span class="hljs-literal">false</span>,
  }
}
</code></pre>
<p>If you are using <code>git</code> for version control, you will need to create a <code>.gitignore</code> file. This file tells <code>git</code> which files/folders to ignore. In this case, you want to ignore: </p>
<ul>
<li>the compiled JavaScript in <code>prod</code></li>
<li>your Node modules</li>
<li>your <code>.env</code> secrets.</li>
</ul>
<p>Add these to your <code>.gitignore</code>:</p>
<pre><code class="lang-txt">/node_modules/
/prod/
.env
</code></pre>
<p>Speaking of secrets, you should set those up now. Create a <code>.env</code> file, and add the following values:</p>
<pre><code class="lang-txt">ROCKETCHAT_URL="localhost:3000"
ROCKETCHAT_USER="tutorial-bot"
ROCKETCHAT_PASSWORD="********"
ROCKETCHAT_USE_SSL=""
</code></pre>
<p><a target="_blank" href="https://github.com/naomis-archive/fcc-rocketchat-tutorial/tree/9cd28ab2adea2c4ce9294c0c35682031cf343b5f">View the code at this point</a>.</p>
<h3 id="heading-how-to-write-the-primary-rocketchat-chatbot-code">How to Write the Primary Rocket.Chat ChatBot Code</h3>
<p>Now it is time to write the initial bot code. Create a <code>src</code> folder within your project directory, and inside that <code>src</code> folder create a <code>bot.ts</code> file. Your file structure should now look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-9.png" alt="Image" width="600" height="400" loading="lazy">
_Image showing a file tree. From top to bottom: A <code>node_modules</code> folder, which is collapsed, a <code>src</code> folder which contains a <code>bot.ts</code> file, a <code>.env</code> file, a <code>.gitignore</code> file, a <code>.package-lock.json</code> file, a <code>package.json</code> file, and a <code>tsconfig.json</code> file. The files show they are being tracked by <code>git</code>, except the <code>node_modules</code> folder and <code>.env</code> file._</p>
<blockquote>
<p>The <code>package-lock.json</code> file is created/updated by <code>npm</code> whenever you run <code>install</code>. This should be committed to your repository too, as it is required for the <code>npm ci</code> command.</p>
</blockquote>
<p>Within your <code>bot.ts</code> file you will write the basic code that powers your bot. Start with your necessary imports:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { api, driver } <span class="hljs-keyword">from</span> <span class="hljs-string">"@rocket.chat/sdk"</span>;
<span class="hljs-keyword">import</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">"dotenv"</span>;
</code></pre>
<p>Because <code>node</code> doesn't load environment variables automatically, you need to call <code>dotenv</code>'s <code>config()</code> method to bring your <code>.env</code> values into the node process:</p>
<pre><code class="lang-ts">dotenv.config();
</code></pre>
<p>Now you can extract those variables from the node environment. Use destructuring to grab the values:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> {
  ROCKETCHAT_URL,
  ROCKETCHAT_USER,
  ROCKETCHAT_PASSWORD,
  ROCKETCHAT_USE_SSL,
} = process.env;
</code></pre>
<p>Aside from <code>ROCKETCHAT_USE_SSH</code>, these environment values are <em>required.</em> Missing one will cause the code you write to error out, so you need to add a step to verify that all of these values are present.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">if</span> (!ROCKETCHAT_URL || !ROCKETCHAT_USER || ROCKETCHAT_PASSWORD) {
  <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Missing required environment variables."</span>);
  process.exit(<span class="hljs-number">1</span>);
}
</code></pre>
<p>Now you can use the Rocket.Chat SDK to connect your bot to the account you created.</p>
<p>Because the methods in the SDK are asynchronous, you will use an anonymous immediately-invoked function expression (IIFE) to enable <code>async/await</code> features.</p>
<pre><code class="lang-ts">(<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-comment">// Nothing here yet</span>
})();
</code></pre>
<blockquote>
<p>These next steps will be written inside this function.</p>
</blockquote>
<p>First, determine if your bot should use SSL to connect to the chat server. If your chat server uses <code>HTTPS://</code>, this should be set to <code>true</code>. Because you are developing locally, this is set to <code>false</code> as <code>localhost</code> does not have an HTTPS protocol.</p>
<p>To ensure your code would work in a production environment as well, you can dynamically set this value based on your environment variables:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> ssl = !!ROCKETCHAT_USE_SSL;
</code></pre>
<p>Next, use the SDK <code>driver</code> to interface with your chat server. Connect the <code>driver</code> to your server:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">await</span> driver.connect({ host: ROCKETCHAT_URL, useSsl: ssl });
</code></pre>
<p>Login as the bot account:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">await</span> driver.login({
    username: ROCKETCHAT_USER,
    password: ROCKETCHAT_PASSWORD,
  });
<span class="hljs-keyword">await</span> api.login({ username: ROCKETCHAT_USER, password: ROCKETCHAT_PASSWORD });
</code></pre>
<p>Have the bot join your <code>general</code> room, to handle instances where the bot isn't already in the room. Also tell the bot to listen for messages with the <code>subscribeToMessages()</code> method.</p>
<pre><code class="lang-ts">  <span class="hljs-keyword">await</span> driver.joinRooms([<span class="hljs-string">"general"</span>]);
  <span class="hljs-keyword">await</span> driver.subscribeToMessages();
</code></pre>
<p>Finally, have the bot send a message when it comes online (so you can confirm connection status).</p>
<pre><code class="lang-ts">  <span class="hljs-keyword">await</span> driver.sendToRoom(<span class="hljs-string">"I am alive!"</span>, <span class="hljs-string">"general"</span>);
</code></pre>
<p>Now, build and run the code. Call these necessary scripts in your terminal:</p>
<pre><code class="lang-bash">npm run build
npm run start
</code></pre>
<p>After some built-in logging from the SDK, you should see the bot send its online message in your chat server.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-10.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image depicting a Rocket.Chat message. The message was sent by <code>tutorial-bot</code>, which has the <code>bot</code> role by its name. The message text reads <code>I am alive!</code>.</em></p>
<p><a target="_blank" href="https://github.com/naomis-archive/fcc-rocketchat-tutorial/tree/216a9a20a4872670d838a475c0140fa1110638d3">View the code at this point</a>.</p>
<h3 id="heading-how-to-write-the-command-handler">How to Write the Command Handler</h3>
<p>Your bot will now connect to the chat server and listen for messages, but it does not have any functions.</p>
<p>Before you can add commands, you need to build the infrastructure to handle those commands.</p>
<p>First, tell the bot to handle messages. Just after your <code>subscribeToMessages()</code> call, add a line to handle responding to messages:</p>
<pre><code class="lang-ts">driver.reactToMessages();
</code></pre>
<p>You'll see an error in your Intellisense, because the <code>reactToMessages()</code> method expects a callback function.</p>
<p>You could write the callback function within this method directly, but instead you will modularise your code and create an exported handler. This keeps your code cleaner and more maintainable.</p>
<p>Create a folder called <code>commands</code> within your <code>src</code> folder, and add two files: <code>CommandHandler.ts</code> and <code>CommandList.ts</code>. Within the <code>CommandList.ts</code> file, we are going to add a single line for now:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CommandList = [];
</code></pre>
<p>As you build commands, you will add them to this array to be able to iterate through them in our handler.</p>
<p>Now you need to write your handler's logic in the <code>CommandHandler.ts</code> file.</p>
<p>Start with your required imports:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { driver } <span class="hljs-keyword">from</span> <span class="hljs-string">"@rocket.chat/sdk"</span>;
<span class="hljs-keyword">import</span> { IMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">"@rocket.chat/sdk/dist/config/messageInterfaces"</span>;
<span class="hljs-keyword">import</span> { CommandList } <span class="hljs-keyword">from</span> <span class="hljs-string">"./CommandList"</span>;
</code></pre>
<p>Define the command handler function:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CommandHandler = <span class="hljs-keyword">async</span> (
    err: unknown,
    messages: IMessage[]
): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; =&gt; {
    <span class="hljs-comment">// Code will go here.</span>
}
</code></pre>
<p>Add some error handling.</p>
<pre><code class="lang-ts">  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.error(err);
    <span class="hljs-keyword">return</span>;
  }
  <span class="hljs-keyword">const</span> message = messages[<span class="hljs-number">0</span>];
  <span class="hljs-keyword">if</span> (!message.msg || !message.rid) {
    <span class="hljs-keyword">return</span>;
  }
</code></pre>
<p>If you see an error in the <code>err</code> parameter, you need to <code>return</code> early.</p>
<p>The <code>messages</code> parameter takes an array of messages, but you want to react to the <em>first</em> message, so we extract it from that array as <code>message</code>.</p>
<p>Then, for TypeScript's assertion handling, you need to exit early if certain properties are missing or undefined. In this case, <code>message.msg</code> is the text content of the message, and <code>message.rid</code> is the ID of the room the message was received in.</p>
<p>For cleaner/more readable code, you can destructure some values out of the message object. Get the room's name from the <code>rid</code> value - the SDK includes a method for doing just this. Also get the prefix and the command that is called.</p>
<pre><code class="lang-ts">  <span class="hljs-keyword">const</span> roomName = <span class="hljs-keyword">await</span> driver.getRoomName(message.rid);
  <span class="hljs-keyword">const</span> [prefix, commandName] = message.msg.split(<span class="hljs-string">" "</span>);
</code></pre>
<p>Add the logic to iterate through our array of commands. TypeScript will identify some errors due to missing structures, but you can ignore those for now as you have not written the commands yet.</p>
<pre><code class="lang-ts">  <span class="hljs-keyword">if</span> (prefix === <span class="hljs-string">"!fCC"</span>) {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> Command <span class="hljs-keyword">of</span> CommandList) {
      <span class="hljs-keyword">if</span> (commandName === Command.name) {
        <span class="hljs-keyword">await</span> Command.command(message, roomName);
        <span class="hljs-keyword">return</span>;
      }
    }
    <span class="hljs-keyword">await</span> driver.sendToRoom(
      <span class="hljs-string">`I am sorry, but \`<span class="hljs-subst">${commandName}</span>\` is not a valid command.`</span>,
      roomName
    );
  }
</code></pre>
<p>This block of code might be a bit confusing as we have not established how commands work yet.</p>
<p>First, the bot determines if the message begins with the correct prefix. If it does not, the bot will ignore the message.</p>
<p>Then the bot iterates through the list of commands, and if it finds a command for which the <code>name</code> value matches the command name sent in the message, it will run that command.</p>
<p>If it does not find <em>any</em> matching commands, it will send a response in the room that the command was not valid.</p>
<p>Before you move on to a command, head back to the <code>reactToMessages()</code> call in the <code>bot.ts</code> file and pass your new handler as the callback:</p>
<pre><code class="lang-ts">  driver.reactToMessages(CommandHandler);
</code></pre>
<p>You may need to manually import it:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { CommandHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">"./commands/CommandHandler"</span>;
</code></pre>
<p><a target="_blank" href="https://github.com/naomis-archive/fcc-rocketchat-tutorial/tree/b3e62ec3ad4215f4081293077774b3e81f67a52c">View the code at this point</a>.</p>
<h3 id="heading-how-to-write-a-command">How to Write a Command</h3>
<p>TypeScript offers an <code>interface</code> feature which can be used to define an object structure. </p>
<p>In your <code>src</code> folder, create an <code>interfaces</code> folder, and create a <code>CommandInt.ts</code> file. </p>
<p>Inside that file, you will define your command type. First, import the message type again.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { IMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">"@rocket.chat/sdk/dist/config/messageInterfaces"</span>;
</code></pre>
<p>Now build the exported interface for the command definitions.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> CommandInt {
    name: <span class="hljs-built_in">string</span>;
    description: <span class="hljs-built_in">string</span>;
    command: <span class="hljs-function">(<span class="hljs-params">message: IMessage, room: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt;
}
</code></pre>
<p>Congratulations! You are now ready to build the <code>ping</code> command.</p>
<p>Within your <code>src/commands</code> folder, create a <code>ping.ts</code> file. Start with your necessary imports: the Rocket.Chat driver and your new command interface.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { driver } <span class="hljs-keyword">from</span> <span class="hljs-string">"@rocket.chat/sdk"</span>;
<span class="hljs-keyword">import</span> { CommandInt } <span class="hljs-keyword">from</span> <span class="hljs-string">"../interfaces/CommandInt"</span>;
</code></pre>
<p>Define and export the command:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ping: CommandInt = {
    name: <span class="hljs-string">"ping"</span>,
    description: <span class="hljs-string">"Pings the bot."</span>,
    command: <span class="hljs-keyword">async</span> (message, room) =&gt; {
        <span class="hljs-comment">// Code will go here.</span>
    }
}
</code></pre>
<p>Let's have the bot respond with "Pong!" when this command is called. Inside the function, replace the comment with:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">await</span> driver.sendToRoom(<span class="hljs-string">"Pong!"</span>, room);
</code></pre>
<p>Now, load this command in your list of commands. Open the <code>CommandList.ts</code> file, where you will import our new command and include it in the array.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { ping } <span class="hljs-keyword">from</span> <span class="hljs-string">"./ping"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CommandList = [ping];
</code></pre>
<p>With this, you should see the errors in the <code>CommandHandler.ts</code> file disappear as well, because TypeScript is inferring that the <code>CommandList</code> array contains <code>CommandInt</code> types.</p>
<p>For extra type safety, and to ensure you do not accidentally add values to your <code>CommandList</code> that aren't proper <code>CommandInt</code> objects, explicitly type this variable.</p>
<pre><code><span class="hljs-keyword">import</span> { CommandInt } <span class="hljs-keyword">from</span> <span class="hljs-string">"../interfaces/CommandInt"</span>;
<span class="hljs-keyword">import</span> { ping } <span class="hljs-keyword">from</span> <span class="hljs-string">"./ping"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CommandList: CommandInt[] = [ping];
</code></pre><p>Run your <code>build</code> and <code>start</code> scripts again to test this new feature.</p>
<p>Call your <code>ping</code> command in the chat room. You should see a successful response:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-11.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image depicting a Rocket.Chat conversation. The first message was sent by <code>nhcarrigan</code>, who has the <code>Admin</code> role by his name. The first message content reads "!fCC ping". The second message was sent by <code>tutorial-bot</code>, which has the <code>Bot</code> role by its name. The second message content reads "Pong!".</em></p>
<p>Call a <code>pong</code> command. You should see that the bot identifies it is not a valid command:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-12.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image depicting a Rocket.Chat conversation. The first message was sent by <code>nhcarrigan</code>, who has the <code>Admin</code> role by his name. The first message content reads "!fCC pong". The second message was sent by <code>tutorial-bot</code>, which has the <code>Bot</code> role by its name. The second message content reads "I am sorry, but <code>pong</code> is not a valid command".</em></p>
<p><a target="_blank" href="https://github.com/naomis-archive/fcc-rocketchat-tutorial/tree/df645aa39fbb9f18513128cfb5b55b804719ee78">View our final code</a>.</p>
<h2 id="heading-further-exploration">Further Exploration</h2>
<p>Congratulations! You have now successfully built a basic Rocket.Chat chatbot.</p>
<p>If you would like to explore further features and command implementations, feel free to browse <a target="_blank" href="https://github.com/nhcarrigan/rocketchat-bot">our live bot's codebase</a>. </p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
