MERN Stack Task Manager
Complete Step-by-Step Setup Guide
Frontend + Backend + Architecture + All Commands
PART 1 — FRONTEND SETUP
Tech: React + TypeScript + Vite + Tailwind CSS + Shadcn UI + Bun
STEP 1 Create Vite + React + TypeScript Project
cd Mern_Stack_Project
bun create vite@latest frontend -- --template react-ts
cd frontend
bun install
📝 This creates a React + TypeScript project using Vite build tool.
STEP 2 Install Required Packages
bun add react-router-dom axios
bun add -d tailwindcss@3 postcss autoprefixer
bunx tailwindcss init -p
Package breakdown:
• react-router-dom — page navigation (Login → Dashboard)
• axios — backend API calls (better than fetch)
• tailwindcss@3 — CSS framework (v3, NOT v4 — v4 has no init command)
• postcss — Tailwind CSS processor
• autoprefixer — cross-browser CSS fix
• -d flag — devDependency, not needed in production
STEP 3 Configure Tailwind CSS
[Link] — replace content:
export default {
content: [
"./[Link]",
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: { extend: {} },
plugins: [],
}
src/[Link] — delete everything, add only:
@tailwind base;
@tailwind components;
@tailwind utilities;
📝 Yellow underline in VS Code is normal — add '[Link]: false' in settings to remove it.
STEP 4 Configure TypeScript Path Aliases
[Link] — replace full content:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "[Link]"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true
},
"include": ["src"],
"references": [{ "path": "./[Link]" }]
}
[Link] — add inside compilerOptions:
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
[Link] — replace full content:
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["[Link]"]
}
STEP 5 Configure Vite Path Alias
bun add -d @types/node
[Link] — replace full content:
import path from "path"
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": [Link](__dirname, "./src"),
},
},
})
📝 @/* alias lets you write: import Button from '@/components/Button' instead of
'../../../components/Button'
STEP 6 Setup Shadcn UI
bunx shadcn@4.9.0 init
# Select: Radix → Nova preset
# Fix [Link] after shadcn init (remove shadcn imports):
# Keep only the 3 tailwind lines
# Add components:
bunx shadcn@4.9.0 add button input card label
📝 Use shadcn@4.9.0 specifically — latest v4 has workspace config issues with Bun.
📝 After init, check src/[Link] — remove any @import lines shadcn added, keep only @tailwind
lines.
STEP 7 Create Folder Structure
Create these folders and empty files inside src/:
src/
├── components/
├── pages/
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── services/
│ ├── [Link]
│ └── [Link]
├── hooks/
│ └── [Link]
├── utils/
│ └── [Link]
└── routes/
└── [Link]
STEP 8 Test Frontend
bun run dev
Open browser: [Link]
Should show 'Task Manager' text — frontend setup complete.
PART 2 — BACKEND SETUP
Tech: [Link] + Express + MongoDB + JWT + bcrypt + Bun
STEP 9 Install Backend Dependencies
cd Mern_Stack_Project/backend
bun add express mongoose dotenv bcryptjs jsonwebtoken cors
bun add -d @types/express @types/cors @types/bcryptjs
@types/jsonwebtoken nodemon
Package breakdown:
• express — web server framework
• mongoose — MongoDB ODM (Object Document Mapper)
• dotenv — read .env file variables
• bcryptjs — hash passwords securely
• jsonwebtoken — create and verify JWT tokens
• cors — allow frontend to call backend API
• nodemon — auto-restart server on file change (dev only)
STEP 10 Configure [Link]
backend/[Link] — final version:
{
"type": "module",
"scripts": {
"dev": "nodemon [Link]",
"start": "node [Link]"
},
"dependencies": {
"bcryptjs": "^3.0.3",
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"jsonwebtoken": "^9.0.3",
"mongoose": "^9.6.3"
},
"devDependencies": {
"@types/bcryptjs": "^3.0.0",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.10",
"nodemon": "^3.1.14"
}
}
📝 "type": "module" allows import/export syntax instead of require().
STEP 11 MongoDB Atlas Setup
1. Go to: [Link]
2. Create free account → Create cluster (Free tier M0)
3. Cluster name: TaskManager, Region: Mumbai (ap-south-1), Provider: AWS
4. Create database user — save username and password
5. Network Access → Add IP → [Link]/0 (allow all)
6. Connect → Drivers → Copy connection string
7. Add database name in string:
# Before:
mongodb+srv://user:pass@[Link]/?appName=TaskManager
# After (add /taskmanager before ?):
mongodb+srv://user:pass@[Link]/taskmanager?appName=TaskManager
STEP 12 Create .env File
backend/.env:
PORT=5000
MONGO_URI=mongodb+srv://user:pass@[Link]/taskmanager?
appName=TaskManager
JWT_SECRET=taskmanager_super_secret_key_2024
📝 Never push .env to GitHub — add it to .gitignore.
STEP 13 Create Backend Folder Structure
Create these folders and empty files inside backend/src/:
backend/
├── src/
│ ├── common/
│ │ ├── db/
│ │ │ └── [Link]
│ │ ├── middleware/
│ │ │ └── [Link]
│ │ └── utils/
│ │ ├── [Link]
│ │ └── [Link]
│ ├── modules/
│ │ ├── auth/
│ │ │ ├── [Link]
│ │ │ ├── [Link]
│ │ │ ├── [Link]
│ │ │ └── [Link]
│ │ └── tasks/
│ │ ├── [Link]
│ │ ├── [Link]
│ │ ├── [Link]
│ │ └── [Link]
│ └── [Link]
├── [Link]
├── .env
├── .gitignore
└── [Link]
STEP 14 Create .gitignore Files
Root .gitignore (Mern_Stack_Project/.gitignore):
node_modules
**/node_modules
.env
**/.env
.[Link]
**/.[Link]
dist
**/dist
*.log
.DS_Store
[Link]
PART 3 — BACKEND CODE FILES
[Link]
import app from "./src/[Link]"
import dotenv from "dotenv"
import connectDB from "./src/common/db/[Link]"
[Link]()
const PORT = [Link] || 5000
connectDB().then(() => {
[Link](PORT, () => {
[Link](`Server running on [Link]
})
})
src/[Link]
import express from "express"
import cors from "cors"
import authRoutes from "./modules/auth/[Link]"
import taskRoutes from "./modules/tasks/[Link]"
const app = express()
[Link](cors())
[Link]([Link]())
[Link]("/api/auth", authRoutes)
[Link]("/api/tasks", taskRoutes)
export default app
src/common/db/[Link]
import mongoose from "mongoose"
const connectDB = async () => {
try {
const conn = await [Link]([Link].MONGO_URI)
[Link](`MongoDB connected: ${[Link]}`)
} catch (error) {
[Link](`DB Error: ${[Link]}`)
[Link](1)
}
}
export default connectDB
src/common/utils/[Link]
import jwt from "jsonwebtoken"
const generateToken = (userId) => {
return [Link]({ id: userId }, [Link].JWT_SECRET, {
expiresIn: "7d"
})
}
export default generateToken
src/common/utils/[Link]
export const successResponse = (res, data, message = "Success", status = 200) => {
return [Link](status).json({ success: true, message, data })
}
export const errorResponse = (res, message = "Error", status = 500) => {
return [Link](status).json({ success: false, message })
}
src/common/middleware/[Link]
import jwt from "jsonwebtoken"
import { errorResponse } from "../utils/[Link]"
const authMiddleware = (req, res, next) => {
const token = [Link]?.split(" ")[1]
if (!token) return errorResponse(res, "No token provided", 401)
try {
const decoded = [Link](token, [Link].JWT_SECRET)
[Link] = decoded
next()
} catch (error) {
return errorResponse(res, "Invalid token", 401)
}
}
export default authMiddleware
src/modules/auth/[Link]
import mongoose from "mongoose"
import bcrypt from "bcryptjs"
const userSchema = new [Link]({
name: { type: String, required: true },
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true },
role: { type: String, default: "user" }
}, { timestamps: true })
[Link]("save", async function (next) {
if () return next()
[Link] = await [Link]([Link], 10)
next()
})
const User = [Link]("User", userSchema)
export default User
src/modules/tasks/[Link]
import mongoose from "mongoose"
const taskSchema = new [Link]({
title: { type: String, required: true },
description: { type: String, default: "" },
status: { type: String, enum: ["pending","completed"], default: "pending" },
userId: { type: [Link], ref: "User", required:
true }
}, { timestamps: true })
const Task = [Link]("Task", taskSchema)
export default Task
src/modules/auth/[Link]
import User from "./[Link]"
import bcrypt from "bcryptjs"
import generateToken from "../../common/utils/[Link]"
import { successResponse, errorResponse } from "../../common/utils/[Link]"
export const register = async (req, res) => {
try {
const { name, email, password } = [Link]
if (!name || !email || !password)
return errorResponse(res, "All fields required", 400)
const existing = await [Link]({ email })
if (existing) return errorResponse(res, "Email already exists", 400)
const user = await [Link]({ name, email, password })
const token = generateToken(user._id)
return successResponse(res, { token, user: { name: [Link], email: [Link]
} }, "Registered", 201)
} catch (err) {
return errorResponse(res, [Link])
}
}
export const login = async (req, res) => {
try {
const { email, password } = [Link]
if (!email || !password) return errorResponse(res, "All fields required", 400)
const user = await [Link]({ email })
if (!user) return errorResponse(res, "Invalid credentials", 401)
const isMatch = await [Link](password, [Link])
if (!isMatch) return errorResponse(res, "Invalid credentials", 401)
const token = generateToken(user._id)
return successResponse(res, { token, user: { name: [Link], email: [Link]
} })
} catch (err) {
return errorResponse(res, [Link])
}
}
src/modules/auth/[Link]
import express from "express"
import { register, login } from "./[Link]"
const router = [Link]()
[Link]("/register", register)
[Link]("/login", login)
export default router
src/modules/tasks/[Link]
import Task from "./[Link]"
import { successResponse, errorResponse } from "../../common/utils/[Link]"
export const getTasks = async (req, res) => {
try {
const tasks = await [Link]({ userId: [Link] }).sort({ createdAt: -1 })
return successResponse(res, tasks)
} catch (err) { return errorResponse(res, [Link]) }
}
export const createTask = async (req, res) => {
try {
const { title, description } = [Link]
if (!title) return errorResponse(res, "Title required", 400)
const task = await [Link]({ title, description, userId: [Link] })
return successResponse(res, task, "Task created", 201)
} catch (err) { return errorResponse(res, [Link]) }
}
export const updateTask = async (req, res) => {
try {
const task = await [Link](
{ _id: [Link], userId: [Link] },
[Link], { new: true }
)
if (!task) return errorResponse(res, "Task not found", 404)
return successResponse(res, task)
} catch (err) { return errorResponse(res, [Link]) }
}
export const deleteTask = async (req, res) => {
try {
const task = await [Link]({ _id: [Link], userId:
[Link] })
if (!task) return errorResponse(res, "Task not found", 404)
return successResponse(res, null, "Task deleted")
} catch (err) { return errorResponse(res, [Link]) }
}
export const toggleTask = async (req, res) => {
try {
const task = await [Link]({ _id: [Link], userId: [Link] })
if (!task) return errorResponse(res, "Task not found", 404)
[Link] = [Link] === "pending" ? "completed" : "pending"
await [Link]()
return successResponse(res, task)
} catch (err) { return errorResponse(res, [Link]) }
}
src/modules/tasks/[Link]
import express from "express"
import { getTasks, createTask, updateTask, deleteTask, toggleTask } from
"./[Link]"
import authMiddleware from "../../common/middleware/[Link]"
const router = [Link]()
[Link](authMiddleware)
[Link]("/", getTasks)
[Link]("/", createTask)
[Link]("/:id", updateTask)
[Link]("/:id", deleteTask)
[Link]("/:id", toggleTask)
export default router
PART 4 — FRONTEND CODE FILES
src/utils/[Link]
import axios from "axios"
const axiosInstance = [Link]({
baseURL: [Link].VITE_API_URL || "[Link]
})
[Link]((config) => {
const token = [Link]("token")
if (token) [Link] = `Bearer ${token}`
return config
})
export default axiosInstance
src/services/[Link]
import axiosInstance from "../utils/axiosInstance"
export const registerUser = async (data: { name: string; email: string; password:
string }) => {
const res = await [Link]("/auth/register", data)
return [Link]
}
export const loginUser = async (data: { email: string; password: string }) => {
const res = await [Link]("/auth/login", data)
return [Link]
}
src/services/[Link]
import axiosInstance from "../utils/axiosInstance"
export const getTasks = async () => (await [Link]("/tasks")).data
export const createTask = async (data: { title: string; description?: string }) =>
(await [Link]("/tasks", data)).data
export const updateTask = async (id: string, data: object) =>
(await [Link](`/tasks/${id}`, data)).data
export const deleteTask = async (id: string) =>
(await [Link](`/tasks/${id}`)).data
export const toggleTask = async (id: string) =>
(await [Link](`/tasks/${id}`)).data
src/routes/[Link]
import { Navigate } from "react-router-dom"
const ProtectedRoute = ({ children }: { children: [Link] }) => {
const token = [Link]("token")
return token ? <>{children}</> : <Navigate to="/login" />
}
export default ProtectedRoute
src/[Link]
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"
import Login from "./pages/Login"
import Register from "./pages/Register"
import Dashboard from "./pages/Dashboard"
import ProtectedRoute from "./routes/ProtectedRoute"
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Navigate to="/login" />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/dashboard" element={
<ProtectedRoute><Dashboard /></ProtectedRoute>
} />
</Routes>
</BrowserRouter>
)
}
export default App
frontend/.env
VITE_API_URL=[Link]
PART 5 — RUN THE PROJECT
Terminal 1 — Start Backend
cd Mern_Stack_Project/backend
bun run dev
# Should print:
# MongoDB connected: [Link]
# Server running on [Link]
Terminal 2 — Start Frontend
cd Mern_Stack_Project/frontend
bun run dev
# Open: [Link]
Test APIs in Postman
Method URL Body / Notes
POST /api/auth/register { "name":"Prashant", "email":"p@[Link]",
"password":"123456" }
POST /api/auth/login { "email":"p@[Link]",
"password":"123456" }
GET /api/tasks Header: Authorization: Bearer <token>
POST /api/tasks { "title":"Buy milk", "description":"2
litres" }
PUT /api/tasks/:id { "title":"Updated title" }
DELETE /api/tasks/:id No body needed
PATCH /api/tasks/:id Toggles pending ↔ completed
Prashant — AvQuint Solutions Internship Assignment