A modern, secure, full-stack todo application built with Next.js, FastAPI, and PostgreSQL. This project implements a complete task management system with JWT-based authentication, responsive UI, and data isolation between users.
- β Full CRUD Operations - Create, read, update, and delete tasks
- π Secure Authentication - JWT-based authentication with Better Auth
- π₯ Multi-User Support - Complete data isolation between users
- π± Responsive Design - Works seamlessly on mobile and desktop
- β‘ Optimistic UI - Instant feedback with automatic rollback on errors
- π― Task Management - Mark tasks as complete/incomplete, filter by status
- π₯ Health Checks - Database connection monitoring endpoint
- βΏ Accessible - ARIA labels and keyboard navigation support
- Tech Stack
- Project Structure
- Quick Start
- Environment Variables
- Development
- API Documentation
- Testing
- Deployment
- Architecture
- Framework: Next.js 16+ (App Router)
- Language: TypeScript
- Styling: TailwindCSS
- Authentication: Better Auth (JWT plugin)
- UI Components: Custom responsive components
- Framework: FastAPI
- Language: Python 3.13+
- ORM: SQLModel
- Database: Neon Serverless PostgreSQL
- Authentication: JWT validation with python-jose
- Server: Uvicorn
- Package Manager:
- Frontend: npm
- Backend: UV (Python package manager)
- Version Control: Git
hackathon-todo/
βββ frontend/ # Next.js application
β βββ app/ # App Router pages and layouts
β β βββ (auth)/ # Authentication pages (signin, signup)
β β βββ (dashboard)/ # Protected dashboard pages
β β βββ page.tsx # Landing page
β βββ components/ # Reusable UI components
β β βββ Navbar.tsx
β β βββ TaskForm.tsx
β β βββ TaskItem.tsx
β β βββ TaskList.tsx
β βββ lib/ # Utilities and configurations
β β βββ api.ts # API client with JWT
β β βββ auth.ts # Server-side auth config
β β βββ auth-client.ts # Client-side auth hooks
β β βββ types.ts # TypeScript interfaces
β βββ package.json
β
βββ backend/ # FastAPI application
β βββ main.py # App initialization and health checks
β βββ config.py # Environment configuration
β βββ db.py # Database engine and session
β βββ models.py # SQLModel task model
β βββ schemas.py # Pydantic request/response schemas
β βββ middleware.py # JWT authentication middleware
β βββ routes/ # API route handlers
β β βββ tasks.py # Task CRUD endpoints
β βββ migrations/ # Database migrations
β β βββ 001_create_tasks_table.sql
β βββ scripts/ # Utility scripts
β β βββ migrate.py # Migration runner
β βββ pyproject.toml
β
βββ specs/ # Feature specifications
βββ overview.md
βββ architecture.md
βββ 001-phase2-implementation/
βββ spec.md
βββ plan.md
βββ tasks.md
βββ data-model.md
- Node.js 18+ and npm
- Python 3.13+
- UV Python package manager (installation guide)
- Neon PostgreSQL account and database (sign up)
git clone <repository-url>
cd hackathon-todocd frontend
# Install dependencies
npm install
# Create environment file
cp .env.local.example .env.local
# Edit .env.local with your configuration:
# - NEXT_PUBLIC_API_URL=http://localhost:8000
# - BETTER_AUTH_SECRET=<your-secret-key>
# - DATABASE_URL=<your-neon-postgres-url>
# - BETTER_AUTH_URL=http://localhost:3000
# Start development server
npm run devThe frontend will be available at http://localhost:3000
cd backend
# Install dependencies using UV
uv pip install -e .
# Create environment file
cp .env.example .env
# Edit .env with your configuration:
# - DATABASE_URL=<your-neon-postgres-url>
# - BETTER_AUTH_SECRET=<same-secret-as-frontend>
# - API_HOST=0.0.0.0
# - API_PORT=8000
# Run database migrations
python scripts/migrate.py
# Start development server
uvicorn main:app --reload --host 0.0.0.0 --port 8000The backend will be available at http://localhost:8000
API documentation (Swagger UI): http://localhost:8000/docs
# API Configuration
NEXT_PUBLIC_API_URL=http://localhost:8000
# Better Auth Configuration
BETTER_AUTH_SECRET=your-secret-key-min-32-chars
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=postgresql://user:password@host/database
# Next.js Configuration (optional)
NEXT_PUBLIC_APP_URL=http://localhost:3000# Database Configuration
DATABASE_URL=postgresql://user:password@host/database
# Authentication
BETTER_AUTH_SECRET=your-secret-key-min-32-chars
# Server Configuration
API_HOST=0.0.0.0
API_PORT=8000
ENVIRONMENT=developmentImportant: Use the same BETTER_AUTH_SECRET in both frontend and backend for JWT validation to work correctly.
cd frontend
# Start dev server
npm run dev
# Build for production
npm run build
# Run linter
npm run lint
# Format code
npm run formatcd backend
# Start dev server with auto-reload
uvicorn main:app --reload
# Run with custom host/port
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# Run migrations
python scripts/migrate.pyMigrations are located in backend/migrations/. To create a new migration:
- Create a new SQL file:
backend/migrations/00X_description.sql - Write your migration SQL
- Run:
python backend/scripts/migrate.py
- Sign Up:
POST /api/auth/signup(handled by Better Auth) - Sign In:
POST /api/auth/signin(handled by Better Auth) - Get Session:
GET /api/auth/session(handled by Better Auth)
Better Auth automatically handles user registration, login, and JWT token generation.
All task endpoints require JWT authentication via Authorization: Bearer <token> header.
GET /api/{user_id}/tasks?status=allQuery parameters:
status:all|pending|completed(default:all)
POST /api/{user_id}/tasks
Content-Type: application/json
{
"title": "Buy groceries",
"description": "Milk, eggs, bread"
}GET /api/{user_id}/tasks/{task_id}PUT /api/{user_id}/tasks/{task_id}
Content-Type: application/json
{
"title": "Updated title",
"description": "Updated description"
}PATCH /api/{user_id}/tasks/{task_id}/completeDELETE /api/{user_id}/tasks/{task_id}GET /api/healthReturns database connection status and environment info.
cd frontend
npm testComprehensive testing procedures are documented in backend/TESTING.md.
-
Concurrent Operations (T089):
- Open multiple browser tabs
- Perform simultaneous create/update/delete operations
- Verify data consistency
-
Data Isolation (T090):
- Create two user accounts
- Verify User A cannot access User B's tasks
- Test all CRUD operations across users
-
Performance Testing:
- Measure API response times (target: < 500ms p95)
- Test frontend load time (target: < 2 seconds)
See backend/TESTING.md for detailed test procedures.
cd frontend
npm run build
# Deploy to Vercel or your preferred hosting platformEnvironment variables must be set in your deployment platform.
The backend can be deployed to:
- Heroku: Add
Procfilewithweb: uvicorn main:app --host 0.0.0.0 --port $PORT - Railway: Configure start command in
railway.toml - Docker: Use provided Dockerfile (if available)
- AWS/GCP: Deploy as containerized application
Ensure DATABASE_URL points to your production Neon database.
User β Next.js β Better Auth β JWT β FastAPI β SQLModel β Neon DB
β β β
Authorization Header Verify Token Filter by user_id
- Stateless Backend: No session storage; all authentication via JWT
- JWT-Based Auth: Shared secret between frontend and backend
- User Isolation: All database queries filtered by
user_idfrom JWT - Optimistic UI: Immediate feedback with automatic rollback on errors
- Responsive Design: Mobile-first approach with Tailwind breakpoints
- Security: CORS configuration, JWT validation, ownership verification
The app uses 6 tables in Neon PostgreSQL β 1 application table and 5 managed by Better Auth.
| Column | Type | Constraints |
|---|---|---|
id |
UUID | Primary key, auto-generated |
user_id |
VARCHAR(255) | Not null, indexed β links to user.id |
title |
VARCHAR(200) | Not null |
description |
TEXT | Optional |
completed |
BOOLEAN | Not null, default: false |
created_at |
TIMESTAMPTZ | Auto-set on insert |
updated_at |
TIMESTAMPTZ | Auto-updated via trigger |
Indexes: idx_tasks_user_id, idx_tasks_user_created (user_id, created_at DESC), idx_tasks_user_completed (user_id, completed)
| Column | Type | Constraints |
|---|---|---|
id |
TEXT | Primary key |
name |
TEXT | Not null |
email |
TEXT | Not null, unique |
emailVerified |
BOOLEAN | Default: false |
image |
TEXT | Optional |
createdAt / updatedAt |
TIMESTAMP | Auto-managed |
| Column | Type | Constraints |
|---|---|---|
id |
TEXT | Primary key |
token |
TEXT | Not null, unique |
expiresAt |
TIMESTAMP | Not null (7 days TTL) |
userId |
TEXT | FK β user.id CASCADE DELETE |
ipAddress / userAgent |
TEXT | Optional |
createdAt / updatedAt |
TIMESTAMP | Auto-managed |
Stores hashed passwords (email/password auth) or OAuth tokens per user.
| Column | Type | Notes |
|---|---|---|
id |
TEXT | Primary key |
providerId |
TEXT | e.g. credentials, google |
accountId |
TEXT | Provider-side user ID |
password |
TEXT | Hashed password (null for OAuth) |
userId |
TEXT | FK β user.id CASCADE DELETE |
accessToken / refreshToken / idToken |
TEXT | OAuth tokens (null for email auth) |
Temporary tokens for email verification and password reset flows.
| Column | Type | Notes |
|---|---|---|
id |
TEXT | Primary key |
identifier |
TEXT | Email being verified |
value |
TEXT | The verification token |
expiresAt |
TIMESTAMP | Short-lived (typically 1 hour) |
Stores the EdDSA key pair used to sign and verify JWT tokens.
| Column | Type | Notes |
|---|---|---|
id |
TEXT | Primary key |
publicKey |
TEXT | Exposed at /api/auth/jwks β used by backend to verify tokens |
privateKey |
TEXT | Encrypted with BETTER_AUTH_SECRET β used to sign JWTs |
createdAt |
TIMESTAMP | Key generation time |
Note: If you change
BETTER_AUTH_SECRET, delete all rows injwksso Better Auth regenerates the key pair with the new secret. Otherwise login will fail with a decryption error.
This project is part of Phase 2 of the Hackathon Todo project.
This is a learning project for implementing spec-driven development with full-stack technologies.
For issues or questions:
- Check the documentation in
/specs - Review the implementation plan in
/specs/001-phase2-implementation/plan.md - See testing procedures in
backend/TESTING.md
Built with: Next.js β’ FastAPI β’ PostgreSQL β’ TailwindCSS β’ Better Auth β’ TypeScript β’ Python
Phase 2 Full-Stack Implementation - December 2025