How to Create Real-time Chat App With Twilio-Conversation?

Khushbu Raval
Simform Engineering
5 min readMay 3, 2023

Let’s explore the power of Twilio chat using JavaScript SDK to build a scalable and reliable chat application.

This article will help you integrate a chat app with Twilio’s conversation API in angular, but that’s not it; you can find more use cases or examples as mentioned here.

Now let’s start and create an example chat app to demonstrate the same.

Twilio-Conversation integration with Angular

Prerequisites

  • Node Js
  • Package manager: npm
  • Create one Free Twilio account to get started using the same
  • Angular CLI

Steps:

Let’s set up the Backend code first

Run the below-mentioned commands to create a new NodeJS project, ‘chat-backend.’

mkdir chat-backend
cd chat-backend
npm init

Now let’s install the required packages express, dotenv & Twilio.

npm i express
npm i dotenv
npm i twilio

Create a .env file and add all the required config variables from the Twilio console

TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_SERVICE_SID=
TWILIO_API_KEY=
TWILIO_API_SECRET=

Now let’s set up index.js & chat.js files as mentioned below

index.js

require('dotenv').config();
const express = require("express");
var app = express();
const chat = require('./chat');

app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Access-Control-Allow-Credentials', true);
next();
});

app.get("/", function (request, response) {
response.send("Hello World!");
})

app.get("/getToken", function (request, response) {
let token = chat.getToken(request.query.userName);
response.send(JSON.stringify(token));
})

app.listen(3000, function () {
console.log("Started application on port %d", 3000);
});

chat.js

const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const serviceSid = process.env.TWILIO_SERVICE_SID;
const apiKey = process.env.TWILIO_API_KEY;
const apiSecret = process.env.TWILIO_API_SECRET;
const client = require('twilio')(accountSid, authToken);

// Create a twilio access token
function getToken(identity) {
const AccessToken = require('twilio').jwt.AccessToken;
const ChatGrant = AccessToken.ChatGrant;

const chatGrant = new ChatGrant({
serviceSid: serviceSid,
});
const token = new AccessToken(
accountSid,
apiKey,
apiSecret,
{
identity: identity,
ttl: 3600
}
);

token.addGrant(chatGrant);
return token.toJwt();
}

module.exports = {getToken};

Here we are done with the backend code needed to work with the chat app; let’s run the server and verify the same

npm start

As we are done with the backend setup, now let’s move on to creating an Angular project

Run the below-mentioned commands to create a new angular project with angular-cli

ng new twilio-chat-example
cd twilio-chat-example
ng serve

let’s Install the Twilio conversation package and start working on JS SDK

npm install --save @twilio/conversations

Create a new component to work with twilio JS SDK

ng g c chats

Firstly you’ll need an access token to set up the client and listen to client events. Call created API /getToken with a unique username OR userId, which will return an access token to use and listen to client events.

connectTwilio() {
this.isLoading = true;
this.getToken().subscribe(token => {
this.client = new Client(token);
this.isLoading = false;
this.listenToEvents();
}, error => console.log(error));
}

Once the client is initialized, you can fetch the user’s subscribed channels, update the unread count, messages, and list newly created chats with given events handling.

Listen to the tokenAboutToExpire & tokenExpired event to renew created token and continue working with Twilio services.

 listenToEvents() {
this.client.on('initialized', () => {
console.log('Client initialized');
this.fetchUserChats();
});

this.client.on('initFailed', (error: any) => {
console.log('Client initialization failed: ', error);
});

this.client.on('connectionStateChanged', (state: ConnectionState) => {
console.log('Connection state change: ', state);
});

this.client.on('connectionError', (error: any) => {
console.log('Connection error: ', error);
});

this.client.on('tokenAboutToExpire', () => {
console.log('About to expire');
this.getToken().subscribe(async (token) => {
this.client = await this.client.updateToken(token);
})
});

this.client.on('tokenExpired', () => {
console.log('Token expired');
this.client.removeAllListeners();
this.connectTwilio();
});

this.client.on('conversationAdded', (conv: Conversation) => {
setTimeout(async () => {
if (conv.dateCreated && conv.dateCreated > this.currentTime) {
console.log('Conversation added', conv);
await conv.setAllMessagesUnread();
let newChat = {
chat: conv,
unreadCount: 0,
lastMessage: ''
}
this.chatList = [newChat,...this.chatList];
}
}, 500);
});

this.client.on('messageAdded', async (msg: Message) => {
console.log('Message added', msg);
if (this.currentConversation && this.currentConversation.sid === msg.conversation.sid) {
this.messages.push(msg);
await this.currentConversation.updateLastReadMessageIndex(msg.index);
this.chatList = this.chatList.map(el => {
if(el.chat.sid === this.currentConversation.sid){
el.lastMessage = msg.body;
}
return el;
});
} else {
this.chatList = this.chatList.map(el => {
if(el.chat.sid === msg.conversation.sid){
el.lastMessage = msg.body;
el.unreadCount++;
}
return el;
});
}
});

this.client.on('typingStarted', (user: Participant) => {
console.log('typing..', user);
if (user.conversation.sid === this.currentConversation.sid) this.isTyping = true;
});

this.client.on('typingEnded', (user: Participant) => {
console.log('typing end..', user);
if (user.conversation.sid === this.currentConversation.sid) this.isTyping = false;
});
}

Fetch users’ subscribed conversations for the listing page

fetchUserChats() {
this.isLoading = true;
this.client.getSubscribedConversations().then(convs => {
let chats:any = [...convs.items];
chats.forEach( async (chat:Conversation) => {
let obj = {
chat: chat,
unreadCount: await chat.getUnreadMessagesCount(),
lastMessage: (await chat.getMessages()).items[chat.lastReadMessageIndex || 0].body
}
this.chatList.push(obj);
})
this.isLoading = false;
}).catch(error => console.log(error));
}

As mentioned below, create a new chat with the client’s createConversation function. Create a new conversation — join the same — add other user/s by their identity.

newChat() {
this.isLoading = true;
this.client.getUser(this.newUser).then(res => {
this.client.createConversation({
friendlyName: `${this.newUser}-${this.userName}`,
}).then(async (channel: Conversation) => {
channel.join().then(async () => {
await channel.setAllMessagesUnread();
channel.add(this.newUser).then(() => {
this.currentConversation = channel;
this.openChat(channel);
this.scrollToBottom();
this.isLoading = false;
})
});
}).catch(error => console.log(error));
}).catch(err => {
this.isLoading = false;
this.error = 'User not found in Twilio';
setTimeout(() => {
this.error = null;
}, 2000);
this.newUser = null;
});
}

Send a message to other conversation participants.

sendMessage() {
this.currentConversation.sendMessage(this.message).then(result => {
this.message = '';
}).catch(err => console.log(err));
}

Fetch messages: when you click on chat from the listing, you need to fetch messages as shown below and reset the unread message count to the last message’s index.

Note: implement load messages on the scroll as getMessages will return 30 items at a time & implement a similar for fetching user’s conversations too.

fetchMessages(skip?: number) {
this.isLoading = true;
this.currentConversation.getMessages(30, skip).then(async (result) => {
this.messages = [...result.items, ...this.messages];
if (!skip) {
let resetTo = this.messages.length >= 1 ? this.messages[this.messages.length - 1].index : 0;
await this.currentConversation.updateLastReadMessageIndex(resetTo);
this.chatList = this.chatList.map( el => {
if(el.chat.sid == this.currentConversation.sid){
el.unreadCount = 0;
}
return el;
})
}
this.isLoading = false;
}).catch(error => {
this.isLoading = false;
console.log(error)
});
}

You’re mostly done with the basic SDK setup. Clone/ refer to this example for more information.

This is a basic example of creating a chat app with Twilio conversations. You can check out other things on its official document for attaching media, closing chats (managing conversation state), reachability indicators, working with external channels, etc.

Things to remember

  • Don’t add multiple listeners for the same event to avoid any misbehavior
  • Check out Twilio conversation limits once before creating a logical flow as per your use case
  • Have a look at Twilio’s pricing plan, as it’s paid service.

Stay tuned to Simform Engineering for the latest trends in software development ecosystem.

References

Documentation: https://www.twilio.com/docs/conversations
Example: Check out the example video on the given GitHub example link
(Frontend) https://github.com/Khushbu-2112/Twilio-chat-frontend-Example
(Backend) https://github.com/Khushbu-2112/Twilio-chat-backend-Example

--

--