Day 3 of 5
⏱ ~60 minutes
Build AI Apps with JavaScript — Day 3

Express Chat Server: Multi-User Conversations

Day 3 builds a production-ready Express chat server with session management, conversation history, and streaming — everything a real chat app needs.

The Full Chat Server

server.js
import express from 'express';
import Anthropic from '@anthropic-ai/sdk';
import * as dotenv from 'dotenv';
import crypto from 'crypto';
dotenv.config();

const app = express();
const client = new Anthropic();
const sessions = new Map();

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

// Create or retrieve session
app.post('/session', (req, res) => {
  const sessionId = crypto.randomUUID();
  sessions.set(sessionId, { messages: [], createdAt: Date.now() });
  res.json({ sessionId });
});

// Streaming chat endpoint
app.post('/chat', async (req, res) => {
  const { message, sessionId } = req.body;
  
  if (!sessions.has(sessionId)) {
    return res.status(400).json({ error: 'Invalid session' });
  }
  
  const session = sessions.get(sessionId);
  session.messages.push({ role: 'user', content: message });
  
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  
  let fullResponse = '';
  
  try {
    const stream = await client.messages.stream({
      model: 'claude-opus-4-5',
      max_tokens: 2048,
      system: 'You are a helpful assistant. Be clear and concise.',
      messages: session.messages
    });
    
    for await (const chunk of stream) {
      if (chunk.type === 'content_block_delta' && 
          chunk.delta.type === 'text_delta') {
        fullResponse += chunk.delta.text;
        res.write(`data: ${JSON.stringify({ text: chunk.delta.text })}

`);
      }
    }
    
    session.messages.push({ role: 'assistant', content: fullResponse });
    res.write('data: [DONE]

');
    
  } catch (error) {
    res.write(`data: ${JSON.stringify({ error: error.message })}

`);
  }
  
  res.end();
});

// Clear session history
app.delete('/session/:id', (req, res) => {
  sessions.delete(req.params.id);
  res.json({ success: true });
});

// Cleanup old sessions every hour
setInterval(() => {
  const cutoff = Date.now() - 60 * 60 * 1000;
  for (const [id, session] of sessions.entries()) {
    if (session.createdAt < cutoff) sessions.delete(id);
  }
}, 60 * 60 * 1000);

app.listen(3000, () => console.log('Chat server on port 3000'));
Day 3 Exercise
Test Multi-User Sessions
  1. Run the server and open two browser tabs.
  2. Start a conversation in tab 1 about topic A. Start a conversation in tab 2 about topic B.
  3. Verify the conversations are independent — tab 1 does not know about tab 2's conversation.
  4. Test the session delete endpoint — verify history clears when called.
  5. Add a custom system prompt and test how it changes the chatbot's behavior.

Day 3 Summary

  • UUID session IDs isolate conversations per user.
  • Sessions stored in a Map — upgrade to Redis or a database for multi-server deployments.
  • Periodic session cleanup prevents memory from growing unboundedly.
  • Error handling inside the stream ensures the client gets an error event, not a broken stream.
Finished this lesson?