Quickstart: Python and FastAPI
Build a notes API with FastAPI that signs users in through Sovaria and runs every query as the calling user with the async Python SDK.
A FastAPI service with POST /signup, POST /signin, GET /notes and POST /notes, using the async client. Each request runs as the user whose token it carries, so the row rules decide what it sees.
#Before you start
Do steps 1 to 3 of the Quickstart first: create a project, copy its project URL and anon key (Quickstart), and create the notes table with its row rules (Quickstart). Every signed-in user then sees and changes only their own notes, whatever the app code does.
#1. Create the project
mkdir notes-api && cd notes-api
python3 -m venv .venv && . .venv/bin/activate
pip install "fastapi[standard]" sovaria-sdk
export SOVARIA_URL=https://<project-ref>.sovarias.in SOVARIA_ANON_KEY=<anon-key>Python 3.11 or newer.
#2. The service
import os
from fastapi import Depends, FastAPI, Header, HTTPException
from pydantic import BaseModel
from sovaria import SovariaError, create_client
URL, ANON_KEY = os.environ["SOVARIA_URL"], os.environ["SOVARIA_ANON_KEY"]
app = FastAPI()
class Credentials(BaseModel):
email: str
password: str
class NoteIn(BaseModel):
body: str
def to_objects(result: dict) -> list[dict]:
"""sql_query answers columns, rows and row_count; rows are lists in column order."""
return [dict(zip(result["columns"], row)) for row in result["rows"]]
async def as_user(authorization: str = Header(default="")):
"""A client that runs every call as the user whose token the request carries."""
token = authorization.removeprefix("Bearer ").strip()
if not token:
raise HTTPException(401, "Sign in first")
async with create_client(URL, ANON_KEY) as client:
client.set_access_token(token)
yield client
def fail(err: SovariaError):
raise HTTPException(err.status_code or 502, str(err.detail))
def tokens(t) -> dict:
"""What the app keeps: the access and refresh tokens (no other fields of the sign-in answer)."""
return t.model_dump(include={"access_token", "refresh_token", "expires_in", "user_id"})
@app.post("/signup")
async def signup(c: Credentials):
async with create_client(URL, ANON_KEY) as client:
try:
return tokens(await client.sign_up(c.email, c.password))
except SovariaError as err:
fail(err)
@app.post("/signin")
async def signin(c: Credentials):
async with create_client(URL, ANON_KEY) as client:
try:
return tokens(await client.sign_in(c.email, c.password))
except SovariaError as err:
fail(err)
@app.get("/notes")
async def list_notes(sovaria=Depends(as_user)):
try:
return to_objects(await sovaria.sql_query("select id, body, created_at from notes order by id desc"))
except SovariaError as err:
fail(err)
@app.post("/notes", status_code=201)
async def add_note(note: NoteIn, sovaria=Depends(as_user)):
try:
result = await sovaria.sql_query("insert into notes (body) values ($1) returning id, body", params=[note.body])
return to_objects(result)[0]
except SovariaError as err:
fail(err)#3. Run and try it
fastapi dev main.py # http://127.0.0.1:8000/docs
TOKEN=$(curl -s 127.0.0.1:8000/signup -H 'Content-Type: application/json' \
-d '{"email":"ada@example.com","password":"Correct-Horse-9!"}' | jq -r .access_token)
curl -s 127.0.0.1:8000/notes -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"body":"From FastAPI"}'
curl -s 127.0.0.1:8000/notes -H "Authorization: Bearer $TOKEN"#Next steps
- Check who can read and change
notesbefore you ship: Tutorial: prove your table is private before you ship. - Push new notes to other devices as they happen: Database change events.
- Django, Flask and sync code: Python: install and configure.
- Every SDK method: Python SDK.
#Coming from Firebase or Supabase?
| Platform | What this is called there |
|---|---|
| Firebase | Firebase Admin SDK for Python |
| Supabase | supabase-py with FastAPI |
| Sovaria | Quickstart: Python and FastAPI |
Moving an existing app? Migrate from Supabase or Firebase imports users (with their passwords), data, files and rules.