You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 01:09:59