A modern, full-stack Todo application with user authentication, task management, and Progressive Web App (PWA) capabilities. Built as a monorepo with FastAPI backend, React frontend, SQLite database, and Docker deployment.
- User Authentication: Registration and login with JWT-based authentication
- Task Management: Full CRUD operations with GTD methodology (inbox, next actions, waiting, someday)
- Projects: Organize tasks into projects with automatic progress tracking
- Contexts: Assign contexts to tasks (Office, Home, Phone, etc.)
- Areas: Group tasks and projects by areas of responsibility
- Tags: Multi-select color-coded tags with M:N relationship to tasks
- Subtasks: Hierarchical task structure with progress indicators
- Search & Filters: Full-text search, combined filters, sorting, URL state sync
- Refresh Tokens: Secure token rotation with HttpOnly cookies
- Responsive UI: Clean, mobile-friendly interface with sidebar navigation
- PWA Support: Installable as a desktop/mobile app with offline capabilities
- Real-time Updates: Instant UI updates using React state management
- Type Safety: Full TypeScript support on frontend, Python type hints on backend
- Multi-language (i18n): Russian and English with easy language addition (react-i18next)
- Python 3.12 with async/await support
- FastAPI for high-performance API
- SQLAlchemy 2.0 with async support
- aiosqlite for SQLite async driver
- Alembic for database migrations
- Pydantic v2 for data validation
- python-jose for JWT handling
- passlib with bcrypt for password hashing
- React 18 with hooks
- TypeScript for type safety
- Vite for fast development and building
- React Router v7 for navigation
- Zustand for lightweight state management
- Tailwind CSS for utility-first styling
- react-hook-form for form handling
- zod for schema validation
- react-i18next + i18next for internationalization
- vite-plugin-pwa for PWA features
- SQLite with WAL mode for better concurrency
- Async database operations throughout
- Docker for containerization
- Docker Compose for multi-container orchestration
- Nginx as reverse proxy
- GitHub Actions for continuous integration
- Automated linting, type checking, and testing
- Docker (version 20.10 or later)
- Docker Compose (version 2.0 or later)
# Clone the repository
git clone <repository-url>
cd todowkaapp
# Start the application
docker-compose up --build
# Access the application
# Frontend: http://localhost
# API: http://localhost/api
# API Documentation: http://localhost/api/docsThe application will be available at http://localhost after the build completes.
# Stop all services
docker-compose down
# Stop and remove volumes (also deletes database)
docker-compose down -vCopy backend/.env.example to backend/.env and configure the following variables:
# Database Configuration
DATABASE_URL=sqlite+aiosqlite:///./data/todowka.db
# Security Configuration
SECRET_KEY=changeme-generate-random-string-64-characters
# JWT Token Expiration
ACCESS_TOKEN_EXPIRE_MINUTES=15
REFRESH_TOKEN_EXPIRE_DAYS=7
# User Registration
REGISTRATION_ENABLED=true
# INVITE_CODE=your-secret-invite-code
# CORS Configuration
ALLOWED_ORIGINS=http://localhost:5173,http://localhost:80,https://yourdomain.com
# Application Environment
APP_ENV=development
LOG_LEVEL=infoImportant: Generate a secure SECRET_KEY for production. You can use:
python -c "import secrets; print(secrets.token_urlsafe(64))"Copy frontend/.env.example to frontend/.env and configure:
# API base URL
VITE_API_BASE_URL=/api
# Application name
VITE_APP_NAME=TodowkaNote: Frontend environment variables must start with VITE_ to be accessible in the browser.
The run.sh script automates the entire backend setup and startup process:
cd backend
# Make the script executable (first time only)
chmod +x run.sh
# Run the script
./run.shWhat the script does:
- Creates a virtual environment if it doesn't exist
- Installs/updates all dependencies from
pyproject.toml - Creates the
data/directory for the database - Creates
.envfile from.env.exampleif it doesn't exist - Runs database migrations automatically
- Starts the FastAPI development server
The script will handle all setup steps and launch the server at http://localhost:8000.
# Navigate to backend directory
cd backend
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install -e .
# Run database migrations
alembic upgrade head
# Start development server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000The API will be available at http://localhost:8000 with interactive docs at http://localhost:8000/api/docs.
The run.sh script automates the entire frontend setup and startup process:
cd frontend
# Make the script executable (first time only)
chmod +x run.sh
# Run the script
./run.shWhat the script does:
- Installs/updates all npm dependencies
- Creates
.envfile from.env.exampleif it doesn't exist - Starts the Vite development server with hot reload
The script will handle all setup steps and launch the frontend at http://localhost:5173.
# Navigate to frontend directory
cd frontend
# Install dependencies
npm install
# Start development server
npm run devThe frontend will be available at http://localhost:5173.
cd backend
# Run all tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=app --cov-report=html
# Run specific test file
pytest tests/test_auth.py -vcd frontend
# Run tests in watch mode
npm test
# Run tests once
npm test -- --run
# Run tests with coverage
npm test -- --coveragecd backend
# Run linter
ruff check .
# Fix linting issues automatically
ruff check --fix .
# Run type checker
mypy . --ignore-missing-imports
# Check import sorting
ruff check --select I .cd frontend
# Run linter
npm run lint
# Fix linting issues automatically
npm run lint -- --fix
# Run type checker
npx tsc --noEmitProblem: Application fails to start or shows errors related to IndexedDB schema.
Symptoms:
- Console errors: "DOMException: The operation failed for reasons unrelated to the database itself"
- "VersionError: The requested version (1) is less than the existing version (2)"
- Application loads but cached data is not working
Solution: Clear IndexedDB cache
// In browser console:
indexedDB.deleteDatabase('todowka-query-cache')Manual Steps:
- Open Developer Tools (F12)
- Go to Application tab
- Expand IndexedDB
- Right-click on
todowka-query-cacheand select "Delete database" - Refresh the page
Automatic Reset: The application has automatic error handling for IndexedDB issues. If a migration fails, the database will be automatically reset and the application will continue without persistence.
Diagnosing:
- Check console for
[IndexedDB]or[QueryClient]log messages - Verify the current database version:
indexedDB.databases().then(dbs => dbs.find(db => db.name === 'todowka-query-cache')) - Look for migration logs in the console
Problem: API requests return 401 Unauthorized errors.
Symptoms:
- Repeated 401 errors in console
- User gets logged out unexpectedly
- SSE connection fails with 401 error
Diagnosing:
1. Check Token in Browser Console:
// Check access token
localStorage.getItem('accessToken')
// Check refresh token cookie
document.cookie.includes('refresh_token')
document.cookie.includes('access_token')2. Check Backend Logs:
# In backend terminal, look for auth logs:
# "Auth attempt via header" or "Auth attempt via cookie"
# "Token: xxxxxxxxxx...rest"
# "User authenticated: {user-id}"
# "Auth failed: {reason}"3. Check Frontend HTTP Logs:
// In browser console, filter for [HTTP] logs:
// [HTTP] Token: xxxxxxxxxx...rest
// [HTTP] Cookie has access_token: true/false
// [HTTP] Request: GET /api/tasks
// [HTTP] 401 Error: 401 /api/meCommon Issues:
Issue 1: Cookie Not Being Sent
- Cause: Cookie domain mismatch (localhost:8000 vs localhost:5173)
- Dev Mode: SSE uses direct connection to
http://localhost:8000(not through proxy) - Check:
APP_ENV=developmentin backend.env - Verify: Cookie
secureflag should beFalsein dev mode - Backend Config: Check
settings.cookie_secureinbackend/app/config.py
Issue 2: Token Expired
- Cause: Access token expired (15 min default)
- Solution: Automatic token refresh should handle this
- Check: Refresh token cookie should be present
- Backend: Check refresh token rotation is enabled
Issue 3: CORS Issues
- Symptoms: CORS error in browser console
- Check:
ALLOWED_ORIGINSin backend.env - Dev Mode: Should include
http://localhost:5173 - Production: Should include your actual domain
Problem: SSE (Server-Sent Events) not working or constantly reconnecting.
Symptoms:
- Real-time notifications not working
- Console shows repeated SSE connection errors
[SSE] [ERROR]messages in console
Diagnosing:
1. Check SSE Store:
// In browser console:
import { useSSEStore } from './stores/sseStore'
const sseStore = useSSEStore.getState()
console.log('SSE Status:', sseStore.connectionStatus)
console.log('Reconnect Attempts:', sseStore.reconnectAttempts)
console.log('Last Error:', sseStore.lastError)
console.log('Total Connected Time:', sseStore.totalConnectedTime)2. Check SSE Logs:
// Filter console for [SSE] logs:
// [SSE] [INFO] Connecting to SSE
// [SSE] [INFO] SSE connection established
// [SSE] [ERROR] SSE connection error
// [SSE] [INFO] Scheduling reconnect3. Check Connection URL:
- Dev Mode: Should connect to
http://localhost:8000/api/sse/notifications - Production: Should connect to
/api/sse/notifications(through Vite proxy)
Common Issues:
Issue 1: 401 Error on SSE
- Cause: Cookie not being sent to SSE endpoint
- Dev Mode: SSE uses direct connection (bypasses proxy)
- Solution: Ensure
APP_ENV=developmentandCOOKIE_SECURE=falsein backend - Check: Cookie should be set with
path=/api/sseanddomain=localhostin dev
Issue 2: Reconnect Limit Reached
- Symptoms:
[SSE] [ERROR] SSE reconnect limit reached - Cause: 5 failed connection attempts
- Solution: Manually reset or fix the underlying issue
- Reset:
sseManager.resetReconnectAttempts()
Issue 3: Connection Drops Frequently
- Check: Backend logs for SSE endpoint errors
- Network: Verify network stability
- Backend: Check if SSE endpoint is running:
curl http://localhost:8000/api/sse/notifications
Enable Extended Logging:
Frontend:
// All logs are enabled by default in development
// Filter console by prefix:
// [HTTP] - HTTP requests and responses
// [SSE] - SSE connection events
// [IndexedDB] - IndexedDB operations
// [QueryClient] - Query cache operationsBackend:
# In backend/.env:
LOG_LEVEL=debug # Set to debug for detailed logs
# Check logs for:
# "Auth attempt via header" or "Auth attempt via cookie"
# "Token: xxxxxxxxxx...rest"
# "User authenticated: {user-id}"
# "Auth failed: {reason}"Check Network Requests:
- Open Developer Tools (F12)
- Go to Network tab
- Filter by "api" to see API requests
- Check Request Headers for Authorization or Cookie
- Check Response status and body
Check Application State:
// In browser console:
import { useAuthStore } from './stores/authStore'
const authStore = useAuthStore.getState()
console.log('Is Authenticated:', authStore.isAuthenticated)
console.log('User:', authStore.user)
console.log('Access Token:', authStore.accessToken?.substring(0, 10) + '...')The project uses GitHub Actions for continuous integration. The CI pipeline runs on:
- Push to main branch
- Pull requests to main branch
Backend Job:
- Checkout code
- Set up Python 3.12
- Install dependencies (with pip caching)
- Run linter (ruff check)
- Run type checker (mypy)
- Run tests (pytest)
Frontend Job:
- Checkout code
- Set up Node.js 20
- Install dependencies (with npm caching)
- Run linter (eslint)
- Run type checker (tsc --noEmit)
- Run tests (vitest)
- Run build (npm run build)
Both jobs run in parallel for faster feedback.
Once the backend is running, visit:
- Swagger UI:
http://localhost:8000/api/docs - ReDoc:
http://localhost:8000/api/redoc
Authentication:
POST /api/auth/register- Register new userPOST /api/auth/login- Login and get tokensPOST /api/auth/refresh- Refresh access tokenPOST /api/auth/logout- Logout (clears refresh cookie)GET /api/auth/me- Get current user info
Tasks:
GET /api/tasks- List tasks (with pagination, filters, search, sorting)POST /api/tasks- Create new taskGET /api/tasks/{id}- Get single taskPUT /api/tasks/{id}- Update taskPATCH /api/tasks/{id}/toggle- Toggle task completionPATCH /api/tasks/{id}/move- Move task to another GTD statusPATCH /api/tasks/{id}/reorder- Change task positionDELETE /api/tasks/{id}- Delete taskGET /api/tasks/counts- Get GTD status countsGET /api/tasks/{id}/subtasks- List subtasksPOST /api/tasks/{id}/subtasks- Create subtask
Contexts:
GET /api/contexts- List contextsPOST /api/contexts- Create contextGET /api/contexts/{id}- Get contextPUT /api/contexts/{id}- Update contextDELETE /api/contexts/{id}- Delete context
Areas:
GET /api/areas- List areasPOST /api/areas- Create areaGET /api/areas/{id}- Get areaPUT /api/areas/{id}- Update areaDELETE /api/areas/{id}- Delete area
Tags:
GET /api/tags- List tagsPOST /api/tags- Create tagGET /api/tags/{id}- Get tagPUT /api/tags/{id}- Update tagDELETE /api/tags/{id}- Delete tagPOST /api/tags/tasks/{task_id}/tags/{tag_id}- Add tag to taskDELETE /api/tags/tasks/{task_id}/tags/{tag_id}- Remove tag from task
Projects:
GET /api/projects- List projectsPOST /api/projects- Create projectGET /api/projects/{id}- Get project (with progress)PUT /api/projects/{id}- Update projectDELETE /api/projects/{id}- Delete projectGET /api/projects/{id}/tasks- List project tasks
Stats & Config:
GET /api/stats- Task statisticsGET /api/config- App configuration
todowkaapp/
βββ backend/ # FastAPI backend application
β βββ app/
β β βββ api/ # API routers
β β β βββ auth.py # Authentication endpoints
β β β βββ router.py # Main API router
β β β βββ tasks.py # Task endpoints (CRUD, move, toggle, subtasks)
β β β βββ contexts.py # Context endpoints
β β β βββ areas.py # Area endpoints
β β β βββ tags.py # Tag endpoints
β β β βββ projects.py # Project endpoints
β β β βββ users.py # User management (admin)
β β β βββ stats.py # Statistics endpoint
β β βββ models/ # SQLAlchemy ORM models
β β β βββ task.py # Task model (GTD fields, subtasks)
β β β βββ user.py # User model
β β β βββ context.py # Context model
β β β βββ area.py # Area model
β β β βββ tag.py # Tag model + task_tags
β β β βββ project.py # Project model
β β βββ schemas/ # Pydantic schemas
β β β βββ auth.py # Auth schemas
β β β βββ task.py # Task schemas (Create/Update/Response)
β β β βββ context.py # Context schemas
β β β βββ area.py # Area schemas
β β β βββ tag.py # Tag schemas
β β β βββ project.py # Project schemas (with progress)
β β βββ services/ # Business logic
β β β βββ task_service.py
β β β βββ context_service.py
β β β βββ area_service.py
β β β βββ tag_service.py
β β β βββ project_service.py
β β βββ config.py # Application configuration
β β βββ database.py # Database setup
β β βββ dependencies.py # FastAPI dependencies
β β βββ main.py # Application entry point
β β βββ security.py # Security utilities
β βββ alembic/ # Database migrations
β βββ tests/ # Backend tests (174 tests)
β βββ data/ # SQLite database (created at runtime)
β βββ pyproject.toml # Python dependencies
β βββ .env.example # Environment variables template
β
βββ frontend/ # React frontend application
β βββ src/
β β βββ api/ # API client
β β β βββ httpClient.ts
β β β βββ users.ts
β β βββ components/ # React components
β β β βββ AppLayout.tsx # Layout + Sidebar
β β β βββ InstallPrompt.tsx # PWA install prompt
β β β βββ ProtectedRoute.tsx
β β β βββ TaskEditModal.tsx # Extended task form
β β β βββ TaskFilterPanel.tsx # Search + filters + sort
β β βββ hooks/ # Custom React hooks
β β β βββ useTasks.ts # Task CRUD + filters
β β β βββ useContexts.ts # Context CRUD
β β β βββ useAreas.ts # Area CRUD
β β β βββ useTags.ts # Tag CRUD
β β β βββ useProjects.ts # Project CRUD
β β β βββ useSubtasks.ts # Subtask CRUD
β β β βββ useGtdCounts.ts # GTD status counts
β β β βββ useTaskFilter.ts # Filter state + URL sync
β β β βββ useDebounce.ts
β β βββ routes/ # Page components
β β β βββ Login.tsx
β β β βββ Register.tsx
β β β βββ Tasks.tsx
β β β βββ GtdTaskList.tsx # Universal GTD list
β β β βββ Projects.tsx # Project cards + progress
β β β βββ ProjectDetail.tsx
β β β βββ Contexts.tsx
β β β βββ Areas.tsx
β β β βββ Tags.tsx
β β β βββ Profile.tsx
β β β βββ Settings.tsx
β β βββ stores/ # Zustand stores
β β β βββ authStore.ts
β β β βββ sseStore.ts
β β βββ services/ # External services
β β β βββ sseManager.ts
β β βββ router.tsx # React Router config
β β βββ main.tsx # Entry point
β βββ public/ # Static assets
β β βββ manifest.json # PWA manifest
β βββ package.json # Node dependencies
β βββ vite.config.ts # Vite configuration
β βββ vitest.config.ts # Test configuration
β βββ .env.example # Environment variables template
β
βββ docs/ # Documentation
β βββ features.md # Feature tracking
β βββ SSE_VITE_PROXY_RESEARCH.md # SSE research findings
β
βββ docker/ # Docker configuration
β βββ docker-compose.yml # Multi-container setup
β βββ nginx.conf # Nginx reverse proxy
β βββ .env.example # Docker environment variables
β
βββ .github/
β βββ workflows/
β βββ ci.yml # CI: lint + typecheck + test
β
βββ .gitignore # Git ignore rules
βββ README.md # This file
βββ LICENSE # License file
- Layered Architecture: Separation of concerns with API, Services, Models, and Schemas layers
- Dependency Injection: FastAPI's dependency system for database sessions and authentication
- Async/Await: All I/O operations use async patterns for better performance
- JWT Authentication: Access tokens in memory, refresh tokens in HttpOnly cookies
- ORM: SQLAlchemy 2.0 with async support for database operations
- Component-Based: Reusable React components with TypeScript
- State Management: Zustand for global state (auth, SSE), React hooks for local state
- Routing: React Router v7 for navigation
- API Client: Custom fetch wrapper with automatic token refresh
- Form Handling: react-hook-form with zod validation
- Styling: Tailwind CSS for utility-first styling
- Password Hashing: bcrypt with salt for secure password storage
- JWT Tokens: Signed with HS256 algorithm, configurable expiration
- Refresh Tokens: Stored in HttpOnly, Secure, SameSite=Strict cookies (configurable in dev)
- CORS: Configured to allow only specified origins
- SQL Injection Prevention: SQLAlchemy ORM with parameterized queries
- XSS Protection: React's built-in escaping and Content Security Policy
- Installable: Can be installed on desktop and mobile devices
- Offline Support: Service worker caches static assets and API responses
- Push Notifications: Ready for push notification integration
- App Manifest: Configured with app icons, colors, and display mode
- Responsive Design: Optimized for all screen sizes
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests and linting
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
The app supports Russian (RU) and English (EN) out of the box. To add a new language:
Copy the English locale folder and translate all JSON files:
cd frontend/src/i18n/locales
# Copy English translations as a starting point
cp -r en <lang-code> # e.g.: de, fr, es, ja
# Translate each file in the new folder:
# <lang-code>/common.json
# <lang-code>/nav.json
# <lang-code>/auth.json
# <lang-code>/tasks.json
# <lang-code>/settings.json
# <lang-code>/projects.json
# <lang-code>/notifications.json
# <lang-code>/sync.json
# <lang-code>/verbs.jsonEdit frontend/src/i18n/index.ts:
// 1. Import the new locale files
import commonDe from './locales/de/common.json'
import navDe from './locales/de/nav.json'
// ... import all 9 namespaces
// 2. Add to resources object
const resources = {
ru: { ... },
en: { ... },
de: { // <-- add new language
common: commonDe,
nav: navDe,
// ... all namespaces
},
}
// 3. Add to supported languages array
const SUPPORTED_LANGS = ['ru', 'en', 'de'] // <-- add codeEdit frontend/src/routes/Settings.tsx β find the language dropdown and add an <option>:
<select ...>
<option value="ru">Π ΡΡΡΠΊΠΈΠΉ</option>
<option value="en">English</option>
<option value="de">Deutsch</option> {/* <-- add this */}
</select>Each namespace covers a specific area of the app:
| Namespace | Contents | ~Keys |
|---|---|---|
common |
Buttons, statuses, generic labels | ~30 |
nav |
Sidebar, header, navigation labels | ~20 |
auth |
Login, registration, timezone setup | ~25 |
tasks |
Task CRUD, filters, dates, GTD, recurrence, reminders | ~80 |
settings |
All settings tabs, profile, password, users, language | ~100 |
projects |
Projects, areas, contexts, tags CRUD | ~25 |
notifications |
Notifications, bell, updates | ~15 |
sync |
Offline, sync status, errors | ~25 |
verbs |
Quick verbs for task creation | ~15 |
Keys use interpolation for dynamic values: "overdueDays": "Overdue by {{count}} d. ({{date}})".
This project is licensed under the MIT License - see the LICENSE file for details.
For issues, questions, or contributions, please open an issue on GitHub.
- Built with modern web technologies
- Inspired by the need for a simple, yet powerful todo application
- Thanks to the open-source community for the amazing tools and libraries