Skip to content

Getting Started with Realtime

Three steps: turn it on, mount the endpoint, and say which models announce their writes.

Step 1: Turn it on

from fastedgy.app import FastEdgy

app = FastEdgy(realtime=True)

Off by default: it costs a LISTEN connection and a pool of consumers per worker holding sockets, which an application with nothing to announce should not pay. Turned on, the services are registered, a worker starts listening with the first socket it takes, and stops with the application. Nothing else to wire in a lifespan.

PostgreSQL only

Announcements travel through LISTEN/NOTIFY, so realtime needs PostgreSQL.

Step 2: Mount the endpoint

The socket route is mounted like any other FastEdgy router, on whichever public router the application builds:

from fastapi import APIRouter
from fastedgy.api import realtime

public_router = APIRouter(prefix="/api")
public_router.include_router(realtime.router)

app.include_router(public_router)

It belongs on a public router, not behind an authentication dependency: a browser cannot set an Authorization header on a WebSocket handshake, so the socket authenticates itself with its own first frame.

Step 3: Declare the models that announce

Nothing is announced by default. An event nobody is watching for is a NOTIFY, a wake-up on every worker holding that scope and a frame on every socket of it, so a model says so for itself:

from fastedgy.models.base import BaseModel
from fastedgy.models.mixins import WorkspaceableMixin
from fastedgy.orm import fields
from fastedgy.realtime import realtime_model


@realtime_model()
class Company(BaseModel, WorkspaceableMixin):
    name = fields.CharField(max_length=200)
    domain = fields.CharField(max_length=200, null=True)

Every insert, update and delete of a company is now announced to the clients reading its scope, whoever made it: a browser, an agent through the MCP server, a queued task.

What a client receives

Two channels carry each write, and a client subscribes to the one it needs:

Channel Who subscribes What it hears
company a list of companies every write of the model
company:42 the page reading company 42 writes on that record

The frame names what moved:

{
  "type": "company.updated",
  "data": { "model": "company", "id": 42 },
  "changed": ["name"]
}

Only identifiers travel. The client reads the record back through the API, which is what keeps an announcement from carrying a field its reader is not allowed to see.

Step 4: Connect a client

In a Vue.js application, one call in the shell holds the socket, and one call per view says what it reads:

import { useRealtime, useResourceChanged } from 'vue-fastedgy';

// Once, in the application shell
useRealtime();

// In a view
useResourceChanged('company', () => reload());

In a Flutter application, flutter_fastedgy holds the socket once asked to, and a holder follows what it holds:

await initializeFastEdgy(realtime: true);

// Whenever the scope being read changes
getService<RealtimeSocket>().watch(scopeSlug);

// A list or a record keeps itself current
final companies = ApiCollection(CompanyApi(), fields: ['id', 'name']);

// Or a listener of its own, cancelled with its screen
final watch = watchResource(CompanyApi(), (event) => reload());

See Vue.js / Realtime for the composables, and Technical Details for the raw protocol if you connect from something else.

Next steps

Usage Guide Technical Details