---
sidebar_position: 1
sidebar_label: Getting started
---

# Getting started with FastAPI

Live updates, chat, who is online, calls and group calls in a FastAPI or Starlette app, through WordPlus Cloud. Two packages:

- **Your server** (`wordplus-realtime`, with `wordplus_realtime.fastapi`) declares your rooms once, says who may join them and call whom, and sends events to rooms and to users: the browser routes as a router, for the user your app finds.
- **The page** (`@wordplus/realtime`, from npm) connects to WordPlus Cloud, joins rooms, and sends and gets events: `emit`, `emitWithAck`, `volatile`.

What goes through WordPlus Cloud is sealed with keys only your server and the browsers of your users hold. Ready-made parts come with it, all optional: the Better Messages chat, its list of who is online and its call screen. The [example app](example.md) is a whole shop built this way.

Your framework can run websockets of its own. [Why WordPlus Cloud](../apps/why.md) says what the cloud runs for you, and what it adds.

**Try it live** at [fastapi.docs.wordplus.cloud](https://fastapi.docs.wordplus.cloud/): the [example app](example.md) of this guide as a demo, a shop of your own for a day, with Ben and Cara opening in windows of their own.

![Two browsers side by side: Ann marks order 1042 as shipped on the staff page of the shop, and the order page of Ben shows Shipped at once, without a reload](/img/apps/orders-live.webp)

## 1. Your app in WordPlus Cloud

In your WordPlus Cloud account, go to **Apps → Add app**. Name it, and list the addresses its pages are served from: `https://shop.example.com`, and `http://localhost:8000` while you develop. Only pages on those addresses may connect.

You get the app **key**, public because every page carries it, and its **secret**, for your server only. Make a **local key** of your own too: everything sealed is sealed with keys derived from it, and it never leaves your servers.

```dotenv
WORDPLUS_SITE=s1500000007
WORDPLUS_SECRET=…
WORDPLUS_LOCAL_KEY=…        # openssl rand -hex 32; the same on every server of the app, kept as long as the app
```

## 2. Install

```bash
pip install 'wordplus-realtime[fastapi]'    # uv add works the same
npm install @wordplus/realtime
npx wordplus-realtime copy static/          # the worker, the room client and the script of the components
```

The worker lets the tabs of a user share one connection, the room client runs group calls, and `realtime-ui.min.js` is the ready-made parts. Copy them again after every update of the npm package, in your build.

## 3. Your server

```python
# app.py
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
from wordplus_realtime import Realtime
import wordplus_realtime.fastapi as wr

realtime = Realtime.from_environment(       # WORDPLUS_SITE, WORDPLUS_SECRET, WORDPLUS_LOCAL_KEY
    # What others see of a user: in presence, calls and the directory.
    profile=lambda user: {"name": name_of(user), "avatar": avatar_of(user)},
    worker="/static/realtime-worker.js",
    rooms="/static/realtime-rooms.min.js",
)

# The room of an order: its customer may join, and the last 20 updates are kept.
realtime.room("order:{id}", lambda user, p: user is not None and owner_of(p["id"]) == user, history=20)

async def current_user(request: Request):
    """The user id of the request, or None: your sign-in, here a session."""
    return request.session.get("user")

app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET)
app.include_router(wr.router(realtime, current_user), prefix="/api/realtime")   # POST /auth, /calls, /keys, /webhook
app.mount("/static", StaticFiles(directory="static"), name="static")
```

- **`current_user(request)`** gives the request user id, or `None`. It may be `async`: a token, a session, a dependency of your own.
- **The pattern of a room** names its parts: `order:{id}` matches `order:1042`, and the rule gets `{"id": "1042"}`.
- **The rules** (`room()`, `calls()`, `kind()`, `profile`) are plain functions. The router runs them on a worker thread, so a blocking database query in one does not hold up the event loop.
- **The routes take JSON POSTs only.** A page on another site cannot send JSON without asking first, and it is never allowed to, so a session cookie needs no CSRF token here.

## 4. The page

```python
from fastapi.templating import Jinja2Templates

templates = Jinja2Templates(directory="templates")

@app.get("/orders/{pk}")
async def order_page(request: Request, pk: int):
    config = wr.config_for(realtime, await current_user(request))
    return templates.TemplateResponse(request, "order.html", {"realtime": config, "pk": pk})
```

```html
<!-- templates/order.html: tojson escapes it for a <script> -->
<script type="application/json" id="realtime">{{ realtime|tojson }}</script>
<script type="module" src="/static/order.js"></script>
```

```js
// order.js, through your bundler (esbuild, Vite, webpack)
import { connect } from '@wordplus/realtime';

const rt = await connect( JSON.parse( document.getElementById( 'realtime' ).textContent ) );

const order = rt.join( 'order:1042' );
order.on( 'updated', ( data ) => showStatus( data.status ) );
order.on( 'error', ( e ) => console.warn( e.code ) );   // forbidden, no_rule …
```

A single-page app can skip rendering, and connect with `connect( { app: 's1500000007', worker: '/static/realtime-worker.js' } )`, which asks `/api/realtime/keys` first.

## 5. Send

```python
@app.post("/orders/{pk}/ship")
async def ship(pk: int):
    order = await mark_shipped(pk)
    await realtime.to(f"order:{pk}").aemit("updated", {"status": order.status})
    return {"ok": True}
```

Everyone in the room gets `{ status }`, sealed with the room key on the way, and a page that reconnects later gets it from the room history. `aemit()` runs the request on a worker thread.

## Next

- [Rooms](rooms.md), [presence and who is online](presence.md), [sending from your server](sending.md) and [events from the page](events.md), with the ready-made chat
- [Calls and group calls](calls.md), with the call screen
- [Encryption and your users](encryption.md)
- [Customizing the screens](customizing.md): their look, a piece of your own, or their whole code
- [Deploying](deploying.md), and [the example app](example.md)
- The [Python reference](../apps/python-reference.md), the [browser reference](../apps/browser.md), [errors](../apps/errors.md) and [limits](../apps/limits.md)
