Master Code
On The Go

Learn. Practice. Build.

AI Project Guide

Build an AI Chatbot

Build a working chatbot with Node.js, Express, and OpenAI. Includes conversation memory, streaming responses, and a clean HTML frontend. Under 45 minutes.

📋 What You'll Need

What you'll build: A chatbot that remembers conversation context. Backend with Express, frontend with plain HTML/JS. Streaming responses like ChatGPT.

1

Set Up the Project

mkdir ai-chatbot
cd ai-chatbot
npm init -y
npm install express openai dotenv
npm install -D nodemon

Create .env:

OPENAI_API_KEY=sk-your-key-here

Add .env and node_modules to .gitignore.

2

Build the Backend

Create server.js:

import 'dotenv/config';
import express from 'express';
import OpenAI from 'openai';

const app = express();
app.use(express.json());
app.use(express.static('public'));

const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY
});

// Store conversation history per session
const sessions = {};

app.post('/api/chat', async (req, res) => {
    const { message, sessionId } = req.body;

    if (!message || !sessionId) {
        return res.status(400).json({ error: 'Missing message or sessionId' });
    }

    // Initialize session if new
    if (!sessions[sessionId]) {
        sessions[sessionId] = [
            {
                role: 'system',
                content: 'You are a helpful coding tutor. Keep answers concise.'
            }
        ];
    }

    // Add user message
    sessions[sessionId].push({ role: 'user', content: message });

    try {
        const response = await openai.chat.completions.create({
            model: 'gpt-4o-mini',
            messages: sessions[sessionId],
            temperature: 0.7
        });

        const reply = response.choices[0].message.content;

        // Add assistant reply to history
        sessions[sessionId].push({ role: 'assistant', content: reply });

        res.json({ reply });
    } catch (err) {
        console.error(err);
        res.status(500).json({ error: 'Failed to get response' });
    }
});

const PORT = 3000;
app.listen(PORT, () => {
    console.log(`Chatbot running at http://localhost:${PORT}`);
});

Add "type": "module" to package.json for ES module syntax.

3

Build the Frontend

Create public/index.html:

<!DOCTYPE html>
<html>
<head>
    <title>AI Chatbot</title>
    <style>
        body { font-family: system-ui; max-width: 600px; margin: 40px auto; padding: 20px; }
        #messages { border: 1px solid #ddd; border-radius: 8px; padding: 16px; min-height: 400px; margin-bottom: 12px; }
        .user { text-align: right; margin: 8px 0; }
        .user span { background: #3b82f6; color: white; padding: 8px 12px; border-radius: 12px; display: inline-block; }
        .bot { text-align: left; margin: 8px 0; }
        .bot span { background: #f1f5f9; padding: 8px 12px; border-radius: 12px; display: inline-block; }
        input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px; }
    </style>
</head>
<body>
    <h1>AI Chatbot</h1>
    <div id="messages"></div>
    <input id="input" placeholder="Type a message and press Enter" />

    <script>
        const sessionId = crypto.randomUUID();
        const messages = document.getElementById('messages');
        const input = document.getElementById('input');

        input.addEventListener('keydown', async (e) => {
            if (e.key !== 'Enter') return;
            const text = input.value.trim();
            if (!text) return;

            messages.innerHTML += `<div class="user"><span>${text}</span></div>`;
            input.value = '';

            const loadingId = 'loading-' + Date.now();
            messages.innerHTML += `<div class="bot" id="${loadingId}"><span>...</span></div>`;

            const res = await fetch('/api/chat', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ message: text, sessionId })
            });
            const data = await res.json();
            document.getElementById(loadingId).innerHTML =
                `<span>${data.reply}</span>`;
        });
    </script>
</body>
</html>
4

Run It

npx nodemon server.js

Open http://localhost:3000. Type a message. The bot replies. Ask a follow-up — it remembers the context. 🎉

5

Add Streaming (ChatGPT Feel)

Replace the /api/chat handler with a streaming version:

app.post('/api/chat', async (req, res) => {
    const { message, sessionId } = req.body;
    if (!message || !sessionId) return res.status(400).end();

    if (!sessions[sessionId]) {
        sessions[sessionId] = [
            { role: 'system', content: 'You are a helpful coding tutor.' }
        ];
    }
    sessions[sessionId].push({ role: 'user', content: message });

    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    const stream = await openai.chat.completions.create({
        model: 'gpt-4o-mini',
        messages: sessions[sessionId],
        stream: true
    });

    let full = '';
    for await (const chunk of stream) {
        const token = chunk.choices[0]?.delta?.content || '';
        if (token) {
            full += token;
            res.write(`data: ${JSON.stringify({ token })}\n\n`);
        }
    }
    res.write('data: [DONE]\n\n');
    res.end();
    sessions[sessionId].push({ role: 'assistant', content: full });
});

Frontend needs an EventSource to read the stream — but the effect is text appearing token-by-token like ChatGPT.

🧠 Key Concepts

🚫 Common Beginner Mistakes

🎯 Practice Challenges

🎯 What's Next?

← All How-To Guides