Build an AI Chat Assistant in React
Full‑Stack AI: React + Express + OpenAI
By AI Learning Assistant · React · Express · OpenAI API · Chat History
🤖 YOUR KIND AI LEARNING ASSISTANT
Welcome to Day 25! Today we're building something truly exciting — your own AI chat assistant, powered by OpenAI. You'll connect a React frontend to a Node.js backend, send messages, and get intelligent responses. I'll explain every step, from CORS to conversation history. Don't worry — we'll go slowly. You've got this! 🚀
What We Are Building
A FULL‑STACK AI CHAT APPLICATION
We'll create a chat interface where you can type a question about JavaScript or React, and an AI assistant (powered by OpenAI's GPT) will answer. The app has two parts:
- Backend (Express server) — Holds your OpenAI API key securely, receives messages from React, calls the OpenAI API, and sends back the AI's reply.
- Frontend (React app) — Displays the chat UI, sends user messages to the backend, shows typing indicators, and displays the AI's responses.
🎯 WHY TWO SERVERS? (REACT + EXPRESS)
Your API key must stay secret — never put it in React code (anyone can see it!). So we put it in a Node.js backend. The React app (running on port 3000) talks to the backend (port 5000). The backend talks to OpenAI. This is a standard full‑stack pattern.
Step 1 — Building the Backend (server.js)
EXPRESS · CORS · OPENAI SDK · ENVIRONMENT VARIABLES
First, create a new folder for your backend (e.g., `webbo3-ai-backend`). Inside, initialize npm and install dependencies:
npm init -y npm install express cors openai dotenv
📜 server.js — Complete Backend Code (with line‑by‑line comments)
// ============================================ // AI Chat Backend (Express + OpenAI) // ============================================ // Load environment variables from .env file (keeps API key secret) require("dotenv").config(); // Import Express (web framework for Node.js) const express = require("express"); // CORS allows your React app (port 3000) to talk to this server (port 5000) // Without CORS, browsers block requests from different origins. const cors = require("cors"); // OpenAI SDK — official library to call GPT models const OpenAI = require("openai"); // Create the Express app const app = express(); // Enable CORS for all routes (simplifies development) app.use(cors()); // Enable JSON body parsing — so we can read req.body.message app.use(express.json()); // Initialize OpenAI client with API key from environment variables const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // ============================================ // POST /chat — receives a user message and returns AI reply // ============================================ app.post("/chat", async (req, res) => { // Extract the message sent from React frontend const { message } = req.body; // Call OpenAI's chat completion API const response = await client.chat.completions.create({ model: "gpt-3.5-turbo", messages: [ { // System prompt — gives the AI a personality and role role: "system", content: "You are Webbo3's AI tutor. Help students learn JavaScript and React. Be encouraging and keep answers short (1‑2 paragraphs)." }, { role: "user", content: message } ] }); // Extract the reply text from the response const reply = response.choices[0].message.content; // Send the reply back to React as JSON res.json({ reply }); }); // Start the server on port 5000 app.listen(5000, () => { console.log("AI server running on http://localhost:5000"); });
📌 IMPORTANT: CREATE .env FILE
In your backend folder, create a file named .env with your OpenAI API key:
OPENAI_API_KEY=sk‑your‑actual‑key‑here
Then add .env to your .gitignore to keep it secret!
Step 2 — Building the Frontend (React App)
CHAT UI · STATE MANAGEMENT · FETCH TO BACKEND
Now let's create the React chat interface. Create a new React app (or use your existing one) and replace src/App.js with the code below.
📜 src/App.js — Complete React Chat Component
// ============================================ // AI Chat Assistant — React Frontend // ============================================ import { useState } from "react"; function App() { // messages array stores all conversation turns: user messages and assistant replies const [messages, setMessages] = useState([ { role: "assistant", text: "Hi! I am Webbo3 AI. Ask me anything about JavaScript!" } ]); // Current input value (what user is typing) const [input, setInput] = useState(""); // Loading state — true while waiting for AI response, shows "thinking..." const [loading, setLoading] = useState(false); // Called when user clicks Send or presses Enter const sendMessage = async () => { // Don't send empty messages if (!input.trim()) return; // Save the user's message text before clearing input const userMessage = input; // Add user message to conversation history setMessages(prev => [...prev, { role: "user", text: userMessage }]); // Clear input field setInput(""); // Show loading indicator setLoading(true); // Send the user message to our backend (port 5000) const res = await fetch("http://localhost:5000/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: userMessage }) }); // Parse the JSON response from backend const data = await res.json(); // Add assistant's reply to conversation history setMessages(prev => [...prev, { role: "assistant", text: data.reply }]); // Hide loading indicator setLoading(false); }; // Render the chat UI return ( <div style={{ padding: "40px", maxWidth: "600px", margin: "0 auto" }}> <h1>Webbo3 AI Tutor</h1> // Chat message container (scrollable) <div style={{ border: "1px solid #ccc", borderRadius: "8px", padding: "20px", height: "400px", overflowY: "scroll", marginBottom: "10px" }}> {messages.map((msg, i) => ( <div key={i} style={{ textAlign: msg.role === "user" ? "right" : "left", margin: "8px 0" }}> <span style={{ background: msg.role === "user" ? "#007bff" : "#f0a500", color: "white", padding: "8px 14px", borderRadius: "20px", display: "inline-block" }}> {msg.text} </span> </div> ))} {loading && <p>Webbo3 AI is thinking...</p>} </div> // Input area and send button <input value={input} onChange={e => setInput(e.target.value)} onKeyPress={e => e.key === "Enter" && sendMessage()} placeholder="Ask about JavaScript..." style={{ width: "80%", padding: "10px" }} /> <button onClick={sendMessage} style={{ padding: "10px 20px", background: "#007bff", color: "white", border: "none" }}>Send</button> </div> ); } export default App;
🎯 Live Simulation — Try the Chat Interface (Demo)
Hi! I am Webbo3 AI. Ask me anything about JavaScript!✨ This simulates the UI (no real AI). In your real app, it will connect to OpenAI.
💬 DEEP DIVE: CONVERSATION HISTORY THREADING
Why send full history?
🗣️ YOU ASK THE AI:
"What is conversation history threading in AI APIs and why is it important to send the full history in each request? Show me how to maintain context across multiple messages."
🤗 YOUR KIND AI ASSISTANT RESPONDS (Step‑by‑Step):
What is conversation history threading?
OpenAI's API is stateless — it doesn't remember previous messages. Each request is independent. If you only send the latest user message, the AI has no memory of what you said before. To have a real conversation, you must send the entire chat history every time.
Why is it important?
- Context awareness: The AI needs to remember earlier messages to answer follow‑up questions (e.g., "What about arrays?" after asking about functions).
- Coherent conversations: Without history, each message is isolated — the AI would answer each question as if it's the first.
- User experience: Users expect the AI to remember the conversation thread, just like a human.
How to maintain context — two approaches
Approach 1 (as in our current code): Store the entire conversation array in React state. Send only the current user message to the backend, and the backend does not store history — it uses only the single message. This works for simple Q&A but loses context for follow‑ups.
Approach 2 (full history threading): Send the entire conversation array to the backend with each request. The backend includes all previous messages in the OpenAI API call. This gives the AI full memory of the conversation.
Code Example — Maintaining Full History (in backend)
// In React: send the whole messages array const res = await fetch("/chat", { method: "POST", body: JSON.stringify({ messages: messages }) // send ALL previous messages }); // In Express backend: app.post("/chat", async (req, res) => { const { messages } = req.body; // messages is an array of {role, content} const response = await openai.chat.completions.create({ model: "gpt-3.5-turbo", messages: messages // includes system prompt + full user/assistant history }); res.json({ reply: response.choices[0].message.content }); });Important limitation: Long conversations can exceed token limits (max ~4096 tokens for gpt-3.5-turbo). For production, you may need to truncate old messages or use a sliding window.
In our current app: We only send the latest message to keep the code simple. For a true conversational AI, you would upgrade to sending the full history. The `messages` array in React already stores everything — you only need to adjust the fetch body to send it all!
📝 YOUR LEARNING JOURNAL
Today I built a full‑stack AI chat app! I learned that the backend hides the API key, CORS allows the frontend to talk to the backend, and conversation history threading is crucial for context. Our current app sends only the latest message, but I now understand how to upgrade it to send the full conversation array. The system prompt gives the AI a teaching personality. I feel like a real full‑stack developer now. Next, I'll add typing indicators and error handling. 🚀
How to Run the Complete App
TERMINAL · TWO SERVERS · TEST
# 1. BACKEND (in webbo3-ai-backend folder) node server.js # You should see: AI server running on port 5000 # 2. FRONTEND (in your React app folder, e.g., webbo3-react) npm start # React app opens on http://localhost:3000 # 3. Open your browser to http://localhost:3000 # Type a message and watch the AI respond!Quick Reference — Full‑Stack AI Chat
Concept What it does CORS Allows React app on port 3000 to call backend on port 5000 dotenv Loads API key from .env file (keeps secrets out of code) System prompt Gives the AI a personality and rules (e.g., "be encouraging") Conversation history Array of messages stored in React state Threading Sending full history to maintain context (future improvement) 🤗 YOUR KIND AI ASSISTANT — FINAL TIPS
💬 "My fetch is failing (CORS error)" → Make sure your backend is running on port 5000 and you have `app.use(cors())`.
💬 "How do I add typing animation?" → You can simulate typing by streaming the response or adding a delayed character‑by‑character effect.
💬 "What about pricing?" → GPT‑3.5‑turbo is very cheap (fractions of a cent per message). Set a spending limit in your OpenAI account.
You did it! You built a full‑stack AI chat app — from React frontend to Express backend to OpenAI. This is a portfolio‑worthy project. Be proud of yourself. See you tomorrow! 🌟
AI-Assisted JavaScript Learning · Build an AI Chat Assistant in React · Full‑Stack with OpenAI
Learn this with a real instructor
Free lessons take you a long way on your own. The full programmes add live classes, assignments, Skill Points and a certificate — built for African students.