Keeping every screen in sync while people change the same events

Realtime Events lets people create, join, leave, cancel and restore events while every open screen updates live, with the database as the single source of truth.

Role
Sole engineer, backend and frontend
Stack
FastAPI, SQLAlchemy, PostgreSQL, React, TypeScript
Live demo
tt-events-realtime-frontend-react.vercel.app
Source
Backend ·Frontend
Realtime Events on a desktop: the event list with dates, places and attendee counts, and the selected event with its attendees
The event list and the selected event, with demo data. A canceled event stays visible and can be restored by its organizer.

The problem

Several people act on the same events at the same time: one edits, another joins, the organizer cancels. Every open screen has to show the current state without a refresh, and no screen may show something the database does not hold.

The usual shortcut, pushing state over the socket and treating it as the truth, breaks the moment a message is lost or arrives out of order.

How it works

The rules live in the domain

Event lifecycle, organizer permissions and joining rules sit in a pure domain layer. FastAPI only parses requests and calls use cases, so the rules are tested without a server.

HTTP is the source of truth

Every change goes through the REST API and is committed first. Only then does one WebSocket channel notify every open client, which maps the notification into its own state. A notification a client cannot read makes it reload from the API.

A written contract for the live channel

The WebSocket messages are specified in an AsyncAPI 3.1 document, so the frontend and any other client build against the same contract.

Honest about scale

The live channel runs inside one service instance. The documentation names the next step to scale it out, a shared broker such as Redis Pub/Sub or PostgreSQL LISTEN/NOTIFY, instead of pretending the limit is not there.

Architecture

Ports and adapters. FastAPI is only the entry point; use cases work over the domain, and persistence, the live channel and migrations are adapters. SQLite runs locally and PostgreSQL in production.

Clients

  • React appTypeScript · Vite · on Vercel
  • Every other open clientSame event list, live

Entry point

FastAPIREST /events, /joiners · WebSocket /ws/events

Application core

Use cases

  • Create
  • Update
  • Cancel and restore
  • Join
  • Leave

Domain

Event lifecycle · Organizer permissions · Joining rules

No framework code

Adapters

  • PostgreSQLSQLAlchemy repositories · Supabase in production
  • Realtime publisherNotifies clients after each commit
  • Alembic migrationsVersioned schema changes

The same events on a phone

A split list and detail view on desktop becomes one list on a phone. Joined and canceled events are marked, and the interface switches between English and German.

Realtime Events on a phone: the event list with joined and canceled events marked

Result

The service runs in production with a public demo: the frontend on Vercel, the API on Render and PostgreSQL behind Alembic migrations.

The backend has 100+ automated tests with coverage above 85%, enforced in CI, and the frontend keeps UI components apart from API and WebSocket logic, with its own unit tests.

Python · FastAPI · SQLAlchemy · Alembic · PostgreSQL · WebSockets · AsyncAPI · React · TypeScript · Vite · Tailwind

Need live features that stay consistent?

Tell me on LinkedIn what you are building. I answer every message.