If you want a FastAPI Postgres backend without running the database yourself, the shortest path is a managed Postgres project plus a small Python client. With WoWSQL you create a project, install wowsql from PyPI, and your FastAPI routes read and write Postgres tables through a generated REST API. You skip connection pools, migrations-on-boot and server patching, and you keep plain Postgres tables underneath.
This guide builds a small notes API step by step: setup, CRUD routes, checking user tokens, and where realtime fits.
Key facts
- Package:
pip install wowsql(version 3.10.0 on PyPI, Python 3.8 or later, built onrequestsandwebsocket-client).- Client:
WowSQLClient(project_url="myproject", api_key="..."), thenclient.table("notes")for queries.- Under the hood: every SDK call becomes a PostgREST request to
https://<slug>.wowsqlconnect.com/rest/v1.- Keys: an anon key (
wowsql_anon_...) and a service role key (wowsql_service_...). The service key bypasses row-level security, so keep it on the server.- Regions: Mumbai (the default) and US East 1. Pricing: Free at $0, Pro at $25 a month, Scale at $99 a month.
- Company: WoWSQL is built in India, DPIIT-recognised and part of NVIDIA Inception.
Why pair FastAPI with a Postgres BaaS
FastAPI is great at the HTTP layer: typed request bodies with Pydantic, automatic OpenAPI docs and dependency injection. What it doesn't give you is a database. Most tutorials then add SQLAlchemy, Alembic, a connection pool and a Postgres server you have to host and back up.
A Postgres backend-as-a-service takes the last part off your plate. Your tables live in managed Postgres, and you reach them over HTTPS with an SDK. That works well when your FastAPI service is the place for business rules, validation and integrations, and you don't want to also be the database administrator. If you'd rather see the raw HTTP layer first, our instant REST API for Postgres guide shows the endpoints the Python SDK calls.
Step 1: Create a project and a table
Sign up on wowsql.com, create a project and pick a region. Mumbai is the default and suits users in India. US East 1 suits North American traffic. Copy the project slug and both API keys from the dashboard.
Then create a table in the SQL editor:
create table notes (
id uuid primary key default gen_random_uuid(),
owner_id text not null,
title text not null,
body text default '',
created_at timestamptz default now()
);
The table is available over the REST API as soon as it exists. There is no model file to keep in sync.
Step 2: Install and connect
pip install fastapi uvicorn wowsql
Put your keys in environment variables, never in code:
export WOWSQL_PROJECT="myproject"
export WOWSQL_SERVICE_KEY="wowsql_service_..."
export WOWSQL_ANON_KEY="wowsql_anon_..."
Create one client for the whole app. project_url accepts the slug, the full domain or the full URL.
# db.py
import os
from wowsql import WowSQLClient
from wowsql.auth import ProjectAuthClient
client = WowSQLClient(
project_url=os.environ["WOWSQL_PROJECT"],
api_key=os.environ["WOWSQL_SERVICE_KEY"],
)
auth = ProjectAuthClient(
project_url=os.environ["WOWSQL_PROJECT"],
api_key=os.environ["WOWSQL_ANON_KEY"],
)
Step 3: Check who is calling
Because the server uses the service key, your FastAPI code is responsible for deciding which rows a user may touch. The front end signs users in with WoWSQL Auth and sends the access token as a Bearer header. A FastAPI dependency turns that token into a user:
# deps.py
from fastapi import Header, HTTPException
from wowsql import WowSQLError
from db import auth
def current_user(authorization: str = Header(...)):
token = authorization.removeprefix("Bearer ").strip()
try:
return auth.get_user(access_token=token) # GET /auth/v1/me
except WowSQLError:
raise HTTPException(status_code=401, detail="Invalid or expired token")
get_user returns an AuthUser with id, email and a few profile fields. Pass the token explicitly on every request so one user's session never leaks into another's. Our Postgres authentication guide covers sign-up, OAuth and token refresh in more depth.
Step 4: Write the CRUD routes
# main.py
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
from wowsql import WowSQLError
from db import client
from deps import current_user
app = FastAPI()
class NoteIn(BaseModel):
title: str
body: str = ""
@app.get("/notes")
def list_notes(user=Depends(current_user)):
result = (
client.table("notes")
.eq("owner_id", user.id)
.order_by("created_at", "desc")
.limit(50)
.get()
)
return result["data"]
@app.post("/notes", status_code=201)
def create_note(note: NoteIn, user=Depends(current_user)):
created = client.table("notes").create({**note.model_dump(), "owner_id": user.id})
return {"id": created["id"]}
@app.delete("/notes/{note_id}")
def delete_note(note_id: str, user=Depends(current_user)):
note = client.table("notes").eq("id", note_id).eq("owner_id", user.id).first()
if not note:
raise HTTPException(status_code=404, detail="Not found")
client.table("notes").delete(note_id)
return {"deleted": note_id}
Run it with uvicorn main:app --reload and open /docs for the generated Swagger page.
Notice the ownership check before the delete. update and delete in the SDK work by primary key, so confirm the row belongs to the caller first. Every query also filters on owner_id, which is the rule that keeps users apart in this design.
Sync SDK, async framework: why these routes use def
The WoWSQL Python SDK is synchronous, because it is built on requests. FastAPI's documentation (checked 28 October 2026) says a path operation declared with a normal def "is run in an external threadpool that is then awaited", so it doesn't block the event loop. That's why the routes above use def rather than async def. If you do call the SDK from inside an async def route, wrap the call with run_in_threadpool from starlette.concurrency so one slow request doesn't stall the others.
Errors, pagination and other handy calls
The SDK raises WowSQLError with a status_code and the raw PostgREST error in response. Add an exception handler once so your API returns clean errors:
from fastapi.responses import JSONResponse
@app.exception_handler(WowSQLError)
def wowsql_error(request, exc: WowSQLError):
return JSONResponse(status_code=exc.status_code or 500, content={"detail": str(exc)})
A few other calls you'll use often: .paginate(page=2, per_page=20) returns data, total and total_pages; .count() returns a row count for the current filters; and .upsert(record, on_conflict="id") inserts or updates in one call. For schema changes in CI, the separate wowsql-cli package (pip install wowsql-cli) has wowsql migration new, up, down and status.
Adding realtime and files
The same package covers more than tables. client.realtime.subscribe("notes", on_change) listens for inserts, updates and deletes over a WebSocket, which is handy for background workers that react to new rows. Our Postgres realtime guide explains how the channel model works. For uploads, WowSQLStorage from wowsql.storage handles buckets, uploads and public URLs, so a FastAPI UploadFile can go straight into a bucket.
When this setup fits, and when it doesn't
This pattern suits APIs, internal tools, AI and data services, and mobile back ends where Python is your main language and you want managed Postgres without operating it. The free plan includes one project, 256 MB of database storage, auth, realtime and the REST and MCP APIs, with no credit card. Pro at $25 a month adds 10 GB of database storage and daily backups with 7-day retention.
It fits less well if you need a long-lived direct database driver connection for heavy ORM work, or features we haven't documented yet. In that case, compare options in our best Supabase alternatives roundup, or read about managed Postgres in India for the hosting details.
FAQ
Can I use FastAPI with a managed Postgres database?
Yes. Create a managed Postgres project, install a client such as wowsql, and call it from your routes. Your FastAPI app handles HTTP and business rules while the provider runs, patches and backs up Postgres.
Is the WoWSQL Python SDK async?
No, it is synchronous and uses requests. Use normal def routes, which FastAPI runs in a threadpool, or wrap calls with run_in_threadpool inside async def routes.
Should FastAPI use the anon key or the service key?
Use the service role key on the server, because it has full access, and never ship it to a browser or app. Your FastAPI code must then check ownership itself, for example by filtering on owner_id.
How do I verify a WoWSQL user token in FastAPI?
Write a dependency that reads the Bearer token and calls auth.get_user(access_token=token) from ProjectAuthClient. It returns the user's id and email, or raises WowSQLError if the token is invalid.
Where is my data hosted?
You choose the region when you create a project: Mumbai (the default) or US East 1. Pricing is Free at $0, Pro at $25 a month and Scale at $99 a month.