DEV Community

brian austin
brian austin

Posted on

How to add Claude AI to any Node.js app in 5 minutes (with conversation memory)

How to add Claude AI to any Node.js app in 5 minutes (with conversation memory)

Every Claude tutorial I've seen does one of two things:

  1. Shows you a single curl request with no memory
  2. Builds a massive framework with Redis, databases, and authentication

Both miss the point. Here's the exact code I use to add Claude with persistent conversation memory to any Node.js app — in under 50 lines, no database required.

The core problem: Claude has no memory by default

Each call to the Claude API is stateless. If you just do this:

const response = await anthropic.messages.create({
  model: 'claude-opus-4-5',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'What did I just ask you?' }]
});
Enter fullscreen mode Exit fullscreen mode

Claude has no idea what came before. Every request starts fresh.

The solution is to maintain a messages array and pass the full history with each request.

Complete implementation (47 lines)

const Anthropic = require('@anthropic-ai/sdk');
const readline = require('readline');

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

// This array is the entire "memory" — grow it, trim it, persist it however you want
const conversationHistory = [];

async function chat(userMessage) {
  // Add the user's message to history
  conversationHistory.push({
    role: 'user',
    content: userMessage
  });

  const response = await client.messages.create({
    model: 'claude-opus-4-5',
    max_tokens: 1024,
    system: 'You are a helpful assistant.',
    messages: conversationHistory  // <-- pass the full history every time
  });

  const assistantMessage = response.content[0].text;

  // Add Claude's response to history for next turn
  conversationHistory.push({
    role: 'assistant',
    content: assistantMessage
  });

  return assistantMessage;
}

// Simple REPL to test it
async function main() {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

  console.log('Claude chatbot with memory. Type "quit" to exit.\n');

  const ask = () => rl.question('You: ', async (input) => {
    if (input.toLowerCase() === 'quit') return rl.close();

    const reply = await chat(input);
    console.log(`Claude: ${reply}\n`);
    ask();
  });

  ask();
}

main();
Enter fullscreen mode Exit fullscreen mode

Run it:

npm install @anthropic-ai/sdk
ANTHROPIC_API_KEY=sk-... node chat.js
Enter fullscreen mode Exit fullscreen mode

Why this works

The conversationHistory array grows with every exchange. By the 5th message, you're sending Claude messages 1-4 as context. Claude reads them all and responds as if it remembers the entire conversation.

This is exactly how the official Claude.ai interface works — it's just an array passed with every request.

Problem: history grows forever

For a real app, you can't let the array grow unbounded — you'll hit token limits and slow down responses. Here's the trimming pattern I use:

const MAX_HISTORY_MESSAGES = 20;  // Keep last 10 exchanges

async function chat(userMessage) {
  conversationHistory.push({ role: 'user', content: userMessage });

  // Trim to max length (always keep in pairs: user + assistant)
  while (conversationHistory.length > MAX_HISTORY_MESSAGES) {
    conversationHistory.splice(0, 2);  // Remove oldest exchange
  }

  const response = await client.messages.create({
    model: 'claude-opus-4-5',
    max_tokens: 1024,
    system: 'You are a helpful assistant.',
    messages: conversationHistory
  });

  const assistantMessage = response.content[0].text;
  conversationHistory.push({ role: 'assistant', content: assistantMessage });

  return assistantMessage;
}
Enter fullscreen mode Exit fullscreen mode

Always splice in pairs (remove 2 at a time) to keep the user/assistant/user/assistant alternating pattern that Claude requires.

Persist memory between sessions

Want memory to survive a server restart? Save the array to a file:

const fs = require('fs');
const HISTORY_FILE = './conversation.json';

// Load on startup
let conversationHistory = [];
if (fs.existsSync(HISTORY_FILE)) {
  conversationHistory = JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf8'));
}

async function chat(userMessage) {
  conversationHistory.push({ role: 'user', content: userMessage });

  // ... (trim + API call as before) ...

  conversationHistory.push({ role: 'assistant', content: assistantMessage });

  // Save after every exchange
  fs.writeFileSync(HISTORY_FILE, JSON.stringify(conversationHistory, null, 2));

  return assistantMessage;
}
Enter fullscreen mode Exit fullscreen mode

For multi-user apps, key the history by session ID or user ID:

const histories = {};

async function chat(userId, userMessage) {
  if (!histories[userId]) histories[userId] = [];
  const history = histories[userId];

  history.push({ role: 'user', content: userMessage });
  // ... trim, API call, push response ...
  return assistantMessage;
}
Enter fullscreen mode Exit fullscreen mode

The flat-rate advantage for development

While building and testing this, I made hundreds of API calls iterating on the trimming logic, testing edge cases, and debugging the alternating role pattern. With per-token billing, that development phase would have cost real money.

I use SimplyLouie — a flat-rate Claude API wrapper at $2/month — specifically because I can hammer the API during development without watching a billing meter. The rate limits are generous enough for any solo project, and there's no invoice surprise at the end of the month.

For production apps at scale, you'd use official Anthropic API keys. But for prototyping, learning, and side projects, the flat-rate model removes a lot of anxiety.

What I'd do next

  • Add streaming responses (see my previous article for the full streaming implementation)
  • Add a system prompt that changes based on the user's context (detected language, subscription tier, etc.)
  • Implement per-user rate limiting before the Claude call to prevent a single user from burning your quota

The full code above is 47 lines and runs with zero external dependencies beyond the Anthropic SDK.

What are you building with Claude? Drop it in the comments — I'm curious what real projects look like.


SimplyLouie is a $2/month flat-rate Claude API. 50% of revenue goes to animal rescue. simplylouie.com

Top comments (0)