A full-stack Taskflow application built with FastAPI (backend) and React + TypeScript (frontend). It allows users to create boards, manage tasks (cards), and track progress visually.
- 🔐 User Authentication (JWT-based login/register)
- 📋 Create and manage boards
- 📝 Add, edit, delete cards
- 🔄 Drag-and-drop task management (To Do → In Progress → Completed)
- 📊 Ordered task positioning (used
floating point indexing) - ⚡ Optimized backend queries (avoids N+1 problem using
selectinload) - 🌐 RESTful API with FastAPI
- 🎨 Modern UI with React + TailwindCSS
- React (Vite)
- TypeScript
- TailwindCSS
- React Beautiful DnD
- FastAPI
- SQLAlchemy (Async ORM)
- PostgreSQL / SQLite
- JWT Authentication
taskflow/
│
├── backend/
│ ├── main.py
│ ├── models.py
│ ├── schema.py
│ ├── database.py
│ └── cors.py
│
├── frontend/my-app
│ ├── src/
│ ├── src/components/
│ ├── DockerFile
│ └── src/config/
│
└── docker-compose.yml
cd taskflow
docker-compose up --build
## 🔐 Authentication
* JWT tokens are stored in `localStorage`
* Automatically attached to API requests
* Expired tokens trigger auto logout
---
## 📡 API Endpoints
### Auth
* `POST /register`
* `POST /login`
### Boards
* `GET /getallboard/`
* `POST /getboard/{board_id}`
* `Delete /deleteboard/{board_id}`
* `PUT /updateboard/{board_id}`
### Cards
* `POST /createcard/`
* `PUT /updatecard/{id}`
---
To edit a card's description:
Right-click on the card
Select the edit option
Update the description as needed
Note: Editing is only available via right-click on the card.
## ⚡ Performance Optimization
* Used `selectinload` to avoid N+1 query problem bacause it will
1 query → fetch board
1 query → fetch all cards using IN (...) total 2 queries , as compared to joinedin which will give rows like board1 +card1;board1+card2 as comapred to selectin which gives board1 once then fetches all cards card1,card2
* Efficient card ordering using position field.
Used floating point indexing which creates a new position.
For example, if a card is inserted between 2.0 and 3.0, then the new position = (2.0 + 3.0) / 2 = 2.5, so we don’t need to update positions of all cards in that column.
Initially, when a card is inserted, it is given the last position in that column.
* To handle race condition when 2 cards are moved simultaneously.
Used database locking + transaction. A transaction begins when a card is moved within a list or across lists, and the card (or relevant rows) are locked. If another user tries to update the same card or positions, they have to wait until the first transaction completes.
Why not only transaction or only locking?
Only transaction is not sufficient because multiple users can read the same initial data at the same time and update based on stale values, resulting in incorrect or inconsistent ordering.
Only locking is not sufficient because locks work only inside a transaction. Without a transaction, the lock is released immediately after the query, so another user can still read and update the same data, leading to race conditions.
## Author
**Radhika Garg**