如何在Next.js 13(App Router)中集成MongoDB社区服务器?
Next.js 13 App目录集成MongoDB社区服务器搭建Todo应用
一、修复Mongoose连接问题
1. 安装依赖
先确保安装了必要的包:
npm install mongoose dotenv
2. 配置环境变量
在项目根目录新建.env.local文件,写入MongoDB连接字符串:
MONGODB_URI="mongodb://localhost:27017/next-todo" # 若用远程MongoDB Atlas,替换为对应连接串
3. 重构数据库连接逻辑
修改services/mongodb.js(或你现有的连接文件),适配Next.js的服务器端运行逻辑:
import mongoose from 'mongoose'; const connectDB = async () => { // 避免重复连接 if (mongoose.connection.readyState === 1) { return mongoose.connection; } try { await mongoose.connect(process.env.MONGODB_URI); console.log('MongoDB连接成功'); return mongoose.connection; } catch (error) { console.error('MongoDB连接失败:', error); throw new Error('数据库连接失败'); } }; export default connectDB;
注意:Next.js 13 App目录中,数据库连接只能在服务器组件或API路由中调用,不能在客户端组件直接执行。
二、创建Todo数据模型
在models/Todo.js定义数据结构:
import mongoose from 'mongoose'; const todoSchema = new mongoose.Schema({ title: { type: String, required: true, trim: true }, completed: { type: Boolean, default: false }, createdAt: { type: Date, default: Date.now } }); // 避免重复注册模型 export default mongoose.models.Todo || mongoose.model('Todo', todoSchema);
三、编写CRUD API路由
在app/api/todos/route.js实现接口:
import connectDB from '@/services/mongodb'; import Todo from '@/models/Todo'; import { NextResponse } from 'next/server'; // 获取所有Todo export async function GET() { await connectDB(); const todos = await Todo.find().sort({ createdAt: -1 }); return NextResponse.json(todos); } // 创建Todo export async function POST(request) { await connectDB(); const { title } = await request.json(); if (!title) { return NextResponse.json({ error: '标题不能为空' }, { status: 400 }); } const todo = await Todo.create({ title }); return NextResponse.json(todo, { status: 201 }); } // 更新Todo状态 export async function PUT(request) { await connectDB(); const { id, completed } = await request.json(); const updatedTodo = await Todo.findByIdAndUpdate(id, { completed }, { new: true }); if (!updatedTodo) { return NextResponse.json({ error: 'Todo不存在' }, { status: 404 }); } return NextResponse.json(updatedTodo); } // 删除Todo export async function DELETE(request) { await connectDB(); const { id } = await request.json(); const deletedTodo = await Todo.findByIdAndDelete(id); if (!deletedTodo) { return NextResponse.json({ error: 'Todo不存在' }, { status: 404 }); } return NextResponse.json({ message: '删除成功' }); }
四、服务器组件渲染Todo列表
修改app/page.js为服务器组件,获取并渲染数据:
import TodoList from '@/components/TodoList'; import connectDB from '@/services/mongodb'; import Todo from '@/models/Todo'; async function getTodos() { await connectDB(); return Todo.find().sort({ createdAt: -1 }); } export default async function Home() { const todos = await getTodos(); return ( <div className="container mx-auto p-4"> <h1 className="text-2xl font-bold mb-4">Todo应用</h1> <TodoList todos={todos} /> </div> ); }
五、编写客户端交互组件
创建components/TodoList.js客户端组件,处理用户操作:
'use client'; import { useState } from 'react'; export default function TodoList({ todos }) { const [title, setTitle] = useState(''); const [currentTodos, setCurrentTodos] = useState(todos); // 添加Todo const handleAddTodo = async (e) => { e.preventDefault(); if (!title.trim()) return; const res = await fetch('/api/todos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) }); const newTodo = await res.json(); setCurrentTodos([newTodo, ...currentTodos]); setTitle(''); }; // 切换完成状态 const handleToggleComplete = async (id, currentStatus) => { const res = await fetch('/api/todos', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, completed: !currentStatus }) }); const updatedTodo = await res.json(); setCurrentTodos(currentTodos.map(todo => todo._id === id ? updatedTodo : todo )); }; // 删除Todo const handleDeleteTodo = async (id) => { await fetch('/api/todos', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }) }); setCurrentTodos(currentTodos.filter(todo => todo._id !== id)); }; return ( <div> <form onSubmit={handleAddTodo} className="mb-4"> <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="添加新Todo..." className="px-4 py-2 border rounded-l-md" /> <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded-r-md"> 添加 </button> </form> <ul> {currentTodos.map(todo => ( <li key={todo._id} className="flex items-center justify-between p-2 border-b"> <span className={`flex-1 ${todo.completed ? 'line-through text-gray-500' : ''}`}> {todo.title} </span> <button onClick={() => handleToggleComplete(todo._id, todo.completed)} className="px-2 py-1 mr-2 bg-green-500 text-white rounded" > {todo.completed ? '标记未完成' : '标记完成'} </button> <button onClick={() => handleDeleteTodo(todo._id)} className="px-2 py-1 bg-red-500 text-white rounded" > 删除 </button> </li> ))} </ul> </div> ); }
常见问题排查
- 确认MongoDB社区服务器已启动,本地连接串的端口(默认27017)和数据库名正确。
- 检查
.env.local文件是否在项目根目录,变量名MONGODB_URI拼写无误。 - 服务器组件中调用连接函数时,已做重复连接判断,避免资源浪费。
- API路由必须用
async/await处理异步操作,确保数据库连接完成后再执行查询。
内容的提问来源于stack exchange,提问作者Kunal Agrawal
相关产品推荐
相关产品推荐

