Building a Real-Time Task Management App with React and Node.js
A task management app is the perfect full stack project. It requires user authentication, CRUD operations, a clean UI, and — if you want to take it further — real-time updates. It is simple enough for a beginner to build in a week, yet extensible enough to demonstrate advanced concepts.
In this tutorial, we will build a complete task management app (like a mini Trello) with a React frontend, a Node.js + Express backend, and PostgreSQL for storage. We will add real-time updates using Socket.io so that when one user adds a task, everyone connected sees it instantly.
Project Structure
We will use a monorepo structure for simplicity:
task-manager/
client/ # React frontend (Vite)
server/ # Node.js + Express backend
.gitignore
README.md
Part 1: Backend Setup
Set up the server as covered in our REST API tutorial. Create a tasks table in PostgreSQL:
CREATE TABLE tasks (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'todo' CHECK (status IN ('todo', 'in_progress', 'done')),
priority TEXT DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high')),
user_id UUID REFERENCES users(id) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
Part 2: Task API Endpoints
Create the routes for task CRUD:
// server/routes/tasks.js
const express = require('express');
const db = require('../db');
const auth = require('../middleware/auth');
const router = express.Router();
router.use(auth);
router.get('/', async (req, res) => {
const result = await db.query(
'SELECT * FROM tasks WHERE user_id = $1 ORDER BY created_at DESC',
[req.userId]
);
res.json(result.rows);
});
router.post('/', async (req, res) => {
const { title, description, priority } = req.body;
const result = await db.query(
`INSERT INTO tasks (title, description, priority, user_id)
VALUES ($1, $2, $3, $4) RETURNING *`,
[title, description, priority || 'medium', req.userId]
);
const task = result.rows[0];
req.app.get('io').emit('task:created', task);
res.status(201).json(task);
});
router.put('/:id', async (req, res) => {
const { title, description, status, priority } = req.body;
const result = await db.query(
`UPDATE tasks SET title = COALESCE($1, title),
description = COALESCE($2, description),
status = COALESCE($3, status),
priority = COALESCE($4, priority),
updated_at = NOW()
WHERE id = $5 AND user_id = $6 RETURNING *`,
[title, description, status, priority, req.params.id, req.userId]
);
if (!result.rows.length) return res.status(404).json({ error: 'Task not found' });
req.app.get('io').emit('task:updated', result.rows[0]);
res.json(result.rows[0]);
});
router.delete('/:id', async (req, res) => {
const result = await db.query(
'DELETE FROM tasks WHERE id = $1 AND user_id = $2 RETURNING id',
[req.params.id, req.userId]
);
if (!result.rows.length) return res.status(404).json({ error: 'Task not found' });
req.app.get('io').emit('task:deleted', { id: req.params.id });
res.json({ message: 'Task deleted' });
});
module.exports = router;
Notice the req.app.get('io') calls — this is where we emit real-time events. We will set up Socket.io in the main server file.
Part 3: Add Real-Time with Socket.io
Install Socket.io on the server:
npm install socket.io
Update your server/index.js to use Socket.io:
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const app = express();
const server = http.createServer(app);
const io = new Server(server, { cors: { origin: '*' } });
app.use(cors());
app.use(express.json());
app.set('io', io);
// ... routes ...
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
server.listen(3000, () => {
console.log('Server running on port 3000 with WebSocket support');
});
Part 4: React Frontend
Create the React app with Vite:
npm create vite@latest client -- --template react
cd client
npm install axios socket.io-client react-router-dom
Create a components folder and build the following components:
Auth Component
A simple login/register form that stores the JWT token in localStorage after successful authentication.
TaskList Component
Displays tasks grouped by status columns (To Do, In Progress, Done). Users can drag and drop tasks between columns to update their status.
TaskForm Component
A form to add new tasks with title, description, and priority fields.
Part 5: Real-Time Frontend Integration
In your main App component, connect to Socket.io:
import { useEffect, useState } from 'react';
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000');
function App() {
const [tasks, setTasks] = useState([]);
useEffect(() => {
fetchTasks();
socket.on('task:created', (task) => {
setTasks(prev => [task, ...prev]);
});
socket.on('task:updated', (updated) => {
setTasks(prev => prev.map(t =>
t.id === updated.id ? updated : t
));
});
socket.on('task:deleted', ({ id }) => {
setTasks(prev => prev.filter(t => t.id !== id));
});
return () => {
socket.off('task:created');
socket.off('task:updated');
socket.off('task:deleted');
};
}, []);
async function fetchTasks() {
const token = localStorage.getItem('token');
const res = await axios.get('/api/tasks', {
headers: { Authorization: `Bearer ${token}` }
});
setTasks(res.data);
}
// ... render ...
}
Now when any user creates or updates a task, all other connected users see the change instantly without refreshing the page.
Part 6: Drag and Drop with React
Install @dnd-kit/core and @dnd-kit/sortable to add drag and drop functionality:
npm install @dnd-kit/core @dnd-kit/sortable
Create three columns (To Do, In Progress, Done). When a task is dropped into a different column, call the PUT API to update its status. The real-time events will sync the change to all users.
Part 7: Deployment
Deploy the backend on Render and the frontend on Vercel. Set the environment variables on each platform:
- Render: DATABASE_URL, JWT_SECRET
- Vercel: VITE_API_URL (pointing to your Render backend URL)
Update the Socket.io connection URL to point to your deployed backend in production.
Extensions and Next Steps
This project can be extended in many ways:
- Add due dates and calendar view
- Add file attachments to tasks
- Add team collaboration with shared workspaces
- Add activity log to track changes
- Add email notifications for task assignments
- Build a mobile app using React Native
Conclusion
This task management app covers the complete full stack: React frontend with drag and drop, Node.js + Express API with authentication, PostgreSQL database, and real-time updates with WebSockets. It is a portfolio-ready project that demonstrates every major skill a full stack developer needs.
Building this project end to end will teach you more than months of video tutorials. Start coding, make mistakes, and iterate. Need help or want to collaborate on a project? Contact Aarti Tech Services for development support, mentorship, or custom project development.