Build a WebSocket Application with FastAPI and Angular

Author:Murphy  |  View: 23631  |  Time: 2025-03-23 20:00:14
Image by geralt (Mobile Smartphone Stock Exchange) in Pixabay

Similar to HTTP, WebSocket is also a communication protocol used in client-server communications. However, unlike HTTP, WebSocket is a bidirectional interactive protocol that allows the client to send messages to the server and also receive event-driven responses passively from the server without having to make requests to the server.

WebSocket is widely used in chatting and gaming where real-time data is a must-have. It can also be used in other fields where real-time or near real-time data is important. For example, an application using historical prices to predict future ones can benefit from WebSocket. When new data comes in from a client in a data stream, the predicted ones using some machine learning or deep learning models can be sent out to the client automatically.

In this post, we will build a simple WebSocket application with FastAPI and Angular, where the former will be used to build the WebSocket server and the latter the client. The concept may sound brand-new to you and it may seem daunting to build such an application. However, as you will see in this post, it is actually not that complex and we can build a WebSocket application with several lines of code quickly.


Build a WebSocket Server with FastAPI

Fastapi, which uses Starlette behind the scene, supports WebSocket and provides some standard methods to accept a client connection, and receive and send data.

In this post, we will put the backend and frontend code in the same repository, in the backend and frontend folder, respectively. The FastAPI code will be put in the backend/app folder:

Python"># backend/app/main.py
import asyncio
import logging
from datetime import datetime

from fastapi import FastAPI, Websocket, WebSocketDisconnect

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("FastAPI app")

app = FastAPI()

async def heavy_data_processing(data: dict):
    """Some (fake) heavy data processing logic."""
    await asyncio.sleep(2)
    message_processed = data.get("message", "").upper()
    return message_processed

# Note that the verb is `websocket` here, not `get`, `post`, etc.
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    # Accept the connection from a client.
    await websocket.accept()

    while True:
        try:
            # Receive the JSON data sent by a client.
            data = await websocket.receive_json()
            # Some (fake) heavey data processing logic.
            message_processed = await heavy_data_processing(data)
            # Send JSON data to the client.
            await websocket.send_json(
                {
                    "message": message_processed,
                    "time": datetime.now().strftime("%H:%M:%S"),
                }
            )
        except WebSocketDisconnect:
            logger.info("The connection is closed.")
            break

Keypoints for this code snippet:

  1. In the path decorator of a WebSocket endpoint, @app.websocket is used, rather than those with HTTP verbs like @app.get, @app.post, etc.
  2. A WebSocket object can be injected in the path operation function, which can be used to accept client connections, receive data from and send data to the client with some standard methods (accept, receive_json and send_json). The code is actually pretty self-explanatory and you can use it similarly in your application as well.

Now you can start the WebSocket server with uvicorn:

uvicorn main:app --reload --host 0.0.0.0 --port 8000

Unlike HTTP endpoints, it's not so straightforward to test WebSocket endpoints independently without a client. It is easier to set up a client and test them from there. In this post, we will use Angular to build a WebSocket client and will interact with the WebSocket server from there.


Set up the Angular project

In this post, Node.js 18.13.0 and Angular v15.1.2 are used. It's recommended to use these versions if you want to follow along and see the exact results as demonstrated. However, older or newer versions should also work properly.

To install Angular CLI, run:

npm install -g @angular/[email protected]

Then we can create a workspace with the ng command:

ng new frontend

We will not add routing for this simple demonstration project, and all the other settings can be left as default.

We will not create a new component and will update the existing app component directly.

However, we will create a new service file to manage WebSocket-related code.

A service class in Angular is just a regular class plus some settings, normally only the providedIn setting in the Injectable decorator. We can create the service file (websocket.service.ts) directly in the app folder.


Create the WebSocket service

Thanks to the RxJS library, using WebSocket in Angular is very simple. We can import the WebSocket factory function from RxJS and use it to create a WebSocketSubject which can then be used to send data to and receive data from a WebSocket server.

The code of the service file is as follows:

// frontend/src/app/websocket.service.ts
import { Injectable } from '@angular/core';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { environment } from '../environments/environment';

interface MessageData {
  message: string;
  time?: string;
}

@Injectable({
  providedIn: 'root',
})
export class WebSocketService {
  private socket$!: WebSocketSubject;
  public receivedData: MessageData[] = [];

  public connect(): void {
    if (!this.socket$ || this.socket$.closed) {
      this.socket$ = webSocket(environment.webSocketUrl);

      this.socket$.subscribe((data: MessageData) => {
        this.receivedData.push(data);
      });
    }
  }

  sendMessage(message: string) {
    this.socket$.next({ message });
  }

  close() {
    this.socket$.complete();
  }
}

Keypoints for this file:

  1. The webSocket factory function and the WebSocketSubject subject is the key to using WebSocket in Angular.
  2. To create a WebSocketSubject, we need to pass in the URL of the WebSocket server, which is ws://localhost:8000/ws in this example. Note that the protocol is ws, not http. It is the one we set in the FastAPI application above, and is saved in environment.ts as an environment variable.
  3. WebSocketSubject works in the same way as regular RxJS Subject. Therefore, it can be subscribed to receive data sent from the server, and we can also use next() to send messages to the server.
  4. When we don't need to interact with the WebSocket server anymore, or when the corresponding component is destroyed, we can call complete() on the WebSocketSubject to complete it and thus close the connection. We should handle the connection closing event properly in the backend code, otherwise, exceptions will be raised.
  5. The received data are saved to an array and will be displayed to the user together as we will see later.

Use the WebSocket service in the component

Now that the WebSocket service is created, we can use it in our component. We should have some mechanism to open and close the connection to the WebSocket server. We will also implement the features to send messages to the server and display the received ones.

This is the code for the component file:

// frontend/src/app/app.component.ts
import { Component, OnDestroy } from '@angular/core';
import { WebSocketService } from './websocket.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnDestroy {
  message = '';

  constructor(public webSocketService: WebSocketService) {
    this.webSocketService.connect();
  }

  sendMessage(message: string) {
    this.webSocketService.sendMessage(message);
  }

  ngOnDestroy() {
    this.webSocketService.close();
  }
}

As we see, all the data logic is encapsulated in the WebSocketService.

And this template file demonstrates how to send data and display the received data:


Send a message to the server:

Received messages from the server:

  • {{ data.time }}: {{ data.message }}

Note that in order to use ngModel we need to import FormsModule in app.module.ts.

After everything is set up, we can run ng serve to bring up the Angular application locally and go to http://127.0.0.1:4200 to visit it. Try to send some messages sequentially and see what will happen:

As demonstrated, we can send messages to the server continuously and the processed data will pop up when it is ready in the backend, without having to poll the server for a reply.


Dockerize the backend and frontend code

To make the code easier to share on different platforms, let's dockerize the backend and frontend code.

For the FastAPI code, we need to create a custom Docker image with a Dockerfile:

# backend/Dockerfile
FROM python:3.11

WORKDIR /app

COPY ./requirements.txt /app/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

COPY ./app /app

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

requirements.txt has the dependencies for the application:

# backend/requirements.txt
fastapi>=0.89.0,<0.90.0
uvicorn[standard]>=0.20.0,<0.21.0

Note that you must install uvicorn[standard] and not just uvicorn, otherwise, WebSocket cannot be used in FastAPI.

Then we need to create a docker-compose.yaml file in order to spin up the backend and frontend code more easily:

# ./docker-compose.yaml
version: "3.9"

services:
  fastapi_app:
    build:
      context: ./backend
    image: fastapi_app:latest
    ports:
      - target: 8000
        published: 8000
    volumes:
      - type: bind
        source: ./backend/app
        target: /app
    networks:
      - websocket

  angular:
    image: node:18.13.0-alpine
    working_dir: /frontend
    ports:
      - target: 4200
        published: 4200
    volumes:
      - type: bind
        source: ./frontend
        target: /frontend
    networks:
      - websocket
    command: npm start

networks:
  websocket:
    name: websocket
    driver: bridge

The ports are set in a way that everything would work exactly the same as in the example above without using Docker.

The code for the whole project can be found here. You are welcome to download it and test it yourself.

When everything is set up, you can use docker-compose to start the services:

docker-compose build
docker-compose up -d

In case you encounter port conflict issues, you can use the command below to find out which applications are using the ports (4200 and 8000) and kill them if applicable:

sudo lsof -i -P -n | grep LISTEN

If everything works properly, you can access the WebSocket web application at http://localhost:4200 as before. Everything should work exactly the same as before.


In this post, we have introduced how to write backend and frontend code to set up a simple interactive real-time web application using WebSocket. The WebSocket server is created with FastAPI and the web user interface is with Angular. Both FastAPI and Angular (through RxJS) provide simple ways to implement the WebSocket protocol and make it very easy to create web applications using WebSocket connections.

This post can help you get started building low-latency or real-time web applications using WebSocket. It can be a valuable tool for data processing as well. For example, most machine learning models take time to build and predict results. With WebSocket, we can build a near real-time application where machine learning predicted data can keep popping up when they become available in the backend.


Related articles:

Tags: Angular Fastapi Hands On Tutorials Python Websocket

Comment