# Handle real-time data with Twilio Sync

[Twilio Sync](/docs/sync) is a powerful tool that enables you to synchronize the state of your applications across platforms, with only milliseconds of delay. It's commonly used to establish chat services, power live dashboards for information like recent calls to a support agent, and integrates with Twilio Flex.

This guide will show how to combine Functions, Assets, and Sync into a web application that displays incoming text messages in real time. All without the need to run or maintain your own server 24/7.

![Web app displaying incoming SMS messages in real-time using Twilio Runtime.](https://docs-resources.prod.twilio.com/81fcf72feb50499f7b78a696a848b0ec9d453c15ca25060fa2e025348e01c767.gif)

A quick overview of the architecture and tools that will be used:

* [Assets](/docs/serverless/functions-assets/assets) will host the app's static content, namely the `index.html` file that users will access
* One [Function](/docs/serverless/functions-assets/functions) will serve as an API endpoint for users to generate their Sync token. This will grant them access to view the messages
* A second Function will be a webhook that accepts incoming messages, and pushes their contents to a Sync List
* Sync stores the [List](/docs/sync/api/list-resource) of messages which will appear in the app, and sends message updates to the web app

To begin, follow the instructions below to create a [Service](/docs/serverless/functions-assets/functions/create-service) and the first Function of this app.

## Create and host a Function

In order to run any of the following examples, you will first need to create a Function into which you can paste the example code. You can create a Function using the Twilio Console or the [Serverless Toolkit](/docs/labs/serverless-toolkit) as explained below:

## Console

If you prefer a UI-driven approach, creating and deploying a Function can be done entirely using the Twilio Console and the following steps:

1. Log in to the Twilio Console and navigate to the [Functions tab](https://www.twilio.com/console/functions/overview). If you need an account, you can sign up for a free Twilio account [here](https://www.twilio.com/try-twilio)!
2. Functions are contained within **Services**. Create a **[Service](/docs/serverless/functions-assets/functions/create-service)** by clicking the **[Create Service](https://www.twilio.com/console/functions/overview/services)** button and providing a name such as *test-function*.
3. Once you've been redirected to the new Service, click the **Add +** button and select **Add Function** from the dropdown.
4. This will create a new [Protected](/docs/serverless/functions-assets/visibility) Function for you with the option to rename it. The name of the file will be path it is accessed from.
5. Copy any one of the example code snippets from this page that you want to experiment with, and paste the code into your newly created Function. You can quickly switch examples by using the dropdown menu of the code rail.
6. Click **Save** to save your Function's contents.
7. Click **Deploy All** to build and deploy the Function. After a short delay, your Function will be accessible from: `https://<service-name>-<random-characters>-<optional-domain-suffix>.twil.io/<function-path>`\
   For example: `test-function-3548.twil.io/hello-world`.

## Serverless Toolkit

The [Serverless Toolkit](/docs/labs/serverless-toolkit) enables you with local development, project deployment, and other functionality via the [Twilio CLI](/docs/twilio-cli/quickstart). To get up and running with these examples using Serverless Toolkit, follow this process:

1. From the CLI, run `twilio serverless:init <your-service-name> --empty` to bootstrap your local environment.
2. Navigate into your new project directory using `cd <your-service-name>`
3. In the `/functions` directory, create a new JavaScript file that is named respective to the purpose of the Function. For example, `sms-reply.protected.js` for a [Protected](/docs/serverless/functions-assets/visibility) Function intended to handle incoming SMS.
4. Populate the file using the code example of your choice and save. **Note** A Function can only export a single handler. You will want to create separate files if you want to run and/or deploy multiple examples at once.

Once your Function(s) code is written and saved, you can test it either by running it locally (and optionally tunneling requests to it via a tool like [ngrok](https://ngrok.com/)), or by deploying the Function and executing against the deployed url(s).

### Run your Function in local development

Run `twilio serverless:start` from your CLI to start the project locally. The Function(s) in your project will be accessible from `http://localhost:3000/sms-reply`

* If you want to test a Function as a [Twilio webhook](/docs/usage/webhooks/getting-started-twilio-webhooks), run: `twilio phone-numbers:update <your Twilio phone number> --sms-url "http://localhost:3000/sms-reply"`\
  This will automatically generate an ngrok tunnel from Twilio to your locally running Function, so you can start sending texts to it. You can apply the same process but with the `voice-url` flag instead if you want to test with [Twilio Voice](/docs/voice).
* If your code does *not* connect to Twilio Voice/Messages as a webhook, you can start your dev server and start an ngrok tunnel in the same command with the `ngrok` flag. For example: `twilio serverless:start --ngrok=""`

### Deploy your Function

To deploy your Function and have access to live url(s), run `twilio serverless:deploy` from your CLI. This will deploy your Function(s) to Twilio under a development environment by default, where they can be accessed from:

`https://<service-name>-<random-characters>-dev.twil.io/<function-path>`

For example: `https://incoming-sms-examples-3421-dev.twil.io/sms-reply`

Your Function is now ready to be invoked by HTTP requests, set as the [webhook](/docs/usage/webhooks/getting-started-twilio-webhooks) of a Twilio phone number, invoked by a Twilio Studio **[Run Function Widget](/docs/studio/widget-library/run-function)**, and more!

## Generate Sync Tokens

When a user visits the application, their browser will make a request to this Function for a Sync [Access Token](/docs/iam/access-tokens) and the name of the Sync List the app will listen to. Name your first new Function `access`, and paste in the contents of the code sample below.

This code generates a Sync Token using secured [Environment Variables](/docs/serverless/functions-assets/functions/variables), and returns a stringified version of the token along with the name of the Sync List used by the application.

```js title="Generate a Sync Access Token and Sync List name"
const AccessToken = Twilio.jwt.AccessToken;
const SyncGrant = AccessToken.SyncGrant;

exports.handler = (context, event, callback) => {
  // Create a Sync Grant for a particular Sync service, or use the default one
  const syncGrant = new SyncGrant({
    serviceSid: context.TWILIO_SYNC_SERVICE_SID || 'default',
  });

  // Create an access token which we will sign and return to the client,
  // containing the grant we just created
  // Use environment variables via `context` to keep your credentials secure
  const token = new AccessToken(
    context.ACCOUNT_SID,
    context.TWILIO_API_KEY,
    context.TWILIO_API_SECRET,
    { identity: 'example' }
  );

  token.addGrant(syncGrant);

  // Return two pieces of information: the name of the sync list so it can
  // be referenced by the client, and the JWT form of the access token
  const response = {
    syncListName: context.SYNC_LIST_NAME || 'serverless-sync-demo',
    token: token.toJwt(),
  };

  return callback(null, response);
};
```

> \[!NOTE]
>
> The examples in this app leverage Environment Variables to share common strings, such as the Service SID and Sync List name, but the samples will work if you don't define your own.
>
> However, you must define, at a minimum, an [API Key and API Secret](/docs/iam/api-keys/key-resource-v2010). Your Account SID should already be in your Environmental Variables by default, regardless of whether you're building in the Console or with the Serverless Toolkit.

## Append new SMS messages to a Sync List

The next important feature of this application is being able to push the contents of incoming texts to the Sync List, so they can render in real-time in the browser. To do this, create a new Function, and name it `handle-sms`. Type or copy the contents of the following code sample into the `handle-sms` Function, and save.

This Function works by leveraging the built-in [Runtime.getSync](/docs/serverless/functions-assets/client#getsyncoptions) method to bootstrap a Sync Client for you. It then verifies that the Sync List is available, appends the body of the incoming message (`event.Body`) to the list, and returns an SMS to the sender acknowledging receipt of their text.

```js title="Append new SMS messages to a Sync List"
exports.handler = async (context, event, callback) => {
  // Make sure the necessary Sync names are defined.
  const syncServiceSid = context.TWILIO_SYNC_SERVICE_SID || 'default';
  const syncListName = context.SYNC_LIST_NAME || 'serverless-sync-demo';
  // You can quickly access a Twilio Sync client via Runtime.getSync()
  const syncClient = Runtime.getSync({ serviceName: syncServiceSid });
  const twiml = new Twilio.twiml.MessagingResponse();

  // Destructure the incoming text message and rename it to `message`
  const { Body: message } = event;

  try {
    // Ensure that the Sync List exists before we try to add a new message to it
    await getOrCreateResource(syncClient.lists, syncListName);
    // Append the incoming message to the list
    await syncClient.lists(syncListName).syncListItems.create({
      data: {
        message,
      },
    });
    // Send a response back to the user to let them know the message was received
    twiml.message('SMS received and added to the list! 🚀');
    return callback(null, twiml);
  } catch (error) {
    // Persist the error to your logs so you can debug
    console.error(error);
    // Send a response back to the user to let them know something went wrong
    twiml.message('Something went wrong with adding your message 😔');
    return callback(null, twiml);
  }
};

// Helper method to simplify getting a Sync resource (Document, List, or Map)
// that handles the case where it may not exist yet.
const getOrCreateResource = async (resource, name, options = {}) => {
  try {
    // Does this resource (Sync Document, List, or Map) exist already? Return it
    return await resource(name).fetch();
  } catch (err) {
    // It doesn't exist, create a new one with the given name and return it
    options.uniqueName = name;
    return resource.create(options);
  }
};
```

## Host the web client

With the necessary Functions in place, it's time to create the front-end of this web application.

## Console

If you're following this example in the Twilio Console:

1. Create a file named `index.html` on your computer.
2. Copy the following HTML example code into the new `index.html` file, and save the file.
3. Upload `index.html` as a public Asset. You can do so by clicking **Add+**, selecting **Upload File** and finding `index.html` in the upload prompt, setting the visibility as **Public**, and then finally clicking **Upload**.

## Serverless Toolkit

If you're following this example with the Serverless Toolkit:

1. Inside your project, create a new file in the `assets/` folder named `index.html`.
2. Copy the following HTML example code into the new `index.html` file, and save the file.
3. That's it! Your file will be automatically uploaded and hosted when you run `twilio serverless:deploy` in the next part of the example.

```html title="HTML for the web client"
<!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>Runtime + Sync = 🚀!</title>
  </head>
  <body>
    <main>
      <h1>Ahoy there!</h1>
      <p>This is an example of a simple web app hosted by Twilio Runtime.</p>
      <p>
        It fetches a Sync access token from a serverless Twilio Function,
        renders any existing messages from a Sync List, and displays incoming
        messages as you text them to your Twilio phone number.
      </p>
      <h2>Messages:</h2>
      <div id="loading-message">Loading Messages...</div>
      <ul id="messages-list" />
    </main>
    <footer>
      <p>
        Made with 💖 by your friends at
        <a href="https://www.twilio.com">Twilio</a>
      </p>
    </footer>
  </body>
  <script
    type="text/javascript"
    src="//media.twiliocdn.com/sdk/js/sync/v3.0/twilio-sync.min.js"
  ></script>
  <script>
    window.addEventListener('load', async () => {
      const messagesList = document.getElementById('messages-list');
      const loadingMessage = document.getElementById('loading-message');

      try {
        // Get the Sync access token and list name from the serverless function
        const { syncListName, token } = await fetch('/access').then((res) =>
          res.json()
        );
        const syncClient = new Twilio.Sync.Client(token);
        // Fetch a reference to the messages Sync List
        const syncList = await syncClient.list(syncListName);
        // Get the most recent messages (if any) in the List
        const existingMessageItems = await syncList.getItems({ order: 'desc' });
        // Hide the loading message
        loadingMessage.style.display = 'none';
        // Render any existing messages to the page, remember to reverse the order
        // since they're fetched in descending order in this case
        messagesList.innerHTML = existingMessageItems.items
          .reverse()
          .map((item) => `<li>${item.data.message}</li>`)
          .join('');
        // Add an event listener to the List so that incoming messages can
        // be displayed in real-time
        syncList.on('itemAdded', ({ item }) => {
          console.log('Item added:', item);
          // Add the new message to the list by adding a new <li> element
          // containing the incoming message's text
          const newListItem = document.createElement('li');
          messagesList.appendChild(newListItem).innerText = item.data.message;
        });
      } catch (error) {
        console.error(error);
        loadingMessage.innerText = 'Unable to load messages 😭';
        loadingMessage.style.color = 'red';
        loadingMessage.style.fontWeight = 'bold';
      }
    });
  </script>
</html>
```

The magic here is primarily concentrated in the `ul` element and accompanying JavaScript. Once the window finishes loading, the script requests the Sync List name and Access Token from the access Function. Once the script has that token, it uses that token with the [twilio-sync library](https://www.npmjs.com/package/twilio-sync) to create a local Sync Client. With that Sync Client, the script then gets the latest messages, injects them into the `ul` as more list items, and sets up an event handler that appends new messages as soon as they come in.

## Deploy the Service

Now is a good time to save and deploy this Service. Save all file changes, and click **Deploy All** if you're working from the Twilio Console, or run `twilio serverless:deploy` from your project's CLI if you're following along with the Serverless Toolkit.

Once you have deployed your code, you could visit the web page, but, sadly, there will be no messages to show yet. We'll address that issue in the next section.

## Connect `handle-sms` to a Twilio Phone Number

To complete this app, you will need to connect the `handle-sms` Function to one of your Twilio Phone Numbers as a webhook. Follow the directions below, and configure `handle-sms` as the [webhook](/docs/usage/webhooks) for incoming messages to your Twilio Phone Number of choice.

## Set a Function as a webhook

In order for your Function to react to incoming SMS and/or voice calls, it must be set as a [webhook](/docs/usage/webhooks) for your Twilio number. There are a variety of methods to set a Function as a webhook, as detailed below:

![Setting a Function as a Messaging webhook using the webhook dropdown option.](https://docs-resources.prod.twilio.com/bf4eae4ac40fe7d47003a93bca295d5c232e0b372358e73ceff931fee3ccdc4f.png)

## Test it out

All the pieces are now in place, so now is a great time to test out this application! Open up your app's web page by visiting its URL. This will be your service name, followed by `index.html`, for example:

```bash
https://sync-6475.twil.io/index.html
```

It should display no messages initially. However, if you send any text messages, they should pop into the messages list almost immediately. If you refresh the page, any previous messages should pop into view first, and new messages will continue adding to the end of the initial list.

## Next steps

This app is functional, but its appearance is a little bare bone. It also doesn't handle the inevitable occurrence when the current user's Sync Token will expire after some time. To make this app look cleaner and more resilient to token expiration, add the following, highlighted updates to index.html.

```html title="Fully-featured web client HTML" description="Includes some styling and token refresh logic"
<!-- !mark(10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,8,9) -->
<!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>Runtime + Sync = 🚀!</title>
    <style>
      body {
        font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI',
          Roboto, 'Helvetica Neue', Arial, sans-serif;
        color: #0d122b;
        border-top: 5px solid #f22f46;
      }
      main {
        max-width: 800px;
        margin: 0 auto;
      }
      a {
        color: #008cff;
      }
      footer {
        margin: 0 auto;
        max-width: 800px;
        text-align: center;
      }
      footer p {
        border-top: 1px solid rgba(148, 151, 155, 0.2);
        padding-top: 2em;
        margin: 0 2em;
      }
    </style>
  </head>
  <body>
    <main>
      <h1>Ahoy there!</h1>
      <p>This is an example of a simple web app hosted by Twilio Runtime.</p>
      <p>
        It fetches a Sync access token from a serverless Twilio Function,
        renders any existing messages from a Sync List, and displays incoming
        messages as you text them to your Twilio phone number.
      </p>
      <h2>Messages:</h2>
      <div id="loading-message">Loading Messages...</div>
      <ul id="messages-list" />
    </main>
    <footer>
      <p>
        Made with 💖 by your friends at
        <a href="https://www.twilio.com">Twilio</a>
      </p>
    </footer>
  </body>
  <script
    type="text/javascript"
    src="//media.twiliocdn.com/sdk/js/sync/v3.0/twilio-sync.min.js"
  ></script>
  <script>
    window.addEventListener('load', async () => {
      const messagesList = document.getElementById('messages-list');
      const loadingMessage = document.getElementById('loading-message');

      try {
        // Get the Sync access token and list name from the serverless function
        const { syncListName, token } = await fetch('/access').then((res) =>
          res.json()
        );
        const syncClient = new Twilio.Sync.Client(token);
        // Fetch a reference to the messages Sync List
        const syncList = await syncClient.list(syncListName);
        // Get the most recent messages (if any) in the List
        const existingMessageItems = await syncList.getItems({ order: 'desc' });
        // Hide the loading message
        loadingMessage.style.display = 'none';
        // Render any existing messages to the page, remember to reverse the order
        // since they're fetched in descending order in this case
        messagesList.innerHTML = existingMessageItems.items
          .reverse()
          .map((item) => `<li>${item.data.message}</li>`)
          .join('');
        // Add an event listener to the List so that incoming messages can
        // be displayed in real-time
        syncList.on('itemAdded', ({ item }) => {
          console.log('Item added:', item);
          // Add the new message to the list by adding a new <li> element
          // containing the incoming message's text
          const newListItem = document.createElement('li');
          messagesList.appendChild(newListItem).innerText = item.data.message;
        });

        // Make sure to refresh the access token before it expires for an uninterrupted experience! 
        syncClient.on('tokenAboutToExpire', async () => {
          try {
            // Refresh the access token and update the Sync client
            const refreshAccess = await fetch('/access').then((res) =>
              res.json()
            );
            syncClient.updateToken(refreshAccess.token);
          } catch (error) {
            console.error(error);
            loadingMessage.innerText =
              'Unable to refresh access to messages 😭, try reloading your page!';
            loadingMessage.style.color = 'red';
            loadingMessage.style.fontWeight = 'bold';
          }
        });
      } catch (error) {
        console.error(error);
        loadingMessage.innerText = 'Unable to load messages 😭';
        loadingMessage.style.color = 'red';
        loadingMessage.style.fontWeight = 'bold';
      }
    });
  </script>
</html>
```

> \[!NOTE]
>
> If you want the ability for users to visit the app at the root URL instead of needing to specify `/index.html` at the end, such as just `https://sync-6475.twil.io/`, [host index.html as a Root Asset](/docs/serverless/functions-assets/assets#hosting-a-root-asset) and re-deploy!
