A JWT-authenticated REST API starter: FastAPI, SQLAlchemy, per-user resource ownership enforced at the query level, and a Postgres-ready deployment that runs on SQLite with zero setup for local development.
"Build me a REST API with user accounts" is one of the highest-volume
request types on any freelance platform, and most of what makes it
tedious isn't the business logic — it's auth (hashing, tokens, the
/register//login dance), making sure user A can't read or edit user
B's data, and a database setup that works locally without a Postgres
server running.
Auth, ownership, and database wiring are solved once here so a specific
project's actual resources (Task in this starter — swap the name, the
CRUD/auth/ownership pattern doesn't change) don't have to re-solve them:
- JWT auth —
bcryptfor hashing,python-josefor tokens, wired through FastAPI'sOAuth2PasswordBearerso/docsgets a working "Authorize" button for free. - Ownership enforced in the query, not after it — every task lookup
filters by
owner_id == current_user.idin the database query itself; a task that exists but belongs to someone else returns 404, not 403 (403 would confirm the ID exists to a caller who doesn't own it). - SQLite by default, Postgres via one environment variable —
DATABASE_URLunset runs against a local file with no setup; set it to a Postgres URL indocker-compose.ymlor production with no code change.
flowchart LR
Client(["client"]) -->|"POST /auth/register\nPOST /auth/login"| AuthRouter["auth router"]
AuthRouter -->|"bcrypt hash / verify"| Auth["auth.py"]
AuthRouter -->|"JWT"| Auth
Client -->|"Bearer token"| Dep["get_current_user\n(dependency)"]
Dep -->|"decode JWT"| Auth
Dep --> TasksRouter["tasks router"]
Client -->|"GET/POST/PATCH/DELETE /tasks"| TasksRouter
TasksRouter -->|"filter by owner_id"| DB[("SQLite (dev)\nPostgres (prod)")]
AuthRouter --> DB
git clone https://github.com/kestrelquant/rest-api-starter
cd rest-api-starter
pip install -r requirements.txt
uvicorn app.main:app --reloadInteractive docs (with a working "Authorize" button once you have a
token) at http://localhost:8000/docs.
cp .env.example .env # set JWT_SECRET_KEY
docker compose up -dcurl -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "smoke@test.com", "password": "hunter22"}'
# {"id":1,"email":"smoke@test.com"}
curl -X POST http://localhost:8000/auth/login \
-d "username=smoke@test.com&password=hunter22"
# {"access_token":"eyJ...","token_type":"bearer"}
curl -X POST http://localhost:8000/tasks \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{"title": "Ship it"}'The register/health responses above are copied from an actual local run of this exact code, not hand-written.
19 tests, fully isolated — each test gets its own in-memory SQLite
database via a fixture, so nothing leaks between tests or touches a real
app.db file.
pip install -r requirements-dev.txt
python -m pytest tests/CI (.github/workflows/ci.yml) runs the suite on Python 3.10–3.12 and
builds the Docker image.
See CONTRIBUTING.md.
MIT — see LICENSE.