App shapes
Publish a FastAPI app
FastAPI works like any async Python app on Ellivate. The auth middleware is injected at publish time; your routes see a per-viewer identity automatically. No sign-in routes, no database drivers.
A minimal FastAPI app
Save as main.py:
from fastapi import FastAPI
import ellivate
app = FastAPI()
@app.get("/")
async def home():
viewer = ellivate.viewer()
items = ellivate.kv.get("shared:items") or []
return {
"welcome": viewer.username if viewer else "friend",
"items": items,
}
@app.post("/items")
async def add(item: str):
current = ellivate.kv.get("shared:items") or []
current.append(item)
ellivate.kv.set("shared:items", current)
return {"ok": True, "count": len(current)}
And a requirements.txt:
fastapi>=0.110
uvicorn>=0.27That's a deployable FastAPI app. Run ellivate publish.
What the pipeline does
1. Writes ellivate_client.py
Dropped at project root. Import with import ellivate. No pip install — it's rewritten fresh on every publish so it stays in sync with the backend.
2. Writes ellivate_auth.py and wires middleware
The translator detects app = FastAPI() in your entry file and inserts init_ellivate_auth(app) right after. That call:
- Adds a
/ellivate-handoffroute so the shell can set the viewer cookie. - Adds a Starlette middleware that verifies the cookie on every other request. Missing cookie → 401. Valid cookie → the request continues.
- Stashes the viewer's identity on a
contextvars.ContextVarthe SDK reads on every KV call. That's how yourellivate.kv.get("personal:...")calls know whose personal data to return.
3. Generates a start command
FastAPI doesn't self-host; Ellivate writes a Procfile-equivalent that runs uvicorn main:app --host 0.0.0.0 --port $PORT. Binding to 0.0.0.0 is required for the container networking — localhost won't be reachable.
Per-viewer scopes in action
Each viewer gets their own personal:-scoped data. For example, a per-viewer draft:
@app.post("/drafts")
async def save_draft(body: str):
# "personal:" prefix means each viewer has their own draft
ellivate.kv.set("personal:draft", body)
return {"saved": True}
@app.get("/drafts")
async def get_draft():
return {"draft": ellivate.kv.get("personal:draft") or ""}Two users hitting the same endpoint see different drafts. Ellivate handles the routing server-side based on the viewer cookie.
JSON APIs for a separate frontend
If you're shipping a FastAPI backend that a separate Next.js / React client will call, the client needs to forward the viewer token. Easiest approach: serve the client from the same Ellivate app (a static/ directory), or use window.ELLIVATE_VIEWER_TOKEN in the browser and add it to fetch headers:
// Client
const res = await fetch("/api/items", {
headers: {
"X-Ellivate-Viewer-Token":
(window as any).ELLIVATE_VIEWER_TOKEN ?? "",
},
});Things to avoid
- SQLAlchemy / databases / tortoise-orm for app state. Use
ellivate.kv. If you genuinely need relational queries, Ellivate will flag it at publish time and ask you to upgrade — we don't ship a sqlite into a container that gets wiped on redeploy. - FastAPI-Users, fastapi-login, OAuth flows. Identity is the shell's job.
@app.on_event("startup")that calls KV. At startup there's no request, no viewer — the SDK falls back to server lane (app-wide data). If you really mean app-wide, that's fine; if you meant per-viewer, do it inside a route instead.
What's next
- Persistence & scopes — deep dive on the KV model.
- Auth model — how the handoff, viewer cookie, and caller lane fit together.