
In this guide, you'll learn how to build a TODO app using modern technologies:
Next.js for the frontend and backend
Mongoose to interact with MongoDB
Tailwind CSS for styling
TypeScript for type safety
Prerequisites
Before we begin, ensure you have:
Node.js installed (v16 or later)
A MongoDB database (local or cloud via MongoDB Atlas)
Basic knowledge of Next.js, TypeScript, and Tailwind CSS
Step 1: Set Up a Next.js Project
Run the following command to create a Next.js app with TypeScript:
create-next-app@latest todo-app
cd todo-app
When you run:
npx create-next-app@latest todo-app
It will prompt you with several options to configure your Next.js project. Here are the typical questions it asks:
Would you like to use TypeScript? (Yes/No)
โ Select Yes if you want TypeScript support.
Would you like to use ESLint? (Yes/No)
โ Select Yes to enable ESLint for code linting and best practices.
Would you like to use Tailwind CSS? (Yes/No)
โ Select Yes if you want Tailwind CSS for styling.
Would you like to use src/ directory? (Yes/No)
โ Selecting Yes places your code inside a src/ folder, which helps with project organization.
Would you like to use experimental app/ directory? (Yes/No)
โ Selecting Yes enables the new Next.js App Router (recommended for new projects).
What import alias would you like configured? (@/* by default)
โ You can keep the default @/* or customize it for better import management.

Step 3: Set Up MongoDB with Mongoose
Using MongoDB Atlas (Cloud)
Go to MongoDB Atlas and create a free account.
Create a new cluster.
Click Connect โ Drivers โ Select Node.js version 4.0+.
Copy the MongoDB connection URI, which looks like this:

Create a .env.local file and add your MongoDB connection string:
MONGODB_URI=mongodb+srv://<your-username>:<your-password>@cluster0.mongodb.net/
Once inside the project, install dependencies:
npm install mongoose
๐ Bonus: Looking for a quick and efficient online background remover? Try Utilshub Background Remover โ an AI-powered tool that instantly removes backgrounds from images! ๐จโจ
Now, create a database connection helper:
Create a new folder lib/ and a file lib/mongodb.ts:
import mongoose from "mongoose";
const MONGODB_URI = process.env.MONGODB_URI as string;
if (!MONGODB_URI) {
throw new Error("Please define MONGODB_URI in .env.local");
}
let cached = (global as any).mongoose || { conn: null, promise: null };
async function dbConnect() {
if (cached.conn) return cached.conn;
if (!cached.promise) {
cached.promise = mongoose
.connect(MONGODB_URI, {
dbName: "todoApp",
bufferCommands: false
})
.then((mongoose) => mongoose);
}
cached.conn = await cached.promise;
return cached.conn;
}
export default dbConnect;
Step 4: Define the TODO Model
Create a models/Todo.ts file:
import mongoose, { Schema, Document, model, models } from "mongoose";
export interface ITodo extends Document {
_id: string;
title: string;
completed: boolean;
}
const TodoSchema = new Schema<ITodo>({
title: { type: String, required: true },
completed: { type: Boolean, default: false }
});
export default models.Todo || model<ITodo>("Todo", TodoSchema);
Step 5: Create API Routes for CRUD Operations
Get all todos
Create /api/todos/route.ts:
import dbConnect from "@/app/lib/mongodb";
import Todo from "@/app/models/Todo";
import { NextResponse } from "next/server";
export async function GET() {
await dbConnect();
const todos = await Todo.find({});
return NextResponse.json(todos);
}
export async function POST(req: Request) {
await dbConnect();
const { title } = await req.json();
const newTodo = await Todo.create({ title });
return NextResponse.json(newTodo, { status: 201 });
}
Update and delete todos
Create pages/api/todos/[id]/route.ts:
import dbConnect from "@/app/lib/mongodb";
import Todo from "@/app/models/Todo";
import { NextResponse } from "next/server";
export async function PUT(
req: Request,
{ params }: { params: { id: string } }
) {
await dbConnect();
if (!params.id) {
return NextResponse.json({ error: "Todo ID is required" }, { status: 400 });
}
try {
const updatedTodo = await Todo.findByIdAndUpdate(
params.id,
await req.json(),
{ new: true }
);
if (!updatedTodo) {
return NextResponse.json({ error: "Todo not found" }, { status: 404 });
}
return NextResponse.json(updatedTodo);
} catch (error) {
return NextResponse.json(
{ error: "Failed to update todo" },
{ status: 500 }
);
}
}
export async function DELETE(
req: Request,
{ params }: { params: { id: string } }
) {
await dbConnect();
if (!params.id) {
return NextResponse.json({ error: "Todo ID is required" }, { status: 400 });
}
try {
const deletedTodo = await Todo.findByIdAndDelete(params.id);
if (!deletedTodo) {
return NextResponse.json({ error: "Todo not found" }, { status: 404 });
}
return NextResponse.json(
{ message: "Todo deleted successfully" },
{ status: 200 }
);
} catch (error) {
return NextResponse.json(
{ error: "Failed to delete todo" },
{ status: 500 }
);
}
}
Step 6: Build the Frontend with React & Tailwind CSS
Create the Todo Component
Create components/TodoItem.tsx:
import { useState } from "react";
type TodoProps = {
id: string;
title: string;
completed: boolean;
refreshTodos: () => void;
};
export default function TodoItem({
id,
title,
completed,
refreshTodos
}: TodoProps) {
const [loading, setLoading] = useState(false);
const toggleCompletion = async () => {
setLoading(true);
await fetch(`/api/todos/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ completed: !completed })
});
refreshTodos();
};
const deleteTodo = async () => {
setLoading(true);
await fetch(`/api/todos/${id}`, { method: "DELETE" });
refreshTodos();
};
return (
<div className="flex items-center justify-between p-3 border rounded-lg">
<span className={`${completed ? "line-through text-gray-500" : ""}`}>
{title}
</span>
<div className="flex gap-2">
<button onClick={toggleCompletion} className="text-green-500">
{completed ? "Undo" : "Done"}
</button>
<button onClick={deleteTodo} className="text-red-500">
Delete
</button>
</div>
</div>
);
}
Build the Main Page Home Component
Build the Homepage (Homepage.tsx)
"use client";
import { useState, useEffect } from "react";
import TodoItem from "./TodoItem";
export default function Homepage() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState("");
const fetchTodos = async () => {
const res = await fetch("/api/todos");
const data = await res.json();
setTodos(data);
};
useEffect(() => {
fetchTodos();
}, []);
const addTodo = async () => {
if (!newTodo) return;
await fetch("/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: newTodo })
});
setNewTodo("");
fetchTodos();
};
return (
<div className="max-w-xl mx-auto mt-10 p-5 bg-white shadow-lg rounded-lg">
<h1 className="text-2xl font-bold mb-4">TODO App</h1>
<div className="flex gap-2 mb-4">
<input
type="text"
className="border p-2 flex-1 rounded-lg"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="New task..."
/>
<button
onClick={addTodo}
className="bg-blue-500 text-white p-2 rounded-lg"
>
Add
</button>
</div>
<div className="space-y-2">
{todos.map((todo: any) => (
<TodoItem
key={todo._id}
id={todo._id}
title={todo.title}
completed={todo.completed}
refreshTodos={fetchTodos}
/>
))}
</div>
</div>
);
}

See MongoDB response here -

๐จ Bonus: Enhance Your UI Design with a Free Background Remover
When designing websites or UI components, high-quality images play a crucial role. However, many designers struggle with removing backgrounds to create clean, professional visuals.
That's where Utilshubโs AI-Powered Background Remover comes in! With just one click, you can:
โ Remove backgrounds from images instantly
โ Make UI components look sleek and professional
โ Create transparent assets for web design
โ Save time on manual editing
๐ก Perfect for UI/UX designers, developers, and content creators!
๐ Conclusion
๐ Congratulations! Youโve built a full-stack TODO app with Next.js, TypeScript, Tailwind CSS, and MongoDB. Try deploying it on Vercel or Railway!
Let me know if you need more improvements! ๐
๐ Have questions or feedback? Drop them in the comments!
๐ก Liked this guide? Share it with others! ๐
Happy coding! ๐๐ป