A full-stack task and todo management application with a React (Vite) frontend and a Django REST backend. Users can register, log in, create tasks with priorities and categories, track progress, and manage their profile—all with JWT authentication and a modern, responsive UI.
- Overview
- Architecture
- Tech Stack
- Project Structure
- Features
- Getting Started
- Configuration
- API Overview
- Testing
- Security & Best Practices
This project is a monorepo containing:
| Part | Description |
|---|---|
| Frontend | Single-page application (SPA) built with React 19, Vite 7, and Tailwind CSS 4. Handles auth, task CRUD, categories, dashboard, calendar, and settings. |
| Backend | Django 6 REST API with Django REST Framework (v1) and optional Django Bolt (v2) async endpoints. Uses MySQL, JWT (Simple JWT), and django-cors-headers for cross-origin requests from the frontend. |
The frontend talks to the backend over HTTP; all protected endpoints require a Bearer JWT token. User data, tasks, and categories are scoped per user (multi-tenant at the user level).
┌─────────────────────────────────────────────────────────────────┐
│ Frontend (React + Vite) │
│ • AuthContext (login, register, token storage) │
│ • Axios instance (baseURL, Bearer token, refresh on 401) │
│ • Pages: Login, Register, Dashboard, My Task, Categories, etc. │
└───────────────────────────────┬─────────────────────────────────┘
│ HTTP/REST (CORS enabled)
▼
┌─────────────────────────────────────────────────────────────────┐
│ Backend (Django 6) │
│ • API v1: DRF views + serializers (sync) │
│ • API v2: Django Bolt async endpoints (when mounted) │
│ • JWT auth (Simple JWT), MySQL, WhiteNoise static, media files │
└───────────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ MySQL Database │
│ • accounts_user (custom user: name, email, profile_picture) │
│ • todos_task (title, description, priority, status, category) │
│ • todos_category (name, color, per user) │
└─────────────────────────────────────────────────────────────────┘
- User registers or logs in via
/api/v1/auth/register/or/api/v1/auth/login/. - Backend returns
accessandrefreshJWT tokens plus user payload. - Frontend stores tokens in
localStorageand setsAuthorization: Bearer <access>on all API requests. - When the API returns 401, the frontend attempts token refresh using the refresh token; on failure, the user is redirected to login.
- Protected routes (Dashboard, My Task, etc.) are wrapped in a
ProtectedRoutecomponent that redirects to/loginif not authenticated.
- User (custom model in
accounts):username,email,name,first_name,last_name,profile_picture, soft delete support. - Category (
todos):name,color(hex),user(FK). Unique per user. - Task (
todos):title,description,priority(Extreme / Moderate / Low),status(Not Started / In Progress / Completed),due_date,image,user(FK),category(FK, optional). Ordered by-created_at.
All list APIs support filtering, search, and ordering where applicable; tasks support pagination (page size 20).
| Technology | Purpose |
|---|---|
| React 19 | UI components and state |
| Vite 7 | Build tool, dev server, HMR |
| React Router 7 | Client-side routing |
| Tailwind CSS 4 | Utility-first styling |
| Axios | HTTP client with interceptors |
| react-hot-toast | Toast notifications |
| react-icons | Icons |
| Technology | Purpose |
|---|---|
| Django 6 | Web framework, ORM, admin |
| Django REST Framework | REST API (v1), pagination, filters |
| djangorestframework-simplejwt | JWT access/refresh tokens |
| django-cors-headers | CORS for frontend origin |
| MySQL | Database (via mysqlclient) |
| Pillow | Image handling (task/user images) |
| WhiteNoise | Static file serving |
| django-unfold | Modern admin UI |
| Django Control Room + panels | Centralized admin/control plane with Redis, Cache, URLs, Celery panels for operations and debugging |
| Django Bolt | Optional async API (v2) with OpenAPI |
- pytest, pytest-django, pytest-cov for backend tests.
- ESLint, Prettier for frontend linting and formatting.
- Configuration via JSON (
dev.json/prod.json) for Django (no env vars required for DB/secret in this setup).
File Tree: ToDo
────────────────────────────────────────────────────────────────────────────────
├── 📁 backend/
│ ├── 📁 accounts/
│ │ ├── 📁 api/
│ │ │ ├── 📁 v1/
│ │ │ │ ├── 🐍 __init__.py
│ │ │ │ ├── 🐍 serializers.py
│ │ │ │ ├── 🐍 urls.py
│ │ │ │ └── 🐍 view.py
│ │ │ └── 📁 v2/
│ │ │ ├── 🐍 __init__.py
│ │ │ ├── 🐍 schemas.py
│ │ │ └── 🐍 views.py
│ │ ├── 📁 migrations/
│ │ │ ├── 🐍 0001_initial.py
│ │ │ ├── 🐍 0002_user_profile_picture.py
│ │ │ └── 🐍 __init__.py
│ │ ├── 📁 test/
│ │ │ ├── 🐍 test_api_auth.py
│ │ │ └── 🐍 test_models.py
│ │ ├── 🐍 __init__.py
│ │ ├── 🐍 admin.py
│ │ ├── 🐍 apps.py
│ │ └── 🐍 models.py
│ ├── 📁 common/
│ │ ├── 🐍 __init__.py
│ │ ├── 🐍 deps.py
│ │ └── 🐍 utils.py
│ ├── 📁 core/
│ │ ├── 🐍 __init__.py
│ │ ├── 🐍 api.py
│ │ ├── 🐍 asgi.py
│ │ ├── 🐍 settings.py
│ │ ├── 🐍 urls.py
│ │ └── 🐍 wsgi.py
│ ├── 📁 media/
│ │ ├── 📁 profile_pictures/
│ │ │ └── 🖼️ 1769006736244.jpg
│ │ └── 📁 tasks/
│ │ └── 🖼️ a.jpg
│ ├── 📁 static/
│ ├── 📁 staticfiles/
│ │ ├── 📁 admin/
│ │ │ ├── 📁 css/
│ │ │ │ ├── 📁 vendor/
│ │ │ │ │ └── 📁 select2/
│ │ │ │ │ ├── 📝 LICENSE-SELECT2.md
│ │ │ │ │ └── 🎨 select2.css
│ │ │ │ ├── 🎨 autocomplete.css
│ │ │ │ ├── 🎨 base.css
│ │ │ │ ├── 🎨 changelists.css
│ │ │ │ ├── 🎨 dark_mode.css
│ │ │ │ ├── 🎨 dashboard.css
│ │ │ │ ├── 🎨 forms.css
│ │ │ │ ├── 🎨 login.css
│ │ │ │ ├── 🎨 nav_sidebar.css
│ │ │ │ ├── 🎨 responsive.css
│ │ │ │ ├── 🎨 responsive_rtl.css
│ │ │ │ ├── 🎨 rtl.css
│ │ │ │ ├── 🎨 unusable_password_field.css
│ │ │ │ └── 🎨 widgets.css
│ │ │ ├── 📁 img/
│ │ │ │ ├── 📝 README.md
│ │ │ │ ├── 🖼️ calendar-icons.svg
│ │ │ │ ├── 🖼️ icon-addlink.svg
│ │ │ │ ├── 🖼️ icon-alert-dark.svg
│ │ │ │ ├── 🖼️ icon-alert.svg
│ │ │ │ ├── 🖼️ icon-calendar.svg
│ │ │ │ ├── 🖼️ icon-changelink.svg
│ │ │ │ ├── 🖼️ icon-clock.svg
│ │ │ │ ├── 🖼️ icon-debug-dark.svg
│ │ │ │ ├── 🖼️ icon-debug.svg
│ │ │ │ ├── 🖼️ icon-deletelink.svg
│ │ │ │ ├── 🖼️ icon-hidelink.svg
│ │ │ │ ├── 🖼️ icon-info-dark.svg
│ │ │ │ ├── 🖼️ icon-info.svg
│ │ │ │ ├── 🖼️ icon-no-dark.svg
│ │ │ │ ├── 🖼️ icon-no.svg
│ │ │ │ ├── 🖼️ icon-unknown-alt.svg
│ │ │ │ ├── 🖼️ icon-unknown.svg
│ │ │ │ ├── 🖼️ icon-viewlink.svg
│ │ │ │ ├── 🖼️ icon-yes-dark.svg
│ │ │ │ ├── 🖼️ icon-yes.svg
│ │ │ │ ├── 🖼️ inline-delete.svg
│ │ │ │ ├── 🖼️ search.svg
│ │ │ │ ├── 🖼️ selector-icons.svg
│ │ │ │ ├── 🖼️ sorting-icons.svg
│ │ │ │ ├── 🖼️ tooltag-add.svg
│ │ │ │ └── 🖼️ tooltag-arrowright.svg
│ │ │ └── 📁 js/
│ │ │ ├── 📁 admin/
│ │ │ │ ├── 📄 DateTimeShortcuts.js
│ │ │ │ └── 📄 RelatedObjectLookups.js
│ │ │ ├── 📁 vendor/
│ │ │ │ ├── 📁 jquery/
│ │ │ │ │ ├── 📄 LICENSE.txt
│ │ │ │ │ └── 📄 jquery.js
│ │ │ │ ├── 📁 select2/
│ │ │ │ │ ├── 📁 i18n/
│ │ │ │ │ │ ├── 📄 af.js
│ │ │ │ │ │ ├── 📄 ar.js
│ │ │ │ │ │ ├── 📄 az.js
│ │ │ │ │ │ ├── 📄 bg.js
│ │ │ │ │ │ ├── 📄 bn.js
│ │ │ │ │ │ ├── 📄 bs.js
│ │ │ │ │ │ ├── 📄 ca.js
│ │ │ │ │ │ ├── 📄 cs.js
│ │ │ │ │ │ ├── 📄 da.js
│ │ │ │ │ │ ├── 📄 de.js
│ │ │ │ │ │ ├── 📄 dsb.js
│ │ │ │ │ │ ├── 📄 el.js
│ │ │ │ │ │ ├── 📄 en.js
│ │ │ │ │ │ ├── 📄 es.js
│ │ │ │ │ │ ├── 📄 et.js
│ │ │ │ │ │ ├── 📄 eu.js
│ │ │ │ │ │ ├── 📄 fa.js
│ │ │ │ │ │ ├── 📄 fi.js
│ │ │ │ │ │ ├── 📄 fr.js
│ │ │ │ │ │ ├── 📄 gl.js
│ │ │ │ │ │ ├── 📄 he.js
│ │ │ │ │ │ ├── 📄 hi.js
│ │ │ │ │ │ ├── 📄 hr.js
│ │ │ │ │ │ ├── 📄 hsb.js
│ │ │ │ │ │ ├── 📄 hu.js
│ │ │ │ │ │ ├── 📄 hy.js
│ │ │ │ │ │ ├── 📄 id.js
│ │ │ │ │ │ ├── 📄 is.js
│ │ │ │ │ │ ├── 📄 it.js
│ │ │ │ │ │ ├── 📄 ja.js
│ │ │ │ │ │ ├── 📄 ka.js
│ │ │ │ │ │ ├── 📄 km.js
│ │ │ │ │ │ ├── 📄 ko.js
│ │ │ │ │ │ ├── 📄 lt.js
│ │ │ │ │ │ ├── 📄 lv.js
│ │ │ │ │ │ ├── 📄 mk.js
│ │ │ │ │ │ ├── 📄 ms.js
│ │ │ │ │ │ ├── 📄 nb.js
│ │ │ │ │ │ ├── 📄 ne.js
│ │ │ │ │ │ ├── 📄 nl.js
│ │ │ │ │ │ ├── 📄 pl.js
│ │ │ │ │ │ ├── 📄 ps.js
│ │ │ │ │ │ ├── 📄 pt-BR.js
│ │ │ │ │ │ ├── 📄 pt.js
│ │ │ │ │ │ ├── 📄 ro.js
│ │ │ │ │ │ ├── 📄 ru.js
│ │ │ │ │ │ ├── 📄 sk.js
│ │ │ │ │ │ ├── 📄 sl.js
│ │ │ │ │ │ ├── 📄 sq.js
│ │ │ │ │ │ ├── 📄 sr-Cyrl.js
│ │ │ │ │ │ ├── 📄 sr.js
│ │ │ │ │ │ ├── 📄 sv.js
│ │ │ │ │ │ ├── 📄 th.js
│ │ │ │ │ │ ├── 📄 tk.js
│ │ │ │ │ │ ├── 📄 tr.js
│ │ │ │ │ │ ├── 📄 uk.js
│ │ │ │ │ │ ├── 📄 vi.js
│ │ │ │ │ │ ├── 📄 zh-CN.js
│ │ │ │ │ │ └── 📄 zh-TW.js
│ │ │ │ │ ├── 📝 LICENSE.md
│ │ │ │ │ └── 📄 select2.full.js
│ │ │ │ └── 📁 xregexp/
│ │ │ │ ├── 📄 LICENSE.txt
│ │ │ │ └── 📄 xregexp.js
│ │ │ ├── 📄 SelectBox.js
│ │ │ ├── 📄 SelectFilter2.js
│ │ │ ├── 📄 actions.js
│ │ │ ├── 📄 autocomplete.js
│ │ │ ├── 📄 calendar.js
│ │ │ ├── 📄 cancel.js
│ │ │ ├── 📄 change_form.js
│ │ │ ├── 📄 core.js
│ │ │ ├── 📄 filters.js
│ │ │ ├── 📄 inlines.js
│ │ │ ├── 📄 jquery.init.js
│ │ │ ├── 📄 nav_sidebar.js
│ │ │ ├── 📄 popup_response.js
│ │ │ ├── 📄 prepopulate.js
│ │ │ ├── 📄 prepopulate_init.js
│ │ │ ├── 📄 theme.js
│ │ │ └── 📄 urlify.js
│ │ ├── 📁 rest_framework/
│ │ │ ├── 📁 css/
│ │ │ │ ├── 🎨 bootstrap-tweaks.css
│ │ │ │ ├── 🎨 default.css
│ │ │ │ ├── 🎨 font-awesome-4.0.3.css
│ │ │ │ └── 🎨 prettify.css
│ │ │ ├── 📁 docs/
│ │ │ │ ├── 📁 css/
│ │ │ │ │ ├── 🎨 base.css
│ │ │ │ │ └── 🎨 highlight.css
│ │ │ │ ├── 📁 img/
│ │ │ │ │ ├── 📄 favicon.ico
│ │ │ │ │ └── 🖼️ grid.png
│ │ │ │ └── 📁 js/
│ │ │ │ ├── 📄 api.js
│ │ │ │ └── 📄 highlight.pack.js
│ │ │ ├── 📁 fonts/
│ │ │ │ ├── 📄 fontawesome-webfont.eot
│ │ │ │ ├── 🖼️ fontawesome-webfont.svg
│ │ │ │ ├── 📄 fontawesome-webfont.ttf
│ │ │ │ ├── 📄 fontawesome-webfont.woff
│ │ │ │ ├── 📄 glyphicons-halflings-regular.eot
│ │ │ │ ├── 🖼️ glyphicons-halflings-regular.svg
│ │ │ │ ├── 📄 glyphicons-halflings-regular.ttf
│ │ │ │ ├── 📄 glyphicons-halflings-regular.woff
│ │ │ │ └── 📄 glyphicons-halflings-regular.woff2
│ │ │ ├── 📁 img/
│ │ │ │ ├── 🖼️ glyphicons-halflings-white.png
│ │ │ │ ├── 🖼️ glyphicons-halflings.png
│ │ │ │ └── 🖼️ grid.png
│ │ │ └── 📁 js/
│ │ │ ├── 📄 ajax-form.js
│ │ │ ├── 📄 coreapi-0.1.1.js
│ │ │ ├── 📄 csrf.js
│ │ │ ├── 📄 default.js
│ │ │ ├── 📄 load-ajax-form.js
│ │ │ └── 📄 prettify-min.js
│ │ └── 📁 unfold/
│ │ ├── 📁 css/
│ │ │ ├── 📁 simplebar/
│ │ │ │ ├── 📄 LICENSE
│ │ │ │ └── 🎨 simplebar.css
│ │ │ └── 🎨 styles.css
│ │ ├── 📁 fonts/
│ │ │ ├── 📁 inter/
│ │ │ │ ├── 📄 Inter-Bold.woff2
│ │ │ │ ├── 📄 Inter-Medium.woff2
│ │ │ │ ├── 📄 Inter-Regular.woff2
│ │ │ │ ├── 📄 Inter-SemiBold.woff2
│ │ │ │ ├── 📄 LICENSE
│ │ │ │ └── 🎨 styles.css
│ │ │ └── 📁 material-symbols/
│ │ │ ├── 📄 LICENSE
│ │ │ ├── 📄 Material-Symbols-Outlined.woff2
│ │ │ └── 🎨 styles.css
│ │ └── 📁 js/
│ │ ├── 📁 alpine/
│ │ │ ├── 📄 LICENSE
│ │ │ ├── 📄 alpine.anchor.js
│ │ │ ├── 📄 alpine.js
│ │ │ ├── 📄 alpine.persist.js
│ │ │ ├── 📄 alpine.resize.js
│ │ │ └── 📄 alpine.sort.js
│ │ ├── 📁 chart/
│ │ │ ├── 📄 LICENSE
│ │ │ └── 📄 chart.js
│ │ ├── 📁 htmx/
│ │ │ ├── 📄 LICENSE
│ │ │ └── 📄 htmx.js
│ │ ├── 📁 simplebar/
│ │ │ ├── 📄 LICENSE
│ │ │ └── 📄 simplebar.js
│ │ ├── 📄 app.js
│ │ └── 📄 select2.init.js
│ ├── 📁 todos/
│ │ ├── 📁 api/
│ │ │ ├── 📁 v1/
│ │ │ │ ├── 🐍 __init__.py
│ │ │ │ ├── 🐍 serializers.py
│ │ │ │ ├── 🐍 urls.py
│ │ │ │ └── 🐍 views.py
│ │ │ ├── 📁 v2/
│ │ │ │ ├── 🐍 __init__.py
│ │ │ │ ├── 🐍 schemas.py
│ │ │ │ └── 🐍 views.py
│ │ │ └── 🐍 __init__.py
│ │ ├── 📁 migrations/
│ │ │ ├── 🐍 0001_initial.py
│ │ │ └── 🐍 __init__.py
│ │ ├── 📁 test/
│ │ │ ├── 🐍 __init__.py
│ │ │ ├── 🐍 test_api.py
│ │ │ └── 🐍 test_models.py
│ │ ├── 🐍 __init__.py
│ │ ├── 🐍 admin.py
│ │ ├── 🐍 apps.py
│ │ └── 🐍 models.py
│ ├── ⚙️ .gitignore
│ ├── 📝 README.md
│ ├── ⚙️ dev.json
│ ├── 🐍 manage.py
│ ├── ⚙️ pytest.ini
│ └── 📄 requirements.txt
├── 📁 frontend/
│ ├── 📁 public/
│ │ ├── 📁 site_svgs/
│ │ │ ├── 📁 dashboard-page/
│ │ │ │ ├── 🖼️ Book.svg
│ │ │ │ ├── 🖼️ Pending.svg
│ │ │ │ └── 🖼️ Task Complete.svg
│ │ │ ├── 📁 login-page/
│ │ │ │ ├── 🖼️ facebook.svg
│ │ │ │ ├── 🖼️ google.svg
│ │ │ │ ├── 🖼️ login.svg
│ │ │ │ └── 🖼️ twitter.svg
│ │ │ ├── 📁 nav-bar/
│ │ │ │ ├── 🖼️ Cal.svg
│ │ │ │ └── 🖼️ Notifications.svg
│ │ │ └── 📁 register-page/
│ │ │ ├── 🖼️ confirm-password.svg
│ │ │ ├── 🖼️ email.svg
│ │ │ ├── 🖼️ first-name.svg
│ │ │ ├── 🖼️ last-name.svg
│ │ │ ├── 🖼️ password.svg
│ │ │ ├── 🖼️ register.svg
│ │ │ ├── 🖼️ register_backgroung.svg
│ │ │ └── 🖼️ uesername.svg
│ │ └── 🖼️ vite.svg
│ ├── 📁 src/
│ │ ├── 📁 assets/
│ │ │ └── 🖼️ react.svg
│ │ ├── 📁 components/
│ │ │ ├── 📁 common/
│ │ │ │ ├── 📄 Button.jsx
│ │ │ │ └── 📄 Card.jsx
│ │ │ ├── 📁 features/
│ │ │ │ ├── 📄 TaskCard.jsx
│ │ │ │ └── 📄 TaskStatusChart.jsx
│ │ │ └── 📁 layout/
│ │ │ ├── 📄 Header.jsx
│ │ │ ├── 📄 Layout.jsx
│ │ │ └── 📄 Sidebar.jsx
│ │ ├── 📁 constants/
│ │ │ └── 📄 index.js
│ │ ├── 📁 contexts/
│ │ │ └── 📄 AuthContext.jsx
│ │ ├── 📁 hooks/
│ │ │ ├── 📄 useAPI.js
│ │ │ └── 📄 useLocalStorage.js
│ │ ├── 📁 pages/
│ │ │ ├── 📄 AccountInfo.jsx
│ │ │ ├── 📄 AddTask.jsx
│ │ │ ├── 📄 Calendar.jsx
│ │ │ ├── 📄 ChangePassword.jsx
│ │ │ ├── 📄 CreateCategories.jsx
│ │ │ ├── 📄 Dashboard.jsx
│ │ │ ├── 📄 EditTask.jsx
│ │ │ ├── 📄 Help.jsx
│ │ │ ├── 📄 Home.jsx
│ │ │ ├── 📄 Login.jsx
│ │ │ ├── 📄 MyTask.jsx
│ │ │ ├── 📄 Notifications.jsx
│ │ │ ├── 📄 Register.jsx
│ │ │ ├── 📄 Settings.jsx
│ │ │ ├── 📄 TaskCategories.jsx
│ │ │ ├── 📄 ViewTask.jsx
│ │ │ └── 📄 Vitals.jsx
│ │ ├── 📁 services/
│ │ │ ├── 📄 api.js
│ │ │ └── 📄 axios.js
│ │ ├── 📁 utils/
│ │ │ ├── 📄 cn.js
│ │ │ └── 📄 toast.js
│ │ ├── 📄 App.jsx
│ │ ├── 🎨 index.css
│ │ └── 📄 main.jsx
│ ├── ⚙️ .editorconfig
│ ├── ⚙️ .env.example
│ ├── ⚙️ .gitignore
│ ├── ⚙️ .prettierignore
│ ├── ⚙️ .prettierrc
│ ├── 📝 README.md
│ ├── 📄 eslint.config.js
│ ├── 🌐 index.html
│ ├── ⚙️ package-lock.json
│ ├── ⚙️ package.json
│ ├── 📄 postcss.config.js
│ ├── 📄 tailwind.config.js
│ └── 📄 vite.config.js
├── ⚙️ .gitignore
└── 📝 Readme.md
────────────────────────────────────────────────────────────────────────────────
- Register: username, email, name (first/last), password, confirm password.
- Login: username + password → JWT access + refresh and user object.
- Token refresh: Automatic retry on 401 using refresh token (axios interceptor).
- Profile: View/update profile (name, email, profile picture); change password.
- Protected routes: All app routes except
/loginand/registerrequire authentication.
- CRUD: Create, read, update, delete tasks.
- Fields: Title, description, priority (Extreme / Moderate / Low), status (Not Started / In Progress / Completed), due date, optional image, optional category.
- Filtering & search: By status, priority, category, and text search; ordering (e.g. by date, priority).
- Statistics: Aggregated counts and percentages (e.g. completed, in progress, not started) for dashboard/vitals.
- CRUD: Create, read, update, delete categories.
- Fields: Name, color (hex). Each user has their own set of categories; names are unique per user.
- Tasks can be optionally linked to one category.
- Unfold Admin UI: Modern Django admin at
/admin/for managing users, tasks, and categories. - Django Control Room dashboard: Central operations console at
/admin/dj-control-room/with Redis, cache, URL, and Celery panels to help you monitor and debug the system (seebackend/README.mdfor more details).
- Dashboard: Overview and task statistics.
- My Task / Vitals: Task list and progress.
- Calendar: Calendar view for tasks.
- Categories: Manage categories; used when creating/editing tasks.
- Notifications, Settings, Help: Placeholder or supporting pages.
- Layout: Header, collapsible sidebar, responsive behavior (e.g. mobile).
- Toasts: Success/error feedback via
react-hot-toast.
- Node.js (e.g. 18+) and npm (or equivalent) for the frontend.
- Python 3.8+ and pip for the backend.
- MySQL 5.7+ (or 8.x) running and accessible.
cd backend
python -m venv venv
# Windows
venv\Scripts\activate
# Linux / macOS
source venv/bin/activate
pip install -r requirements.txtCreate the MySQL database and user (adjust port if needed):
CREATE DATABASE todolist CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'todolist'@'localhost' IDENTIFIED BY 'todolist1234';
GRANT ALL PRIVILEGES ON todolist.* TO 'todolist'@'localhost';
FLUSH PRIVILEGES;Ensure backend/dev.json matches your DB (name, user, password, host, port). Example:
{
"DEBUG": true,
"SECRET_KEY": "your-secret-key",
"ALLOWED_HOSTS": ["*"],
"DATABASES": {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": "todolist",
"USER": "todolist",
"PASSWORD": "todolist1234",
"HOST": "localhost",
"PORT": "3306",
"OPTIONS": {}
}
}
}Run migrations and (optionally) create a superuser:
python manage.py migrate
python manage.py createsuperuser # optional, for /admin/
python manage.py runserverBackend will be at http://localhost:8000. API v1 base: http://localhost:8000/api/v1/.
cd frontend
npm installCopy environment example and set the API base URL:
cp .env.example .envIn .env:
VITE_API_BASE_URL=http://localhost:8000/api/v1Start the dev server:
npm run devFrontend will typically run at http://localhost:5173. Use Login or Register to get a JWT and access the app.
- Terminal 1:
backend→python manage.py runserver - Terminal 2:
frontend→npm run dev
Then open http://localhost:5173 and sign in or register.
- Config file:
backend/dev.json(orprod.jsonwhenDEBUGis false indev.json). HoldsSECRET_KEY,DEBUG,ALLOWED_HOSTS, andDATABASES. - CORS: Allowed origins are in
core/settings.py(e.g.http://localhost:5173,http://localhost:3000). Adjust for your frontend URL. - JWT: Access token lifetime 60 minutes, refresh 1 day; configured in
SIMPLE_JWTinsettings.py. - Static/Media: Static files via WhiteNoise; media (uploads) at
MEDIA_URL/MEDIA_ROOT(e.g./media/,backend/media/). - Admin & Ops Dashboard: Unfold-themed Django admin plus Django Control Room (see
backend/README.md) with Redis, Cache, URLs, and Celery panels at/admin/dj-*-panel/and the main dashboard at/admin/dj-control-room/.
- API base URL:
VITE_API_BASE_URLin.env(e.g.http://localhost:8000/api/v1). Used inconstants/index.jsand axios baseURL. - Refresh token: Axios interceptor in
services/axios.jsuses the same base URL for the refresh endpoint; ensure it points to the same backend.
The app is designed to use API v1 (Django REST Framework). The following is a concise overview; the backend README (backend/README.md) has full request/response examples.
- v1:
http://localhost:8000/api/v1/
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/register/ |
Register (username, email, name, password, password_confirm) |
| POST | /auth/login/ |
Login (username, password) → access, refresh, user |
| POST | /auth/refresh/ |
Body: { "refresh": "..." } → new access token |
| Method | Endpoint | Description |
|---|---|---|
| GET | /users/me/ |
Current user |
| PUT | /users/profile/ |
Update profile (including profile_picture) |
| POST | /users/change-password/ |
Change password |
| Method | Endpoint | Description |
|---|---|---|
| GET | /todos/ |
List (optional: status, priority, category, search, ordering, page) |
| POST | /todos/ |
Create (multipart/form-data; optional image, category) |
| GET | /todos/{id}/ |
Retrieve one |
| PUT | /todos/{id}/ |
Full update |
| DELETE | /todos/{id}/ |
Delete |
| GET | /todos/statistics/ |
Counts and percentages by status |
| Method | Endpoint | Description |
|---|---|---|
| GET | /categories/ |
List current user’s categories |
| POST | /categories/ |
Create (name, color) |
| GET | /categories/{id}/ |
Retrieve one |
| PUT | /categories/{id}/ |
Update |
| DELETE | /categories/{id}/ |
Delete |
All authenticated requests must include the header:
Authorization: Bearer <access_token>.
The backend also includes an API v2 (Django Bolt) definition in core/api.py and under accounts.api.v2 and todos.api.v2. Those endpoints mirror v1 with async handlers and OpenAPI; they are only active if the Bolt API is mounted in the project URLs (see backend docs).
From backend/ with the virtual environment activated:
pytest
pytest -v
pytest --cov=todos --cov=accounts --cov-report=html
pytest accounts/test/
pytest todos/test/Coverage report: htmlcov/index.html.
Use the project’s existing scripts:
cd frontend
npm run lint
npm run format:check
npm run formatAdd npm test or a test runner (e.g. Vitest) if you introduce frontend unit tests.
- Authentication: All task and category endpoints require a valid JWT; auth is enforced by DRF and (if used) Bolt guards.
- Authorization: Queries are filtered by
userso that users only see and modify their own tasks and categories. - Passwords: Django’s default password validators are used; tokens are not stored in code.
- CORS: Limited to explicit origins (e.g. dev frontend URLs).
- File uploads: Image types and size limits are enforced (see
ALLOWED_IMAGE_TYPESandFILE_UPLOAD_MAX_MEMORY_SIZEinsettings.py). - Secrets: Use strong
SECRET_KEYandprod.json(or env-based config) in production; never commit real secrets.
For more detail on the backend (migrations, admin, troubleshooting), see backend/README.md.