Build a Modern TODO App with Next.js, TypeScript, Tailwind CSS & Mongoose (Full CRUD Functionality!) π


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 asrc/
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; // Explicitly define _id
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} // β
Make sure this function is passed
/>
))}
</div>
</div>
);
}
See MongoDB response here -
π¨ Bonus: Enhance Your UI Design with a Free Background Remover
Utilshubβs Background Remover β Make Your UI Assets Stand Out
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! ππ»
Subscribe to my newsletter
Read articles from ajit gupta directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

ajit gupta
ajit gupta
Learning!