Real-time data streaming using FastAPI and WebSockets

fastapi javascript popular python
Streaming data from FastAPI to the browser

We have several options for real-time data streaming in web applications. We can use polling, long-polling, Server-Sent Events and WebSockets. The last two can be used for server-push scenarios where we want to send data to a browser without any specific request from the client. All of this solutions have their advantages and disadvantages, so we need to make sure that a particular approach is the best for our application.

Today, we will have a look at how simple it can be to start streaming data to a browser from a backend Python application using WebSockets. There are multiple Python web frameworks capable of doing that, but for this demostration we will use FastAPI, a modern async framework that is gaining momentum in the new space of Python async frameworks.

Streaming in WebSockets basically means sending small data packets over an open connection between the server and the client. We can send both text or binary data packets and what we put inside is completely up to us. Since JSON is a popular data format (although not really memory efficient), we will use it to structure our data packets and send them as text for easy debugging.

The example data will be just a series of numbers that we will render into a self-updating chart on the client web page, simulating time-series data. For this purpose, I have chosen a simple JavaScript library TimeChart since it is easy to use, built on WebGL for performance and supports continuous updates for time-series data (the data in the chart will automatically flow from right to left, always displaying new data points in the chart while hiding the old ones). There are other charting libraries that can do this, but TimeChart is very minimalistic and has small footprint, perfect for this example.

To run the program we will need to install a couple of dependencies: FastAPI (the web framework), Uvicorn (ASGI server) and jinja2 (to render server-side templates) for the backend and TimeChart and its dependencies for the frontend. As always you can find the whole example on Github as Python real-time data streaming using FastAPI and WebSockets, which includes all the source code as well as dependencies defined using Poetry.

Let’s first start with our Python code:

import json
import asyncio
from fastapi import FastAPI
from fastapi import Request
from fastapi import WebSocket
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")

with open('measurements.json', 'r') as file:
measurements = iter(json.loads(file.read()))

@app.get("/")
def read_root(request: Request):
return templates.TemplateResponse("index.htm", {"request": request})

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
await asyncio.sleep(0.1)
payload = next(measurements)
await websocket.send_json(payload)

As you can see, the code is pretty short! We are basically doing a couple of things here:

I used iter() function when loading our sample dataset to create a Python iterator so that we can simply grab a next value in the list, giving us the illusion of having a continuous data stream.

Also, asyncio.sleep() is called to make the data stream a bit slower.

Now, let’s have a look at our index.htm template, where we also store our JavaScript code:

 <html>
<head>
<title>Real time streaming</title>
<script src="https://d3js.org/d3-array.v2.min.js"></script>
<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-format.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3-time.v1.min.js"></script>
<script src="https://d3js.org/d3-time-format.v2.min.js"></script>
<script src="https://d3js.org/d3-scale.v3.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-axis.v1.min.js"></script>
<script src="https://huww98.github.io/TimeChart/dist/timechart.min.js"></script>
<style>
#chart { width: 100%; height: 300px; margin-top: 300px; }
</style>
</head>
<body>
<div id="chart"></div>
<script>
const el = document.getElementById('chart');
const dataPoints = [];
const chart = new TimeChart(el, {
series: [{ data: dataPoints, name: 'Real-time measurement streaming', color: 'darkblue' }],
realTime: true,
xRange: { min: 0, max: 500 },
});
const ws = new WebSocket("ws://localhost:8000/ws");
let x = 0;
ws.onmessage = function(event) {
const measurement = JSON.parse(event.data);
x += 1
dataPoints.push({x, y: measurement.value});
chart.update();
};
</script>
</body>
</html>

At the very beginning we just need to add some boiler plate: link to the TimeChart library and all its dependencies, style our chart placeholder and create our HTML chart placeholder (as a simple div with the id chart).

The rest of the code is our little JavaScript application where we create a TimeChart object representing our chart, create a WebSocket object and define a listener that will receive our data stream (the ws.onmessage function).

There are some things to think of when using TimeChart library:

Consuming a WebSockets data stream is also simple on the JavaScript side. We just need to specify the address of the stream (which is our ws endpoint) and define what should happen when we receive data. As we are consuming the stream as text, we just need to convert each data frame to JSON to grab our value.

Believe it or not, this is it! We have just created a real-time data stream of fake time-series data and displayed it as a dynamic self-updating chart in the browser!

Last updated on 12.7.2020.