A modern full-stack web application with Better Auth (frontend) and FastAPI (backend) using shared JWT authentication.
This is a production-ready todo application featuring:
- π Better Auth + JWT: Secure authentication with token-based API access
- β Task Management: Full CRUD operations with user isolation
- π€ Multi-user Support: Each user has their own tasks
- π¨ Modern UI: Responsive design with Tailwind CSS
- ποΈ PostgreSQL Database: Persistent data storage
- π Stateless Auth: Frontend and backend verify JWT tokens independently
- π§ͺ Comprehensive Tests: 15/15 backend tests passing
Create .env files with the SAME SECRET KEY in both:
backend/.env:
DATABASE_URL=postgresql://postgres:password@localhost:5432/todo_db
BETTER_AUTH_SECRET=your-secret-key-min-32-chars-long-change-in-productionfrontend/.env.local:
NEXT_PUBLIC_API_URL=http://localhost:8000
BETTER_AUTH_SECRET=your-secret-key-min-32-chars-long-change-in-production
DATABASE_URL=postgresql://postgres:password@localhost:5432/todo_dbBETTER_AUTH_SECRET must be identical in both files!
# Create PostgreSQL database
createdb todo_db
# Run migrations
cd backend
uv sync
uv run alembic upgrade head
cd ../frontend
npm install
npx better-auth migrate# Terminal 1 - Backend
cd backend
uv run uvicorn app.main:app --reload --port 8000
# Terminal 2 - Frontend
cd frontend
npm run dev- Frontend: http://localhost:3000
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
- QUICK-START.md - Get running in 5 minutes
- JWT-INTEGRATION-GUIDE.md - Complete JWT authentication guide
- IMPLEMENTATION-SUMMARY.md - Detailed change summary
- backend/README.md - Backend API documentation
- frontend/README.md - Frontend documentation
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend (Next.js) β
β ββββββββββββββββ ββββββββββββββββ β
β β Better Auth β βββΆ β JWT Token β β
β β Server β β (7 days) β β
β ββββββββββββββββ ββββββββββββββββ β
β β β
β βΌ β
β Authorization: Bearer <token> β
βββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββ
β
Shared Secret: BETTER_AUTH_SECRET
β
βββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββ
β βΌ β
β ββββββββββββββββ β
β β JWT Verify β β
β β Middleware β β
β ββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββ β
β β Task Routes β β
β β (Filtered) β β
β ββββββββββββββββ β
β Backend (FastAPI) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Flow:
- User logs in β Better Auth issues JWT token (stored in HTTP-only cookie)
- Frontend makes API call β Token attached to
Authorizationheader - Backend verifies token β Uses shared secret to validate signature
- Backend identifies user β Decodes user ID from token
- Backend filters data β Returns only user's own tasks
- Framework: FastAPI
- Database: PostgreSQL
- ORM: SQLAlchemy 2.0
- Migrations: Alembic
- Authentication: JWT verification with python-jose
- Password Hashing: Passlib + bcrypt
- Testing: Pytest (15/15 passing β )
- Framework: Next.js 14 (App Router)
- Language: TypeScript
- Authentication: Better Auth with JWT plugin
- Styling: Tailwind CSS
- HTTP Client: Axios (auto-attaches JWT tokens)
- Testing: Jest + React Testing Library
| Feature | Description |
|---|---|
| User Isolation | Each user only sees their own tasks |
| Stateless Auth | Backend doesn't need to call frontend to verify users |
| Token Expiry | JWT tokens expire after 7 days |
| Signature Verification | Tokens can't be forged without the secret key |
| Password Hashing | Better Auth handles secure bcrypt password storage |
| CORS Protection | Restricted to allowed origins |
cd backend
uv run pytest # Run all tests
uv run pytest --cov-report=html # With coverage reportResults: β 15/15 tests passing
- Authentication: 8/8 tests
- Task CRUD: 7/7 tests
- User isolation verified
- JWT verification working
cd frontend
npm test # Run all tests
npm run test:watch # Watch modeBackend (Alembic):
cd backend
uv run alembic revision --autogenerate -m "description"
uv run alembic upgrade head
uv run alembic downgrade -1Frontend (Better Auth):
cd frontend
npx better-auth migrateBackend:
cd backend
uv run black app tests # Format
uv run flake8 app tests # Lint
uv run mypy app # Type checkFrontend:
cd frontend
npm run lint # ESLint
npm run build # Production buildPOST /api/auth/register- Register new userPOST /api/auth/login- Login and get JWT tokenGET /api/auth/me- Get current user info
GET /api/{user_id}/tasks- List all tasksPOST /api/{user_id}/tasks- Create new taskGET /api/{user_id}/tasks/{task_id}- Get task by IDPUT /api/{user_id}/tasks/{task_id}- Update taskDELETE /api/{user_id}/tasks/{task_id}- Delete task
All task endpoints require Authorization: Bearer <token> header and enforce user ownership.
- β
Verify
BETTER_AUTH_SECRETmatches in both .env files - β Check token is being sent in request headers (DevTools β Network)
- β Try logout and login again to get fresh token
- β
Ensure PostgreSQL is running:
pg_isready - β Check DATABASE_URL in .env files
- β
Verify database exists:
psql -l | grep todo_db
- β Ensure DATABASE_URL is set in frontend/.env.local
- β Check PostgreSQL permissions
- β
Try:
cd frontend && npx better-auth migrate --force
- β Verify backend ALLOWED_ORIGINS includes frontend URL
- β Check both services are running on correct ports
- β Clear browser cache and cookies
.
βββ backend/ # FastAPI backend
β βββ app/
β β βββ models/ # SQLAlchemy models
β β βββ schemas/ # Pydantic schemas
β β βββ routers/ # API endpoints
β β βββ services/ # JWT verification & auth
β β βββ config.py # Settings with BETTER_AUTH_SECRET
β β βββ main.py # FastAPI app
β βββ tests/ # 15 tests (all passing)
β βββ alembic/ # Database migrations
β
βββ frontend/ # Next.js frontend
β βββ app/ # Pages (login, register, dashboard)
β βββ components/ # React components
β βββ lib/
β β βββ auth-server.ts # Better Auth config (JWT plugin)
β β βββ auth-client.ts # Client-side auth helpers
β β βββ api.ts # Axios client (auto-attaches JWT)
β βββ hooks/ # Custom React hooks
β
βββ JWT-INTEGRATION-GUIDE.md # Comprehensive JWT guide
βββ QUICK-START.md # 5-minute setup
βββ IMPLEMENTATION-SUMMARY.md # Detailed change summary
βββ src/ # Original CLI app (legacy)
- Generate secure
BETTER_AUTH_SECRET(32+ chars):openssl rand -base64 32 - Set environment variables in production
- Ensure secrets match in frontend and backend
- Set up HTTPS/SSL certificates
- Update
ALLOWED_ORIGINSwith production domain - Set
DEBUG=Falsein backend - Run database migrations
- Test authentication flow end-to-end
Backend:
DATABASE_URL=postgresql://user:pass@host:5432/todo_db
BETTER_AUTH_SECRET=<your-production-secret>
ALLOWED_ORIGINS=https://yourdomain.com
DEBUG=FalseFrontend:
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
BETTER_AUTH_SECRET=<same-secret-as-backend>
DATABASE_URL=postgresql://user:pass@host:5432/todo_dbThis project is built following the Spec-Kit Plus methodology.
- Follow coding standards (backend: PEP 8, frontend: ESLint)
- Write tests for new features
- Update documentation as needed
- Ensure all tests pass before committing