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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741346670528/27580a43-e1ea-43ae-9010-c8def73f5d08.png align="center")

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:

```bash
create-next-app@latest todo-app
cd todo-app
```

When you run:

```bash
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:

1. **Would you like to use TypeScript?** (Yes/No)  
    → Select **Yes** if you want TypeScript support.
    
2. **Would you like to use ESLint?** (Yes/No)  
    → Select **Yes** to enable ESLint for code linting and best practices.
    
3. **Would you like to use Tailwind CSS?** (Yes/No)  
    → Select **Yes** if you want Tailwind CSS for styling.
    
4. **Would you like to use** `src/` directory? (Yes/No)  
    → Selecting **Yes** places your code inside a `src/` folder, which helps with project organization.
    
5. **Would you like to use experimental** `app/` directory? (Yes/No)  
    → Selecting **Yes** enables the new Next.js App Router (recommended for new projects).
    
6. **What import alias would you like configured?** (`@/*` by default)  
    → You can keep the default `@/*` or customize it for better import management.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741160181114/e95c3946-bb5e-4eb8-b92a-b2123c5a0c78.png align="center")

## Step 3: Set Up MongoDB with Mongoose

#### **Using MongoDB Atlas** [**(Cloud)**](https://www.mongodb.com/atlas)

1. Go to **MongoDB** Atlas and create a free account.
    
2. Create a new **cluster**.
    
3. Click **Connect** → **Drivers** → Select **Node.js** version 4.0+.
    
4. Copy the **MongoDB connection URI**, which looks like this:
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741160919233/9e455101-8640-4802-a652-082c5681cceb.png align="center")
    

Create a `.env.local` file and add your MongoDB connection string:

```bash
MONGODB_URI=mongodb+srv://<your-username>:<your-password>@cluster0.mongodb.net/
```

Once inside the project, install dependencies:

```bash
npm install mongoose
```

📌 **Bonus:** Looking for a quick and efficient **online background remover**? Try [**Utilshub Background Remover**](https://www.utilshub.com/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`:

```typescript
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:

```typescript
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`:

```typescript
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`:

```typescript
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`:

```typescript
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`)

```typescript
"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>
  );
}
```

---

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741348686815/8bb32220-c45f-43fd-828f-41636f22404e.png align="center")

See MongoDB response here -

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741348821653/8a4300b0-8166-4b54-afa3-5aaefc526cf2.png align="center")

## **🎨 Bonus: Enhance Your UI Design with a Free Background Remover**

### [**Utilshub’s Background Remover**](https://www.utilshub.com/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! 🚀💻
