mongodb

How to Create a Next.js MongoDB Todo Project: A Complete Guide

Introduction In today’s fast-paced development world, building a full-stack app doesn’t have to be complicated. With the power of Next.js and MongoDB, developers can quickly create scalable applications. In this tutorial, you will learn how to build a simple, full-stack To-Do App that leverages the App Router in Next.js and MongoDB for data persistence. Moreover, this app will be built using pure JavaScript (no TypeScript), making it perfect for beginners. By the end of this guide, you’ll understand how to set up a database, connect it using Mongoose, and build a frontend that interacts seamlessly with your backend. Prerequisites Before we begin, make sure you have: Project Overview We’ll build a simple but powerful To-Do App that includes: Choose: I have a Node.js version v22.17.0 npm Version 10.3.0 mongoose version v7.0.11 Step 1: Setting Up the Next.js Project First, let’s create a new Next.js project with JavaScript support: npx create-next-app@latest todo-project cd todo-project While running this command, it will ask you some questions about the dependency so that you can choose the option “No” for TypeScript and TailwindCSS Install additional dependencies we’ll need: Step 2: Setting Up MongoDB Connection Install MongoDB Community Edition locally and run the MongoDB service. Option A: MongoDB Atlas (Cloud) Option B: Local MongoDB Install MongoDB Community Edition locally and run the MongoDB service. Creating the Database Connection Utility Create a new file lib/mongodb.js: npm install mongoose I have created lib/mongob.js inside the src folder, and now paste this code import mongoose from ‘mongoose’ const MONGODB_URI = process.env.MONGODB_URI if (!MONGODB_URI) throw new Error(‘MONGODB_URI not defined in .env.local’) let cached = global.mongoose || { conn: null, promise: null } export async function connectDB() { if (cached.conn) return cached.conn if (!cached.promise) { cached.promise = mongoose.connect(MONGODB_URI, { dbName: ‘todo-app’, bufferCommands: false, }).then((mongoose) => { return mongoose }) } cached.conn = await cached.promise return cached.conn } Create a .env.local file in your src folder: MONGODB_URI=mongodb://<username>:<password>@localhost:27017/nextjsdb?authSource=admin Step 3: Creating the Todo Model Create a new directory models Inside the src and add Todo.js: import mongoose from ‘mongoose’ const TodoSchema = new mongoose.Schema({ text: { type: String, required: true }, completed: { type: Boolean, default: false } }, { timestamps: true }) export default mongoose.models.Todo || mongoose.model(‘Todo’, TodoSchema) Step 4: Creating API Routes For GET and POST requests, we use a single route.js file. For DELETE and PUT requests (which require dynamic parameters like an id We create a separate folder structure. This is because Next.js follows a file-based routing system, and each endpoint must have its own file or dynamic folder to handle different HTTP methods and routes correctly. GET & POST → /app/api/todos/route.js import { connectDB } from ‘@/lib/mongodb’ import Todo from ‘@/models/Todo’ export async function GET() { await connectDB() const todos = await Todo.find().sort({ createdAt: -1 }) return Response.json(todos) } export async function POST(req) { const { text } = await req.json() await connectDB() const newTodo = await Todo.create({ text }) return Response.json(newTodo) } Create the Folder path below. I have added PUT & DELETE → /app/api/todos/[id]/route.js Note: This would be a folder name [id] Do not confuse. import { connectDB } from ‘@/lib/mongodb’ import Todo from ‘@/models/Todo’ export async function PUT(req, { params }) { const { id } = params const { completed } = await req.json() await connectDB() const updated = await Todo.findByIdAndUpdate(id, { completed }, { new: true }) return Response.json(updated) } export async function DELETE(req, { params }) { const { id } = params await connectDB() await Todo.findByIdAndDelete(id) return Response.json({ message: ‘Deleted’ }) } Step 6: Build the UI — /app/page.js Now that our API is ready, let’s shift our focus to the front end. To begin with, we’ll build the user interface using React (via Next.js). This will include a task input field, a task list, and buttons to complete or delete a task. First, we define two state variables: task to hold the current input, and todos to store the fetched task list. After the component mounts, we use useEffect to fetch tasks from the API and display them on the screen. When a user adds a task, it is sent to the backend via a POST request. Then, the new task is added to the state and shown immediately in the list. In contrast, when a task is toggled or deleted, we use PUT and DELETE requests to update the backend accordingly. As a result, the interface remains synced with the database in real time. The file /app/page.js is the main UI page of your application — it’s where users: ‘use client’ What’s Happening in /app/page.js: This line tells Next.js that this file uses client-side rendering (since we use React hooks like useState and useEffect). React State const [task, setTask] = useState(”) const [todos, setTodos] = useState([]) Add a New Task const addTodo = async () => { if (!task.trim()) return const res = await fetch(‘/api/todos’, { method: ‘POST’, body: JSON.stringify({ text: task }), }) const newTodo = await res.json() setTodos([newTodo, …todos]) setTask(”) } Full code ‘use client’import { useEffect, useState } from ‘react’export default function Home() { const [task, setTask] = useState(”) const [todos, setTodos] = useState([]) useEffect(() => { fetchTodos() }, []) const fetchTodos = async () => { const res = await fetch(‘/api/todos’) const data = await res.json() setTodos(data) } const addTodo = async () => { if (!task.trim()) return const res = await fetch(‘/api/todos’, { method: ‘POST’, body: JSON.stringify({ text: task }), }) const newTodo = await res.json() setTodos([newTodo, …todos]) setTask(”) } const toggleComplete = async (id, completed) => { const res = await fetch(`/api/todos/${id}`, { method: ‘PUT’, body: JSON.stringify({ completed: !completed }) }) const updated = await res.json() setTodos(todos.map(todo => todo._id === id ? updated : todo)) } const deleteTodo = async (id) => { await fetch(`/api/todos/${id}`, { method: ‘DELETE’ }) setTodos(todos.filter(todo => todo._id !== id)) } return ( <main className=”min-h-screen p-6 bg-gray-100 flex flex-col items-center”> <h1 className=”text-2xl font-bold mb-4″>To-Do App</h1> <div className=”flex gap-2 mb-4″> <input type=”text” value={task} onChange={(e) => setTask(e.target.value)} placeholder=”Enter task…” className=”border p-2 rounded w-64″ /> <button onClick={addTodo} className=”bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600″ > Add </button> </div>

How to Create a Next.js MongoDB Todo Project: A Complete Guide Read More »

connecting nextjs project with mongodb blog post

How to Connect Next.js with MongoDB

MongoDB is a powerful NoSQL database that pairs perfectly with Next.js for full-stack applications. In this guide, you’ll learn how to connect Next.js to MongoDB (locally or with MongoDB Atlas) using Mongoose, and how to build simple API routes to insert and retrieve data. Prerequisites Before you begin, ensure you have the following installed: Create a Next.js app if needed: npx create-next-app@latest next-mongo-app cd next-mongo-app Although there is a small code change if you want to use TypeScript, I suggest using JavaScript for learning purposes. Step 1: Install Mongoose npm install mongoose Set the MongoDB URI in the .env.local file in your root directory MONGODB_URI=mongodb://<username>:<password>@localhost:27017/<databaseName>?authSource=admin //example MONGODB_URI=mongodb://admin:12345@localhost:27017/nextjsdb?authSource=admin Step 2: Set Up MongoDB Connection Helper Create a folder name lib and a file lib/mongodb.js: Make sure you are connected to the MongoDB database // lib/mongodb.js import mongoose from ‘mongoose’; const MONGODB_URI = process.env.MONGODB_URI; if (!MONGODB_URI) { throw new Error(‘Please define the MONGODB_URI environment variable’); } let cached = global.mongoose; if (!cached) { cached = global.mongoose = { conn: null, promise: null }; } export async function connectToDatabase() { if (cached.conn) return cached.conn; if (!cached.promise) { cached.promise = mongoose.connect(MONGODB_URI, { bufferCommands: false, useNewUrlParser: true, useUnifiedTopology: true, }).then((mongoose) => mongoose); } cached.conn = await cached.promise; return cached.conn; } Step 3: Define a Mongoose Model Create a folder models and a file models/Post.js // models/Post.js import mongoose from ‘mongoose’; const PostSchema = new mongoose.Schema({ title: String, content: String, }, { timestamps: true }); export default mongoose.models.Post || mongoose.model(‘Post’, PostSchema); Step 4: Create an API Route Create pages/api/posts.js: // pages/api/posts.js import { connectToDatabase } from ‘../../../lib/mongodb’; import Post from ‘../../../models/Post’; export default async function handler(req, res) { await connectToDatabase(); if (req.method === ‘GET’) { const posts = await Post.find({}); return res.status(200).json(posts); } if (req.method === ‘POST’) { const post = await Post.create(req.body); return res.status(201).json(post); } return res.status(405).json({ message: ‘Method not allowed’ }); } Step 5: Test with a Frontend Form Update pages/index.js With a simple form: This will show in the home URL / in the browser a simple form for inserting data into the database // pages/index.js or any component ‘use client’; // if using App Router import { useState } from ‘react’; export default function Home() { const [title, setTitle] = useState(”); const [content, setContent] = useState(”); async function handleSubmit(e) { e.preventDefault(); const res = await fetch(‘/api/posts’, { method: ‘POST’, headers: { ‘Content-Type’: ‘application/json’ }, body: JSON.stringify({ title, content }) }); const data = await res.json(); console.log(data); // Clear form after submit setTitle(”); setContent(”); } return ( <div style={{ maxWidth: 500, margin: ‘0 auto’ }}> <h1>Create Post</h1> <form onSubmit={handleSubmit}> <div> <label>Title:</label> <input type=”text” value={title} onChange={(e) => setTitle(e.target.value)} required style={{ width: ‘100%’, padding: ‘8px’, marginBottom: ’10px’ }} /> </div> <div> <label>Content:</label> <textarea value={content} onChange={(e) => setContent(e.target.value)} required rows={5} style={{ width: ‘100%’, padding: ‘8px’, marginBottom: ’10px’ }} ></textarea> </div> <button type=”submit”>Submit</button> </form> </div> ); } Folder Structure Overview Your folder structure should look the same. I have created: myproject/ ├── lib/ │ └── mongodb.js ├── models/ │ └── Post.js ├── pages/ │ ├── api/ │ │ └── posts.js │ └── index.js ├── .env.local └── … Api output should look like this : Comment below, let me know how you start your next JS journey

How to Connect Next.js with MongoDB Read More »

Scroll to Top