How the story begins
I’m a member of the Retool Power Users Slack, and I poke my head in a few times a week to see what people talk and ask questions about.
Anil Vaitla (@avaitla16) asked whether or not anyone had been able to implement real-time, event-based query refreshes inside a Retool app. I’ve done it, so I’m here to explain how I got it working.
Before I dive into this, here are a few broad topics you should at least be vaguely familiar with before reading this article:
Explaining exactly how these work isn’t within the scope of this article, but if you have questions about specific implementation details, please feel free to reach out with any questions.
Some popular tools or libraries to look into for real-time event handling are:
Let’s start by mentioning a few use cases for real-time effects.
What are some real-time use cases?
This discussion started with a use case for a Dashboard that’s left open all day, perhaps on a screen at the office, and this screen would refresh live as data changed throughout the day. Real-time events are an excellent tool to build a dashboard like this.
Simultaneous users can be a real pain without a tool like real-time events. I ran into issues with table data staying in sync for multiple users. I use Pusher to force query refreshes to make sure visual data on certain pages stays in sync seamlessly.
For some further reading, PubNub has a cool guide on WebSockets and more use cases.
I will show you how real-time events work in Retool for a raw WebSocket and an external library. Pusher will be my external library of choice because it happens to be the one I currently use.
Hooking up a WebSocket
Hooking up a one-way, listening-only WebSocket is very straightforward in Retool. Hooking up a two-way send-and-receive WebSocket is, unfortunately, quite a bit messier. WebSockets are bi-directional by design, so I feel it is crucial to demonstrate the complete picture.
I will be using a public WebSocket that is excellent for testing from websocket.org. It’s called an echo WebSocket, and by its name, you might guess that it echos back anything you send to it. Here’s an interactive page for it that you can try yourself:
https://www.websocket.org/echo.html
The WebSocket Goal
Before we begin, here’s where we’re trying to end up:
You can type a message in the box and press “Send.” Retool sends the text to the echo WebSocket service. Then, a response handler appends any responses from the echo WebSocket to a responses list. The text reads the response list to build the visual shown above. This textbox will update in real-time and will not require a page refresh to see new messages.
The WebSocket Visuals
Let’s start by going ahead and getting the visual part of the app set up. You will need the following Retool components:
- A Text Input for inputting a message
- A Button for sending the message
- A Container for visual separation
- A Text component for displaying responses
- A second Button to clear the responses text box
Leave the names as the defaults, and arrange them to your preference.
The WebSocket Global Stuff
In the top right corner of your app, you will see a button that has three dots on it:
Click on this button, and select “Scripts and styles”. Click on the “Javascript” tab, and add the following code snippet:
In short, we’re creating a global WebSocket and a global messages array that we can access throughout the Retool app. I have added comments to the code snippet to help explain each line of code.
The WebSocket Queries
You will need to create three Javascript queries in Retool:
- websocket - appends messages to our response messages array with an event listener
- send_websocket_message - sends values from the text input to the WebSocket
- clear_messages - clears the response messages array
The websocket
query requires special attention. In the settings, under the “Advanced” tab, please check the setting that says “Run this query on page load?”
This query running on page load is the primary magic that lets us add real-time listening to Retool apps. As we’ll see with the later Pusher example, this query is all you need to listen to a WebSocket connection.
You can find the code for the scripts and styles section, the three queries above, and a pusher example query at the GitHub Gist here:
https://gist.github.com/sesgoe/81aad6dd4b587d6bcc4d8937407ef601
Once you plug in the code in the appropriate places, you should see something like this:
As far as I can tell, the reason this works is related to how Event Listeners in Javascript work in general. We’re creating a WebSocket
object and attaching event listeners to it, so the browser doesn’t garbage collect it, and therefore we get to listen to WebSocket events. Because this runs on page load, Retool sets it up to immediately start listening for WebSocket messages.
That’s the gist of doing it with a raw WebSocket. Now that we’ve created an app using a raw WebSocket let’s briefly talk about why you’d use something like Pusher in the first place.
Why use an external library?
The main difference between doing it manually, and something like Pusher, is that Pusher is a library built on top of WebSockets. WebSocket by itself is a very bare-bones framework.
For example, WebSockets will not handle disconnects or reconnects on their own. WebSockets make no assumptions about the data types you send or receive so that you can send either binary data or strings. Data serialization is another thing left to the developer. These WebSocket design choices allow for maximum developer control but are punishing to get wrong.
Unless you have a specific business or security use case for implementing your own WebSocket handling, I recommend using an external library. External libraries will usually handle a lot of the annoying aspects of WebSockets for you out of the box. Things like handling JSON instead of raw data, reconnecting on disconnects, etc.
Now that you understand the why, let’s talk about building a similar event listener with Pusher.
Pusher it to the limit
Disclaimer for this section: I will not be providing the screenshots to build another Retool app. This section is mostly about adding an external real-time library to a Retool app, and I am assuming that you understand tools like Pusher well enough to set it up for your particular use case.
Typically in Javascript-land, you would npm install
or yarn add
the Pusher library to use it in your frontend project. We don’t have access to npm install
in Retool, so how do we use something like Pusher? Thankfully, most frontend Javascript libraries include a way to use them by simply adding a script tag to your page. If we go and look at the installation guide for Pusher:
https://github.com/pusher/pusher-js#installation
You can see that the standard way is to yarn add pusher-js
or npm install pusher-js
, but there is also a CDN option to add this script tag to your page:
This CDN option is what we need to get it functioning in Retool. It’s essentially the same as including the script tag in the page yourself, which gives you access to the library from inside Retool queries.
To add Pusher, click on that triple-dot button in the top right next to “Share” and select “Scripts and styles” again. This time we want to stay on the “Libraries” tab and add the script source for Pusher like so:
https://js.pusher.com/7.0/pusher.min.js
Once you have added the pusher.min.js
file to your “Libraries” tab, you can access Pusher library functions inside your Retool queries. Here’s an example Retool Javascript query that runs a query’s .trigger()
every time I receive a Pusher event:
Note, this can be found at the Github Gist too:
https://gist.github.com/sesgoe/81aad6dd4b587d6bcc4d8937407ef601#file-05_pusher_handler-js
You can find Retool’s documentation on the .trigger()
function here:
https://docs.retool.com/docs/scripting-retool#triggering-a-query
Explaining the backend for Pusher is out of scope for this article, but I wanted to provide a quick demo gif to show you what you can expect with Pusher:
A friendly tip
It’s worth keeping in mind that you will probably want to add a real-time status indicator to your apps wherever you decide to use real-time events. Libraries will usually handle disconnects and reconnects for you, but it’s typically helpful to see the current real-time connection status.
Special Thanks
Special thanks to Justin from the Retool Community team for helping me edit and shape this post.