← Back to Blogs

Portfolio Project

Building a Real-Time Chat App with the Twilio API

The "Chat App" is a rite of passage for every developer. Here is how to build a production-ready messaging service using Twilio's Conversations API in less than 100 lines of code.

Building a chat app from scratch using raw WebSockets is a great learning exercise, but it's a nightmare to scale. Handling offline messages, push notifications, and media storage is hard.

That is why smart developers use the Twilio Conversations API. It handles the infrastructure so you can focus on the UI. In this guide, we will build a simple group chat room.

Prerequisites

1. The Backend (Token Generation)

Twilio uses a secure "Access Token" system. Your frontend (React/Vanilla JS) never touches your API keys. Instead, it asks your backend for a temporary token.

server.js (Node/Express)
const express = require('express');
const AccessToken = require('twilio').jwt.AccessToken;
const ChatGrant = AccessToken.ChatGrant;

const app = express();

app.get('/token', (req, res) => {
  const identity = req.query.identity || 'Anonymous';

  // Create a "Grant" for the Chat service
  const chatGrant = new ChatGrant({
    serviceSid: process.env.TWILIO_SERVICE_SID,
  });

  // Create an Access Token
  const token = new AccessToken(
    process.env.TWILIO_ACCOUNT_SID,
    process.env.TWILIO_API_KEY,
    process.env.TWILIO_API_SECRET,
    { identity: identity }
  );

  token.addGrant(chatGrant);

  // Return the token to the browser
  res.send(token.toJwt());
});

app.listen(3000, () => console.log('Server running on port 3000'));

2. The Frontend (Connecting to Twilio)

On the client side, we use the Twilio SDK to connect to the WebSocket channel. This automatically handles connection drops and message syncing.

client.js (Frontend)
import { Client } from '@twilio/conversations';

// 1. Fetch token from your backend
const response = await fetch('/token?identity=User1');
const token = await response.text();

// 2. Initialize the Twilio Client
const client = new Client(token);

client.on('stateChanged', (state) => {
  if (state === 'initialized') {
    console.log("Connected to Twilio!");
    joinGeneralChannel();
  }
});

async function joinGeneralChannel() {
  try {
    // Try to join the 'general' channel
    const conversation = await client.getConversationByUniqueName('general');
    console.log("Found channel!");
    setupListeners(conversation);
  } catch {
    // If not found, create it
    const conversation = await client.createConversation({
      uniqueName: 'general', 
      friendlyName: 'General Chat'
    });
    await conversation.join();
    setupListeners(conversation);
  }
}

3. Handling Messages

Once connected, sending and receiving messages is event-driven. This is the core of the "Real-Time" experience.

client.js (Cont.)
function setupListeners(conversation) {
  // 1. Listen for new messages
  conversation.on('messageAdded', (message) => {
    console.log("New Message:", message.body);
    // Append to your HTML UI here
    appendMessageToUI(message.author, message.body);
  });

  // 2. Send a message (e.g., from a form input)
  document.getElementById('send-btn').addEventListener('click', () => {
    const text = document.getElementById('input').value;
    conversation.sendMessage(text);
  });
}

Why this project matters

Adding "Real-Time Chat" to your portfolio proves you understand: