Building a Chatbot from Scratch: Part 4 - Adding Intelligence

Our chatbot responds to messages. Now let's make it actually useful with pattern matching and response logic.

Series Recap

  • [Part 1](/blog/building-chatbot-from-scratch-part-1-setup): Project setup
  • [Part 2](/blog/building-chatbot-from-scratch-part-2-ui): Chat UI
  • [Part 3](/blog/building-chatbot-part-3-websockets): Real-time WebSocket communication

Now we make it smart.

Response Patterns

The simplest approach: pattern matching.

// server/responses.js
const patterns = [
  {
    match: /hello|hi|hey/i,
    responses: [
      "Hey there! How can I help?",
      "Hello! What's on your mind?",
      "Hi! Nice to chat with you."
    ]
  },
  {
    match: /how are you/i,
    responses: [
      "I'm just code, but I'm functioning optimally!",
      "All systems nominal. How about you?"
    ]
  },
  {
    match: /help|support/i,
    responses: [
      "I can help with questions about our product. What would you like to know?",
      "Sure! Try asking about pricing, features, or getting started."
    ]
  },
  {
    match: /bye|goodbye|later/i,
    responses: [
      "Take care! Come back anytime.",
      "Goodbye! Have a great day!"
    ]
  }
];

const defaultResponses = [
  "I'm not sure I understand. Could you rephrase that?",
  "Interesting! Tell me more.",
  "I'm still learning. Can you try asking differently?"
];

function getResponse(message) {
  for (const pattern of patterns) {
    if (pattern.match.test(message)) {
      const responses = pattern.responses;
      return responses[Math.floor(Math.random() * responses.length)];
    }
  }
  return defaultResponses[Math.floor(Math.random() * defaultResponses.length)];
}

module.exports = { getResponse };

Integrating with WebSocket

// server/index.js
const { getResponse } = require('./responses');

io.on('connection', (socket) => {
  socket.on('message', (userMessage) => {
    // Echo user message
    io.emit('message', {
      text: userMessage,
      sender: 'user',
      timestamp: Date.now()
    });
    
    // Generate and send bot response
    setTimeout(() => {
      const botResponse = getResponse(userMessage);
      io.emit('message', {
        text: botResponse,
        sender: 'bot',
        timestamp: Date.now()
      });
    }, 500 + Math.random() * 1000); // Simulate thinking
  });
});

The random delay makes it feel more natural.

Adding Context

Track conversation state:

const conversations = new Map();

function getContextualResponse(socketId, message) {
  const context = conversations.get(socketId) || { history: [], state: 'greeting' };
  
  context.history.push(message);
  
  // State machine for conversation flow
  if (context.state === 'greeting' && /pricing|cost/i.test(message)) {
    context.state = 'discussing_pricing';
    return "Our basic plan starts at $10/month. Want to know what's included?";
  }
  
  if (context.state === 'discussing_pricing' && /yes|sure|tell me/i.test(message)) {
    context.state = 'explaining_features';
    return "Basic includes: 5 projects, 10GB storage, and email support.";
  }
  
  conversations.set(socketId, context);
  return getResponse(message);
}

What's Next

Pattern matching works for simple bots, but for real intelligence you'd want:

  • NLP libraries (compromise.js, natural)
  • Integration with AI APIs (OpenAI, Claude)
  • Intent classification
  • Entity extraction

We'll keep this tutorial focused on fundamentals, but the architecture supports adding these later.

Try It

Clone simple-chatbot and experiment with adding your own patterns!

Next up: Part 5 - Styling and deployment.

Back to Journal