Building a chat with Twilio, lit-html, Parcel and TypeScript
Tags: typescript, lithtml, twilio, parceljs
Published: 2018-05-28 11:54:41

---
cover_image: https://thepracticaldev.s3.amazonaws.com/i/b8hzsgydpkh2dixq22c4.png
title: Building a chat with Twilio, lit-html, Parcel and TypeScript
published: true
description: Learn how to efficiently re-render DOM elements with lit-html by building a Chat application with Twilio, TypeScript and Parcel.
tags: typescript, lit-html, twilio, parceljs
canonical_url: https://www.twilio.com/blog/2018/05/building-a-chat-with-twilio-lit-html-parcel-and-typescript.html
---

_[Building a chat with Twilio, lit-html, Parcel and TypeScript](https://www.twilio.com/blog/2018/05/building-a-chat-with-twilio-lit-html-parcel-and-typescript.html) was originally published on the [Twilio Blog](https://www.twilio.com/blog) on May 14, 2018._

---

When building a web application you often reach a place where you’ll have to render a similar element multiple times and as efficiently as possible. That’s when a lot of web developers start reaching out to frameworks and libraries such as React, Vue or Angular. But what if we only want to do efficient templating and don’t want the tooling & code overhead of these libraries?

One library that aims to solve this problem is [`lit-html` by the Polymer team](https://github.com/Polymer/lit-html). It uses browser-native functionality such as [`<template/>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template) tags and [tagged template literals](https://css-tricks.com/template-literals/#article-header-id-3) to create efficient templating. Let’s take a look at how we can use it by building a [Twilio Chat](https://www.twilio.com/docs/chat) application with `lit-html`.

### Prerequisites

Before we get started make sure that you have [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed. Since we’ll be using [Twilio Chat](https://www.twilio.com/docs/chat) at one point in this application, you want to get a Twilio account as well. [Sign up for free](https://www.twilio.com/try-twilio).

### Project Setup

Start by creating a new folder for your project and initializing a Node project:

```bash
mkdir lit-twilio-chat
cd lit-twilio-chat
npm init -y
```

Before we get to the `lit-html` part we need to configure two things. `lit-html` comes as an `npm` module that we’ll have to bundle into an application. We’ll be using zero configuration bundler called [`Parcel`](https://parceljs.org/) for this but if you feel more comfortable with [Webpack](https://webpack.js.org/), you are welcome to use that one. We’ll also be using [TypeScript](https://www.typescriptlang.org/) for this project since both `lit-html` and Twilio Chat have excellent support for TypeScript.

Install the following dependencies to get the two setup:

```bash
npm install -D parcel-bundler typescript
npm install -S tslib @types/events
```

Next create a file called `tsconfig.json` in the root of your project and add the following:

```json
{
 "compilerOptions": {
   "target": "es5",
   "module": "ESNext",
   "lib": ["dom", "es2015", "es2015.collection"],
   "importHelpers": true,
   "strict": true,
   "moduleResolution": "node"
 }
}
```

Then open the `package.json` and modify the `scripts` section to add a few new `parcel` based commands:

```json
"scripts": {
  "build": "parcel build src/index.html",
  "start": "parcel src/index.html",
  "dev": "parcel watch src/index.html",
  "test": "echo \"Error: no test specified\" && exit 1"
},
```

To test our setup, create an `index.html` file as our entry file for Parcel inside a folder called `src`:

```html
<!DOCTYPE html>
<html lang="en">
 
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Chat with lit-html & TypeScript</title>
</head>
 
<body>
  <div id="root"></div>
  <script src="./index.ts"></script>
</body>
 
</html>
```

In there we load a file `index.ts` via a relative path. Parcel will detect that file and will try to load it and start the bundling. Create the `index.ts` file in the same directory as the `index.html` file and place the following code into it:

```ts
const root = document.getElementById('root') as HTMLElement;
root.innerHTML = '<h1>Hello</h1>';
```

Test if the setup works by running:

```bash
npm start
```

This will start the `parcel` bundler in watch mode to ensure the files are re-compiled whenever we change something. Open your browser and go to http://localhost:1234. You should have a greeting of “Hello”:

![Screenshot of Browser showing Hello](https://thepracticaldev.s3.amazonaws.com/i/zoya5hv7z323912erkyk.png)

### Time to Start Templating!

So far we are rendering something very static to the page without using `lit-html`. Let’s dip our toes into `lit-html` with a small task first. Let’s render the same content we previously had, plus the current time and update it every second.

Before we change the code, make sure to install `lit-html` as a dependency:

```bash
npm install lit-html -S
```

We’ll need to import two functions from `lit-html` for our code. One is `html` and the other `render`. `html` is actually a tagged template function, meaning we’ll use it like this:

```ts
const templateResult = html`<h1>Hello</h1>`;
```

Tagged template functions work like normal template strings in JavaScript except that under the hood this will generate an HTML `<template />` and make sure that re-rendering happens efficiently. This way we can use variables inside our templates but also nest templates. If you’re wondering how this is efficient, make sure to check out [Justin Fagnani’s talk at the Chrome Dev Summit](https://www.youtube.com/watch?v=Io6JjgckHbg) last year.

The `render` function will then take care of actually rendering the content to the page.

Modify your `index.ts` page accordingly:

```ts
import { html, render } from 'lit-html';
 
function renderTime(time: Date) {
  return html`
    <p>It\'s: ${time.toISOString()}</p>
  `;
}
 
function renderHello(time: Date) {
  return html`
    <h1>Hello</h1>
    ${renderTime(time)}
  `;
}
 
const root = document.getElementById('root') as HTMLElement;
 
setInterval(() => {
  const view = renderHello(new Date());
  render(view, root);
}, 1000);
```

If you still have the parcel dev server running from earlier, you should be able to switch back to http://localhost:1234 in your browser and see the page updated with a date constantly updating.

![Screenshot of browser showing Hello and time](https://thepracticaldev.s3.amazonaws.com/i/bjxxh1wxm59nblytc1l7.png)

### What About Components?

Breaking things into smaller components is always a great way to reuse code and make it more maintainable. We can do the same thing with `lit-html` by creating functions that render different HTML structures and templates. Let’s create two components `ChatMessage` and `ChatMessageList` for our chat application. Create a new file `src/ChatMessage.ts`:

```ts
import { html } from 'lit-html';
 
const ChatMessage = (author: string, body: string) => {
  return html`
    <div class="message">
      <p class="message-author">${author}</p>
      <p class="message-body">${body}</p>
    </div>
  `;
};
 
export default ChatMessage;
```

This is similar to [stateless components](https://reactjs.org/docs/components-and-props.html) in React.

Create another file `src/ChatMessageList.ts` and place the following code into it:

```ts
import { html } from 'lit-html';
import { repeat } from 'lit-html/lib/repeat';
 
import ChatMessage from './ChatMessage';
 
interface Message {
  sid: string;
  author: string;
  body: string; 
}
 
const ChatMessageList = (messages: Message[]) => {
  return html`
    <div class="message-list">
      ${repeat(
        messages,
        msg => msg.sid,
        msg => ChatMessage(msg.author, msg.body)
      )}
    </div>
  `;
};
export default ChatMessageList;
```

We are using a helper function from `lit-html` called `repeat` that allows us to efficiently loop over an array to render each component. The function receives a list of items and two functions, one to identify a key to check which items updated and one to render the content. We are using `msg.sid` for uniqueness as this will be a unique key for every message once we integrate Twilio Chat.

Let’s test our two components by rendering some mock messages. Alter `src/index.ts`:

```ts
import { html, render } from 'lit-html';
 
import ChatMessageList from './ChatMessageList';
 
function renderMessages() {
  const messages = [
    { sid: '1', author: 'Dominik', body: 'Hello' },
    { sid: '2', author: 'Panda', body: 'Ahoy' },
    { sid: '3', author: 'Dominik', body: 'how are you?' }
  ];
 
  return html`
    <h1>Messages:</h1>
    ${ChatMessageList(messages)}
  `;
}
 
const root = document.getElementById('root') as HTMLElement;
 
const view = renderMessages();
render(view, root);
```

Switch back to http://localhost:1234 and you should see 3 messages being displayed:

![Screenshot of mock messages displayed](https://thepracticaldev.s3.amazonaws.com/i/5qlo3k56cfbu1dar65jf.png)

### Let’s Get Chatty

Now that we know how to render a list of messages, let’s add Twilio Chat to the mix to create an actual real-time chat. Twilio Chat provides a JavaScript/TypeScript SDK to build all the messaging logic, including channels and events. Install the SDK by running:

```bash
npm install -S twilio-chat
```

Let’s move the entire chat related logic into a central place that will handle rendering the interface as well as updating it when necessary.

Create a new file `src/chat.ts` and place the following code into it:

```ts
import Client from 'twilio-chat';
import { Message } from 'twilio-chat/lib/Message';
import { Channel } from 'twilio-chat/lib/channel';
import { html, render as renderLit } from 'lit-html/lib/lit-extended';
import ChatMessageList from './ChatMessageList';
 
export class Chat {
  private messages: Message[] = [];
  private channel: Channel | null = null;
 
  constructor(
    private token: string,
    private channelName: string,
    private root: HTMLElement
  ) {
    this.messageSubmit = this.messageSubmit.bind(this);
  }
  
  private messageSubmit(event: Event) {
  }
}
```

We’ll be using `lit-extended` in this case rather than the normal `lit-html` to render the view. This enables us to use features such as event listeners.

In order to use the Twilio Chat SDK we’ll need to use a client token. We’ll take care generating this later but for now we’ll pass it in the constructor of the `Chat` class and a `channelName` of the channel we want to join and a `root` HTML element that we want to render content into.

Next create a `render` and `messageSubmit` method. `render()` will be responsible for rendering the entire UI from the list of messages to the input form. `messageSubmit` will then be triggered whenever the form is submitted (i.e. a new message is sent). Add the following functions to your `Chat` class:

```ts
  constructor(
    private token: string,
    private channelName: string,
    private root: HTMLElement
  ) {
    this.messageSubmit = this.messageSubmit.bind(this);
  }

  private messageSubmit(event: Event) {
    event.preventDefault();
    const form = event.target as HTMLFormElement;
    const msg: string = form.message.value;
    if (this.channel) {
      this.channel.sendMessage(msg);
    }
    form.message.value = '';
  }

  private render() {
    const view = html`
    ${ChatMessageList(this.messages)}
    <form on-submit="${this.messageSubmit}">
      <input type="text" name="message" placeholder="Type your message..." />
      <button type="submit">Send</button>
    </form>
    `;

    renderLit(view, this.root);
  }
```

We need to establish a connection to Twilio Chat. For this we’ll have to:

- Create a Twilio Client
- Try to get the channel we want to join or create it if it doesn’t exist
- Join the channel
- Retrieve the existing messages
- Listen for new messages coming in

We’ll bundle all of that functionality into a public `init()` function that kicks this off and a `registerChannelListeners()` method that will be responsible for updating the UI if a new message comes in. Add the following code to your `Chat` class:

```ts
  private render() {
    const view = html`
    ${ChatMessageList(this.messages)}
    <form on-submit="${this.messageSubmit}">
      <input type="text" name="message" placeholder="Type your message..." />
      <button type="submit">Send</button>
    </form>
    `;
    
     renderLit(view, this.root);
  }

  private registerChannelListeners() {
    if (!this.channel) {
      return;
    }

    this.channel.on('messageAdded', (msg: Message) => {
      this.messages.push(msg);
      this.render();
    });
  }

  async init() {
     const client = await Client.create(this.token);
     try {
       this.channel = await client.getChannelByUniqueName(this.channelName);
     } catch {
       this.channel = await client.createChannel({
         uniqueName: this.channelName
       });
     }
     if (this.channel.status !== 'joined') {
       client.on('channelJoined', () => {
         this.registerChannelListeners();
       });
       await this.channel.join();
     } else {
       this.registerChannelListeners();
     }
     this.messages = (await this.channel.getMessages()).items;
     this.render();
     return this;
   }
}
```

Before we can test our code, we need to configure Twilio Chat. Log into the [Twilio Console](https://www.twilio.com/console/chat/dashboard). Go to the [Chat dashboard](https://www.twilio.com/console/chat/dashboard) and create a new Chat service.

For every user we’ll have to generate a unique access token for Chat. For now we’ll just use a test token that you can generate inside the [Chat Testing Tools](https://www.twilio.com/console/chat/dev-tools/testing-tools). Type in an arbitrary “Client Identity” (think username) and generate the token.

Now switch back into `src/index.ts` and replace the code with the following:

```ts
import { Chat } from './chat'
 
async function init() {
  const root = document.getElementById('root') as HTMLElement;
  const token = 'INSERT_YOUR_ACCESS_TOKEN';
  const channel = 'demo';
  const chat = new Chat(token, channel, root);
  await chat.init();
}
 
init();
```

Make sure you replace `INSERT_YOUR_ACCESS_TOKEN` with the token you generated. Go back to http://localhost:1234 and open the same page in a second window. You should be able to send messages in one window and they should show up in the other :)

![gif showing two browsers send messages to each other](https://thepracticaldev.s3.amazonaws.com/i/0kn9bab3nmbgh0vtw257.gif)

### Improving the Experience

Right now we have hard-coded the access token and that’s not ideal. You should instead generate this token dynamically server-side and either assign people different usernames or prompt them to create a username. We are also loading the entire Twilio Chat SDK on page load. What if the person got to the page but doesn’t actually want to chat?

Let’s modify our code to fetch a token dynamically and also make use of a concept called code-splitting by dynamically loading the chat related code if the user actually wants to chat.

To generate the access token we’ll be using Twilio Functions which allow you to host serverless functions written in Node.js directly on the Twilio infrastructure.

Go to the [Functions part of the Twilio Console](https://www.twilio.com/console/runtime/functions/manage) and create a new Blank function. Specify a path such as `/chat-token` and place the following code into it:

```js
exports.handler = function(context, event, callback) {  
  // make sure you enable ACCOUNT_SID and AUTH_TOKEN in Functions/Configuration
  const ACCOUNT_SID = context.ACCOUNT_SID;
 
  // you can set these values in Functions/Configuration or set them here
  const SERVICE_SID = 'CHAT_SERVICE_SID';
  const API_KEY = context.API_KEY || 'enter API Key';
  const API_SECRET = context.API_SECRET || 'enter API Secret';
 
  // REMINDER: This identity is only for prototyping purposes
  const IDENTITY = event.username || 'only for testing';
 
  const AccessToken = Twilio.jwt.AccessToken;
  const IpMessagingGrant = AccessToken.IpMessagingGrant;
 
  const chatGrant = new IpMessagingGrant({
        serviceSid: SERVICE_SID
  });
 
  const accessToken = new AccessToken(
        ACCOUNT_SID,
        API_KEY,
        API_SECRET
  );
 
  accessToken.addGrant(chatGrant);
  accessToken.identity = IDENTITY;
 
  const response = new Twilio.Response();
  response.appendHeader('Access-Control-Allow-Origin', '*');
  response.appendHeader('Access-Control-Allow-Methods', 'GET');
  response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
  response.appendHeader('Content-Type', 'application/json');
  response.setBody({ token: accessToken.toJwt() });
  callback(null, response);
}
```

Make sure to replace the value of `SERVICE_SID` with the SID for your Chat Service.
Save the Twilio Function and copy the path for later use.

If you are using Twilio Functions for the first time, you’ll also want to go into the [Configure section](https://www.twilio.com/console/runtime/functions/configure), enable `ACCOUNT_SID` and add entries for an `API_KEY` and `API_SECRET` that you can [generate a pair of them here](https://www.twilio.com/console/runtime/api-keys/create).

![screenshot of Twilio Functions configure section](https://thepracticaldev.s3.amazonaws.com/i/hfhh1omxhai3rfo5r2zz.png)

Now that the token generation endpoint is ready, let’s start using it. First let’s update the `src/index.html` file to add a new input field for a username and a button to start chatting:

```html
  <input type="text" id="username" placeholder="Username" />
  <button id="btn">Start Chat</button>
  <div id="root"></div>
  <script src="./index.ts"></script>
```

Next we’ll modify the `src/index.ts file`. Instead of immediately initializing everything, we’ll hold off until the button has been clicked. We’ll also use `import('./chat');` as a dynamic import. This will tell `parcel` that we only need everything from this module at a later part and `parcel` will automatically perform code splitting for us. Update your code accordingly:

```ts
const TOKEN_URL = `YOUR_TOKEN_URL`;
const btn = document.querySelector('button') as HTMLButtonElement;
const input = document.querySelector('#username') as HTMLInputElement;
const root = document.querySelector('#root') as HTMLElement;
 
async function init() {
  const username = input.value;
  const tokenUrl = `${TOKEN_URL}?username=${username}`;
  const resp = await fetch(tokenUrl);
 
  if (!resp.ok) {
    console.error('Failed to request token');
    return;
  }
 
  const { token } = await resp.json();
  const { Chat } = await import('./chat');
  const channel = 'demo';
  const chat = new Chat(token, channel, root);
  await chat.init();
} 
 
btn.addEventListener('click', init);
```

Switch back to your browser, open the developer tools and switch to the network pane. If you refresh the page now you should see a JavaScript file being loaded. When you then click on the “Start Chat” button, you should see multiple requests being made. One will fetch the access token from your Twilio Function and one will be the bundle that has all the code related to the chat functionality.

Your chat should work just like before, except that you can now have multiple users chat with different usernames. Give it a try by opening two windows at once.

![gif of two chat users interacting in separate browser windows](https://thepracticaldev.s3.amazonaws.com/i/w5jc33qzm90flzfy3rtl.gif)

### Now Chat Away!

That’s it. We built a chat UI that is small and lightweight yet handles performant re-rendering thanks to `lit-html`. We were also able to leverage code-splitting to further improve the page load time by only loading the things that are necessary on initial page load thanks to `parcel`‘s out-of-the-box support for it.

If you want to deploy this to production, run:

```bash
npm run build
```

This will create a bunch of files in a folder called dist that you can serve from any static hosting service or upload to the [Twilio Runtime Assets](https://www.twilio.com/console/runtime/assets/public) to serve.

From here you can go ahead and start styling your application or look into other functionality from Twilio Chat such as:

- Private channels
- Typing and reachability indicators
- Webhooks for bot responses
- The REST API to insert messages from your server

If you have any questions or if you want to show me something cool you built with Twilio or `lit-html`, feel free to reach out to me:

- Twitter: [@dkundel](https://www.twitter.com/dkundel)
- Email: dkundel@twilio.com
- GitHub: [dkundel](https://github.com/dkundel)

---

_[Building a chat with Twilio, lit-html, Parcel and TypeScript](https://www.twilio.com/blog/2018/05/building-a-chat-with-twilio-lit-html-parcel-and-typescript.html) was originally published on the [Twilio Blog](https://www.twilio.com/blog) on May 14, 2018._