---
sidebar_position: 10
---

# FastAPI and Starlette

`wordplus_realtime.fastapi` gives FastAPI the browser's routes as a router, for the user your app finds.

```bash
pip install 'wordplus-realtime[fastapi]'
npm install @wordplus/realtime
```

## Your server

```python
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from wordplus_realtime import Realtime
import wordplus_realtime.fastapi as wr

realtime = Realtime.from_environment(
    profile=lambda user: {"name": name_of(user)},
    worker="/static/realtime-worker.js",
    rooms="/static/realtime-rooms.min.js",
)
realtime.channel("private-orders-", lambda user, channel: user is not None and owner_of(channel[15:]) == user)

async def current_user(request: Request):
    session = await session_of(request)
    return session.user_id if session else None

app = FastAPI()
app.include_router(wr.router(realtime, current_user), prefix="/api/realtime")   # POST /auth, /calls, /keys
app.mount("/static", StaticFiles(directory="static"), name="static")            # npx wordplus-realtime copy static/
```

- **`current_user(request)`** gives the request's user's id, or `None`. It may be `async`.
- **The owners** (`channel()`, `calls()`, `kind()`, `profile`) are plain functions. The router runs them on a worker thread, so a blocking database query in one doesn't hold up the event loop.
- **The routes take JSON POSTs only.** A page on another site can't send JSON without asking first, and it is never allowed to. A session cookie therefore needs no CSRF token here.

## The page

Render the settings into the page:

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

```html
<!-- order.html (Jinja) -->
<script>window.wordplusRealtimeConfig = {{ realtime|tojson }};</script>
```

```js
import { connect } from '@wordplus/realtime';

const rt = await connect( window.wordplusRealtimeConfig );
rt.subscribe( `private-orders-${ pk }` ).on( 'updated', ( data ) => refresh( data ) );
```

A single-page app can skip that, and connect with `connect( { site: 's1500000007', worker: '/static/realtime-worker.js' } )`, which asks `/api/realtime/keys` first. An app on another address, such as one in Capacitor, uses `endpoint: 'https://api.example.com/api/realtime'`, and your server answers the app's address with CORS.

## Publishing

```python
@app.post("/orders/{pk}/pay")
async def pay(pk: int):
    order = await mark_paid(pk)
    await realtime.apublish(f"private-orders-{pk}", "updated", {"status": order.status})
    return order
```

`apublish()` runs the request on a worker thread. It raises `RealtimeError` when WordPlus Cloud refuses, with its `code` and `status`.
