A full-stack task collaboration platform built with React, Node.js, MongoDB, and Socket.io. Similar to a lightweight Trello/Notion hybrid with real-time multi-user collaboration.
Live Demo: https://task-collab-beta.vercel.app
- Project Overview
- Functional Requirements
- Technical Stack
- Frontend Architecture
- Backend Architecture
- Database Schema
- API Contract Design
- Real-Time Synchronization Strategy
- Scalability Considerations
- Setup & Installation
- Project Structure
- Testing Guide
TaskFlow is a collaborative task management application that allows teams to:
- Create and manage project boards
- Organize tasks into customizable lists
- Assign tasks to team members
- Drag-and-drop tasks across lists in real-time
- Track activity history for all board actions
- Collaborate seamlessly with multiple users simultaneously
- Real-time multi-user collaboration via WebSockets
- User authentication with JWT tokens
- Activity history tracking with detailed event logging
- Board membership management (users can join shared boards)
- Responsive design with dark theme UI
- Search and pagination for tasks
- Users can register with email and password
- Users can login with credentials
- JWT token-based authentication
- Persistent session management via localStorage
- User profile with name and email
- Create boards (becomes owner/member automatically)
- View all boards user is member of
- Join existing boards via shared links
- View board with all members
- Real-time member updates
- Create multiple lists within a board
- Lists act as columns/stages (To Do, In Progress, Done, etc.)
- Automatic sorting by position
- Lists populate with associated tasks
- Create tasks within lists
- Update task title, description, status
- Delete tasks
- Assign tasks to board members
- Drag-and-drop tasks between lists
- Position-based ordering within lists
- Multi-user simultaneous editing
- Real-time task creation/update/deletion
- Real-time task movement between lists
- Instant updates across all connected clients
- Log all user actions
- Display activity history in timeline format
- Show user attribution for each action
- Track specific changes (assignments, status changes)
- Framework: React 18.3.1
- Routing: React Router 6.28.0
- State Management: Zustand (lightweight alternative to Redux)
- Real-Time: Socket.io client
- Drag & Drop: @hello-pangea/dnd
- UI Components: Radix UI (accessible component library)
- Styling: Tailwind CSS with custom dark theme
- HTTP Client: Axios
- Deploy: Vercel
- Framework: Express.js 5.2.1
- Database: MongoDB 9.2.1
- Real-Time: Socket.io 4.8.3
- Authentication: JWT + bcryptjs
- CORS: Configured with environment variables
- Deploy: Node.js (Vercel/Railway/Heroku compatible)
- MongoDB: NoSQL document database
- Collections: Users, Boards, Lists, Tasks, Activities
- Indexing: Optimized indexes for fast queries
frontend/src/
├── components/
│ ├── board/ # Board-related components
│ │ ├── ActivityPanel.jsx # Activity history sidebar
│ │ ├── ListColumn.jsx # Individual list column
│ │ ├── TaskCard.jsx # Task card display
│ │ └── TaskModal.jsx # Task creation/edit modal
│ ├── layout/ # Layout components
│ │ └── ProtectedRoute.jsx # Auth-protected routes
│ ├── landing/ # Marketing/landing components
│ ├── auth/ # Auth components
│ └── ui/ # Reusable UI components (button, input, etc.)
├── pages/
│ ├── LandingPage.jsx
│ ├── LoginPage.jsx
│ ├── RegisterPage.jsx
│ ├── DashboardPage.jsx # Boards overview
│ └── BoardPage.jsx # Main board workspace
├── services/
│ ├── api.js # Axios API client with interceptors
│ └── socket.js # Socket.io connection and events
├── store/
│ ├── authStore.js # Zustand auth store
│ └── boardStore.js # Zustand board/list/task store
├── lib/
│ └── utils.js # Utility functions
└── App.jsx
useAuthStore: {
user: { _id, name, email, id }, // Current user
token: "jwt_token", // JWT token
isAuthenticated: boolean, // Auth status
setAuth(user, token), // Login/Register
logout(), // Logout
updateUser(updates) // Update profile
}useBoardStore: {
currentBoard: Board, // Active board
lists: List[], // Lists with tasks
setCurrentBoard(board, lists), // Load board data
addList(list), // Create list
addTask(task), // Create task
updateTask(task), // Update task
deleteTask(taskId), // Delete task
moveTask(task), // Move task to different list
reorderTasksLocally() // Optimistic UI update
}User Action
↓
Component calls store/API method
↓
Optimistic UI update (if applicable)
↓
API request to backend
↓
Backend processes & broadcasts via socket
↓
Store updates via socket listener
↓
Component re-renders with updated data
State Management:
- Zustand for lightweight, efficient state
- Persisted authentication in localStorage
- Normalized board/list/task structure for easy updates
Socket Communication:
Socket Events:
- joinBoard(boardId) → Join board room
- taskCreated ← Server broadcast
- taskUpdated ← Server broadcast
- taskMoved ← Server broadcast
- taskDeleted ← Server broadcastAPI Client:
- Axios instance with JWT token interceptor
- Automatic token attachment to all requests
- Global 401 handling for session expiry
backend/src/
├── server.js # Main Express app & Socket.io setup
├── config/
│ └── db.js # MongoDB connection
├── models/ # Mongoose schemas
│ ├── User.js # User schema
│ ├── Board.js # Board schema
│ ├── List.js # List schema
│ ├── Task.js # Task schema
│ └── Activity.js # Activity log schema
├── controllers/ # Business logic
│ ├── auth.controller.js
│ ├── board.controller.js
│ ├── list.controller.js
│ ├── task.controller.js
│ └── activity.controller.js
├── routes/ # API endpoints
│ ├── auth.routes.js
│ ├── board.routes.js
│ ├── list.routes.js
│ ├── task.routes.js
│ └── activity.routes.js
├── middleware/ # Custom middleware
│ ├── auth.middleware.js # JWT verification
│ └── error.middleware.js # Error handling
├── utils/
│ └── activityLogger.js # Activity logging utility
└── .env # Environment variables
CORS Handler:
- Whitelist local development URLs (localhost:3000, :5173, :5174)
- Whitelist production frontend (https://task-collab-beta.vercel.app)
- Allow credentials for JWT tokens
- Support preflight requests
Auth Middleware:
Endpoint Protection:
- Extracts JWT from Authorization header
- Verifies token signature
- Attaches user.id to request
- Rejects if token invalid/expiredError Handler:
- Centralized error catching
- Consistent error response format
- Status code mapping
- MongoDB Atlas or local MongoDB
- Connection pooling for performance
- Reconnection logic for resilience
User (1) ──────────────> (N) Board
owns/member
Board (1) ──────────────> (N) List
contains
Board (1) ──────────────> (N) Task
contains
List (1) ──────────────> (N) Task
contains
User (1) ──────────────> (N) Task
assigned
User (1) ──────────────> (N) Activity
performs
{
_id: ObjectId,
name: String, // e.g., "John Doe"
email: String, // Unique email
password: String, // Hashed password (bcrypt)
createdAt: Date,
updatedAt: Date
}{
_id: ObjectId,
title: String, // e.g., "Website Redesign"
owner: ObjectId, // Reference to User
members: [ObjectId], // Array of User IDs
createdAt: Date,
updatedAt: Date
}
Indexes:
- members: For fast user membership queries{
_id: ObjectId,
title: String, // e.g., "To Do", "In Progress"
boardId: ObjectId, // Reference to Board
position: Number, // Order within board (0, 1, 2...)
createdAt: Date,
updatedAt: Date
}
Indexes:
- boardId + position: For fast list retrieval and sorting{
_id: ObjectId,
title: String, // Task title
description: String, // Task details (optional)
listId: ObjectId, // Reference to List
boardId: ObjectId, // Reference to Board (denormalized)
assignedTo: [ObjectId], // Array of User IDs (supports multiple)
status: String, // "todo", "in-progress", "done", "blocked"
position: Number, // Order within list
dueDate: Date, // Optional due date
createdAt: Date,
updatedAt: Date
}
Indexes:
- boardId + position: For fast task retrieval
- listId: For list-specific queries
- title: Full-text search index{
_id: ObjectId,
user: ObjectId, // Reference to User (who performed action)
action: String, // E.g., "created task", "updated task"
details: String, // Detailed action description
taskId: ObjectId, // Reference to Task (if applicable)
boardId: ObjectId, // Reference to Board
assignedToUser: ObjectId, // User assigned to task (if applicable)
changes: Object, // Fields that changed (for updates)
createdAt: Date,
updatedAt: Date
}
Indexes:
- boardId + createdAt (-1): For activity timeline queriesCommon Queries:
-
Get user's boards with members
db.boards.find({ members: userId }).populate('members')
-
Get board with all lists and tasks
db.lists.find({ boardId }) db.tasks.find({ boardId }).populate('assignedTo')
-
Get activity timeline
db.activities.find({ boardId }).sort({ createdAt: -1 }).limit(100)
-
Search tasks
db.tasks.find({ boardId, title: { $regex: query } }).limit(20)
Register new user
Request:
{
name: String,
email: String,
password: String (min 6 chars)
}
Response (201):
{
_id: String,
name: String,
email: String,
token: String (JWT)
}
Error (400):
{ message: "User exists" }
Login with credentials
Request:
{
email: String,
password: String
}
Response (200):
{
_id: String,
name: String,
email: String,
token: String (JWT)
}
Error (401):
{ message: "Invalid credentials" }
Get all boards user is member of
Response (200):
[ Board, Board, ... ]
Headers: Authorization: Bearer {token}
Create new board
Request:
{
title: String
}
Response (201):
{
_id: String,
title: String,
owner: ObjectId,
members: [ObjectId]
}
Get board details with lists and tasks
Response (200):
{
board: {
_id: String,
title: String,
owner: ObjectId,
members: [ { _id, name, email } ]
},
lists: [
{
_id: String,
title: String,
position: Number,
tasks: [ Task, Task, ... ]
}
]
}
Join an existing board
Response (200):
{
message: "Joined board successfully",
board: Board,
lists: List[]
}
Error (400):
{ message: "User is already a member" }
Create new list
Request:
{
title: String,
boardId: String,
position: Number
}
Response (201):
{
_id: String,
title: String,
boardId: String,
position: Number
}
Create new task
Request:
{
title: String,
description: String (optional),
listId: String,
boardId: String,
assignedTo: [String] (optional - array of user IDs),
status: String (default: "todo"),
dueDate: Date (optional)
}
Response (201):
Task object with all fields
Socket Event:
io.to(boardId).emit('taskCreated', task)
Update task
Request: (any fields to update)
{
title: String,
description: String,
status: String,
assignedTo: [String],
dueDate: Date
}
Response (200):
Updated Task object
Socket Event:
io.to(boardId).emit('taskUpdated', task)
Delete task
Response (200):
{ message: "Task deleted" }
Socket Event:
io.to(boardId).emit('taskDeleted', taskId)
Move task to different list
Request:
{
sourceListId: String,
destinationListId: String,
newPosition: Number
}
Response (200):
Updated Task object
Socket Event:
io.to(boardId).emit('taskMoved', task)
Get activity history for board
Response (200):
[
{
_id: String,
user: { _id, name, email },
action: String,
details: String,
assignedToUser: { _id, name, email } (if applicable),
taskId: String,
boardId: String,
createdAt: Date
}
]
Limit: 100 most recent activities
Sort: Newest first
Socket.io Setup:
Server: new Server(server, { cors: corsOptions })
Client: io(BASE_URL)Connection Flow:
1. User navigates to board page
→ Frontend calls connectSocket()
→ Socket connects to server
2. Socket.on('connect')
→ Emit 'joinBoard' with boardId
→ Server joins socket to room (boardId)
3. User performs action (create/update/delete task)
→ Frontend sends API request
→ Backend processes & emits socket event
→ All sockets in room receive update
→ Store listener processes event
→ UI re-renders with new data
Client → Server:
socket.emit('joinBoard', boardId)
- Joins user to board-specific room
- Called when viewing board page
socket.disconnect()
- Implicit when leaving page
- Server removes from roomServer → Client (Broadcasts):
io.to(boardId).emit('taskCreated', task)
io.to(boardId).emit('taskUpdated', task)
io.to(boardId).emit('taskMoved', task)
io.to(boardId).emit('taskDeleted', taskId)Example: User updates task status
Frontend:
1. User clicks status dropdown → select "Done"
2. Component calls: tasksAPI.update(taskId, { status: 'done' })
3. Optimistic UI update: updateTask() in store
4. Loading state shown to user
Backend:
5. Receives PATCH /api/tasks/:taskId with { status: 'done' }
6. Updates MongoDB record
7. Returns updated task
8. Broadcasts: io.to(boardId).emit('taskUpdated', task)
9. Logs activity
Frontend Listeners:
10. socket.on('taskUpdated') fires
11. Store listener: updateTask(task)
12. Zustand store updates
13. Component re-renders
14. All connected users see update instantly
Optimistic Updates:
- Frontend updates UI immediately
- If API fails, revert to previous state
- User sees instant feedback
Last-Write-Wins:
- If two users edit same field simultaneously
- Last API request wins
- Server time is source of truth
- Activity log captures both actions
Position Conflicts:
- When moving tasks between lists
- Server increments positions
- Other tasks reordered automatically
- No manual conflict resolution needed
Logging happens server-side:
// After task update
await logActivity({
user: req.user.id,
action: "updated task",
details: "changed task status to done",
taskId: task._id,
boardId: task.boardId,
changes: { status: 'done' }
})Activity fetched on demand:
// Client requests activity when panel opened
const { data } = await activitiesAPI.getByBoard(boardId)
// Returns last 100 activities sorted newest first1. Single Server Instance
- Current: Single Node.js server handles all connections
- Bottleneck: One server can handle ~10k concurrent connections
2. In-Memory Socket Rooms
- Current: Socket.io stores rooms in server memory
- Issue: If server crashes, connections lost
3. Database Queries
- Current: Queries on every request
- Issue: No caching strategy
Problem: Socket.io rooms only exist on one server
Solution: Socket.io Adapter
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
const pubClient = createClient();
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));With Redis adapter:
- Multiple servers share socket connections
- Broadcasts reach all servers
- Rooms span multiple instances
Setup:
Internet
↓
Load Balancer (Nginx/HAProxy)
↓
├─ Server 1 (Node.js + Socket.io)
├─ Server 2 (Node.js + Socket.io)
└─ Server 3 (Node.js + Socket.io)
↓
MongoDB Cluster
Sticky Sessions Requirement:
- Socket.io users must connect to same server for reconnection
- Load balancer tracks user → server mapping
- Redis handles state across servers
Redis Cache:
// Cache board details (5 minute TTL)
const cacheKey = `board:${boardId}`;
const cached = await redis.get(cacheKey);
if (!cached) {
const board = await Board.findById(boardId).populate('members');
await redis.setex(cacheKey, 300, JSON.stringify(board));
}
// Invalidate on update
await redis.del(cacheKey);Benefits:
- Reduce database queries by 70%
- Faster API responses
- Reduce MongoDB load
Current Indexes:
// boards
db.boards.createIndex({ members: 1 })
// lists
db.lists.createIndex({ boardId: 1, position: 1 })
// tasks
db.tasks.createIndex({ boardId: 1 })
db.tasks.createIndex({ listId: 1 })
db.tasks.createIndex({ title: "text" })
// activities
db.activities.createIndex({ boardId: 1, createdAt: -1 })
db.activities.createIndex({ createdAt: -1 })Future Optimizations:
// Sharding by boardId for massive scale
db.tasks.createIndex({ boardId: 1, _id: 1 })
// Materialized views for activity counts
db.activitySummary.createIndex({ boardId: 1, userId: 1 })
// Document-level encryption for sensitive dataAPI Gateway
├─ Auth Service
├─ Board Service
├─ Task Service
├─ Activity Service
└─ Notification Service
Shared Services:
├─ MongoDB Cluster
├─ Redis Cluster
└─ Message Queue (RabbitMQ/Kafka)
Current:
- Frontend deployed to Vercel (auto-handled)
- API calls to backend
Improvements:
- CloudFront/CloudFlare for API caching
- Gzip compression
- HTTP/2 push
- Image optimization
Current:
mongoose.connect(MONGO_URI)
// Default pool size: 10Optimized:
mongoose.connect(MONGO_URI, {
maxPoolSize: 50, // For production
minPoolSize: 10,
socketTimeoutMS: 45000,
serverSelectionTimeoutMS: 5000
})const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);Recommended Tools:
- New Relic / DataDog for APM
- Sentry for error tracking
- ELK Stack for logging
- PromQL/Grafana for metrics
// Add request logging
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} - ${duration}ms`);
});
next();
});- Node.js 16+ and npm
- MongoDB (local or Atlas)
- Git
Backend (.env file)
# Server
PORT=5000
# Database
MONGO_URI=mongodb://localhost:27017/taskflow
# Authentication
JWT_SECRET=your_super_secret_jwt_key_change_in_production
# Frontend URL (for CORS)
FRONTEND_URL=https://task-collab-beta.vercel.appFrontend (vite.config.js)
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000/api'1. Clone repository
git clone <repository-url>
cd task_collab2. Backend Setup
cd backend
npm install
npm run devServer will start on http://localhost:5000
3. Frontend Setup (in new terminal)
cd frontend
npm install
npm run devApp will open on http://localhost:5173
4. Access Application
- Open browser to
http://localhost:5173 - Register account
- Create board and invite teammates
Frontend Deployment (Vercel)
cd frontend
vercel deployBackend Deployment
Option 1: Vercel (Node.js)
cd backend
vercel deployOption 2: Railway.app
# Connected via GitHub
# Auto-deploys on pushOption 3: Digital Ocean / Heroku
# Configure add-ons: MongoDB Atlas, Redis
# Deploy via CLI or GitHub integrationtask_collab/
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ │ ├── board/
│ │ │ │ ├── ActivityPanel.jsx
│ │ │ │ ├── ListColumn.jsx
│ │ │ │ ├── TaskCard.jsx
│ │ │ │ └── TaskModal.jsx
│ │ │ ├── layout/
│ │ │ ├── landing/
│ │ │ ├── auth/
│ │ │ └── ui/
│ │ ├── pages/
│ │ │ ├── LandingPage.jsx
│ │ │ ├── LoginPage.jsx
│ │ │ ├── RegisterPage.jsx
│ │ │ ├── DashboardPage.jsx
│ │ │ └── BoardPage.jsx
│ │ ├── services/
│ │ │ ├── api.js
│ │ │ └── socket.js
│ │ ├── store/
│ │ │ ├── authStore.js
│ │ │ └── boardStore.js
│ │ ├── lib/
│ │ │ └── utils.js
│ │ └── App.jsx
│ ├── public/
│ ├── index.html
│ ├── package.json
│ ├── vite.config.js
│ └── tailwind.config.js
│
├── backend/
│ ├── src/
│ │ ├── server.js
│ │ ├── config/
│ │ │ └── db.js
│ │ ├── models/
│ │ │ ├── User.js
│ │ │ ├── Board.js
│ │ │ ├── List.js
│ │ │ ├── Task.js
│ │ │ └── Activity.js
│ │ ├── controllers/
│ │ │ ├── auth.controller.js
│ │ │ ├── board.controller.js
│ │ │ ├── list.controller.js
│ │ │ ├── task.controller.js
│ │ │ └── activity.controller.js
│ │ ├── routes/
│ │ │ ├── auth.routes.js
│ │ │ ├── board.routes.js
│ │ │ ├── list.routes.js
│ │ │ ├── task.routes.js
│ │ │ └── activity.routes.js
│ │ ├── middleware/
│ │ │ ├── auth.middleware.js
│ │ │ └── error.middleware.js
│ │ └── utils/
│ │ └── activityLogger.js
│ ├── .env
│ ├── .env.example
│ ├── package.json
│ └── README.md
│
└── README.md (this file)
Test 1: User Authentication
- Register: name, email, password
- Verify username shows correctly
- Login with credentials
- Check JWT token in localStorage
Test 2: Board Management
- Create board
- Verify owner/member status
- Join board with second user(copy the url and paste it in browser 2 , where u logged in as another user)
- Verify member list updates
Test 3: Task Operations
- Create list in board
- Create task in list
- Assign task to team member
- Verify assignment in dropdown
- Drag task to different list
- Verify position updated
Test 4: Real-Time Sync
- Open board in two browser windows
- Create task in first window
- Verify task appears instantly in second window
- Update status in second window
- Verify update appears in first window
Test 5: Activity Tracking
- Perform multiple actions
- Open Activity panel
- Verify all actions logged
- Verify user names displayed correctly
- Verify timestamps accurate
Register User
curl -X POST http://localhost:5000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"John","email":"john@test.com","password":"test123"}'Create Board
curl -X POST http://localhost:5000/api/boards \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"title":"My Board"}'Get Activities
curl http://localhost:5000/api/activities/{boardId} \
-H "Authorization: Bearer {token}"- API Response Time: < 200ms (p95)
- Task Update Latency: < 100ms (real-time)
- Page Load Time: < 3s
- Database Query Time: < 50ms (p95)
Current Monitoring:
- Browser DevTools (Network tab)
- Console logs for socket events
- Network tab for API calls
Production Monitoring (Recommended):
- New Relic APM
- Sentry error tracking
- CloudWatch/DataDog metrics
- ESLint configuration: React best practices
- Prettier for code formatting
- Responsive design (mobile-first)
- Accessibility (WCAG 2.1 AA)
- Consistent error handling
- Input validation on all endpoints
- Rate limiting for API endpoints
- CORS configured properly
- Logging for debugging
- Unit tests for utilities
- Integration tests for API endpoints
- E2E tests for user flows
- Socket.io event testing
- Single-server deployment (no clustering)
- No payment processing
- No email notifications
- Limited user roles (owner/member only)
- No file attachments on tasks
- No task comments/discussions
- Add Redis for caching & clustering
- Implement user roles (viewer, editor, admin)
- Add task comments and @mentions
- File attachments with cloud storage
- Email notifications for assignments
- Mobile app (React Native)
- GraphQL API as alternative
- Dark/Light theme toggle
- Task templates
- Workspace teams management
Contributions welcome! Please:
- Fork repository
- Create feature branch
- Submit pull request
- Follow code standards
MIT License - See LICENSE file for details
For issues or questions:
- GitHub Issues
- Email: support@taskflow.app
- Documentation: https://docs.taskflow.app
Built as a full-stack engineering interview assignment demonstrating:
- Modern React patterns and state management
- RESTful API design
- Real-time WebSocket communication
- Database modeling and optimization
- Scalability considerations
- Code quality and maintainability
Technologies: React, Node.js, Express, MongoDB, Socket.io, Tailwind CSS, Zustand
Live Demo: https://task-collab-beta.vercel.app