A full-stack To-Do application with a FastAPI back-end and a vanilla HTML/CSS/JS front-end. Features JWT authentication, per-user card ownership, paginated task lists, and a layered back-end architecture separating models, schemas, services, and routers.
- User registration and login with JWT-based authentication
- Full CRUD operations on To-Do cards, scoped to the authenticated user
- Ownership enforcement — users can only access and modify their own cards
- Paginated card listing with
skip/limitquery parameters - Input validation via Pydantic v2
- SQLite database with SQLAlchemy/SQLModel ORM
- Plain HTML/CSS/JS front-end — no framework, no build step
- Comprehensive test suite — unit, service, and integration tests
| Layer | Technology |
|---|---|
| Back-end framework | FastAPI |
| ORM | SQLModel + SQLAlchemy |
| Validation | Pydantic v2 |
| Authentication | JWT via python-jose + passlib/bcrypt |
| Database | SQLite (swappable via DATABASE_URL) |
| Front-end | HTML, CSS, Vanilla JS |
| Testing | pytest + httpx |
app/
├── core/
│ ├── config.py # Settings loaded from .env
│ ├── security.py # Password hashing and JWT creation
│ ├── dependencies.py # FastAPI dependency: get_current_user
│ └── logging.py # Logger configuration
├── db/
│ ├── database.py # SQLAlchemy engine
│ ├── init_db.py # Table creation and seed data
│ └── session.py # get_session dependency
├── errors/
│ └── card.py # Domain exceptions: CardNotFoundError etc.
├── models/
│ ├── card.py # Card SQLModel table
│ └── user.py # User SQLModel table
├── routers/
│ ├── card.py # Card endpoints
│ └── user.py # User endpoints (register, login, me)
├── schemas/
│ ├── auth.py # Token response schema
│ ├── card.py # CardCreate, CardRead, CardUpdate, CardReplace
│ └── user.py # UserCreate, UserRead
├── services/
│ ├── card.py # Card business logic
│ └── user.py # User business logic and domain exceptions
└── main.py # App initialisation, lifespan, exception handlers
static/
├── index.html # Single-page front-end
├── style.css # Design system and component styles
└── app.js # API client, state management, DOM rendering
tests/
├── conftest.py # Shared fixtures: engine, session, user
├── test_card_model.py
├── test_card_schema.py
├── test_card_services.py
├── test_card_router.py
├── test_user_model.py
├── test_user_schema.py
├── test_user_services.py
└── test_user_router.py
git clone https://github.com/your-username/index-todo.git
cd index-todopython -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activatepip install -r requirements.txtcp .env.example .envOpen .env and set SECRET_KEY to a secure random value:
openssl rand -hex 32uvicorn app.main:app --reloadThe API will be available at http://127.0.0.1:8000.
Interactive API docs are at http://127.0.0.1:8000/docs.
Open static/index.html directly in your browser. No build step or additional server required.
The front-end is a single-page application built with plain HTML, CSS, and JavaScript — no framework or bundler. All three files live in static/.
Views:
- Login / Register — tabbed auth form with inline validation and error feedback
- Card list — paginated task list with filter (all / active / completed), per-card edit and delete, and a completion toggle
- Card form — create and edit tasks with title, notes, tag, due date, and completed state
- Profile — displays username, account status, and user ID pulled from
GET /users/me
Notable details:
- Session token is stored in
sessionStorage— the user stays logged in on page refresh - Login uses
application/x-www-form-urlencodedas required by the OAuth2 password flow - Pagination fetches
limit + 1items to detect next-page availability without a separate count request - All API errors surface via a non-blocking toast notification
| Method | Endpoint | Description |
|---|---|---|
POST |
/users/ |
Register a new user |
POST |
/users/login |
Login and receive a JWT token |
GET |
/users/me |
Get the current authenticated user |
All card endpoints require a valid Authorization: Bearer <token> header.
| Method | Endpoint | Description |
|---|---|---|
POST |
/cards/ |
Create a new card |
GET |
/cards/?skip=0&limit=20 |
List cards for the current user (paginated) |
GET |
/cards/{id} |
Get a single card |
PUT |
/cards/{id} |
Full replacement of a card |
PATCH |
/cards/{id} |
Partial update of a card |
DELETE |
/cards/{id} |
Delete a card |
- Register via
POST /users/ - Login via
POST /users/login— returns an access token - Include the token in subsequent requests:
Authorization: Bearer <your_token>
pytestRun with verbose output:
pytest -vRun a specific test file:
pytest tests/test_card_router.py -vLayered architecture — HTTP concerns live in routers, business logic in services, and data structure in models and schemas. Services raise domain exceptions (CardNotFoundError, CardAlreadyCompletedError, UserAlreadyExistsError) which are caught by exception handlers in main.py and converted to HTTP responses.
Ownership enforcement — Card ownership is verified at the router layer via a get_card_for_user helper, keeping the service layer free of HTTP concerns.
Pagination — GET /cards/ accepts skip and limit query parameters, with a server-side hard cap of 100 items per request regardless of what the client requests.
Test isolation — Integration tests use FastAPI's dependency_overrides to inject an in-memory database session and bypass JWT auth. Each test runs in a transaction that is rolled back on teardown, keeping tests fully isolated without recreating the schema.
| Variable | Required | Default | Description |
|---|---|---|---|
SECRET_KEY |
Yes | — | JWT signing key |
DATABASE_URL |
No | sqlite:///./app.db |
Database connection string |
DEBUG |
No | false |
Enables SQL query logging |
ACCESS_TOKEN_EXPIRE_MINUTES |
No | 60 |
Token lifetime in minutes |