Next.js 中使用 MongoDB 完整指南

1. 安装依赖

复制代码
npm install mongodb
# 或者使用 mongoose(ODM)
npm install mongoose

2. 数据库连接配置

使用原生 MongoDB 驱动

创建 lib/mongodb.js 文件:

复制代码
import { MongoClient } from 'mongodb'

const uri = process.env.MONGODB_URI
const options = {}

let client
let clientPromise

if (process.env.NODE_ENV === 'development') {
  // 开发环境中使用全局变量避免重复连接
  if (!global._mongoClientPromise) {
    client = new MongoClient(uri, options)
    global._mongoClientPromise = client.connect()
  }
  clientPromise = global._mongoClientPromise
} else {
  // 生产环境中每次创建新连接
  client = new MongoClient(uri, options)
  clientPromise = client.connect()
}

export default clientPromise

环境变量 .env.local

复制代码
MONGODB_URI=mongodb://localhost:27017/myapp
# 或者 MongoDB Atlas
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/database

3. API 路由中的基本操作

创建基础的数据库操作函数

创建 lib/db.js

复制代码
import clientPromise from './mongodb'

export async function connectToDatabase() {
  const client = await clientPromise
  const db = client.db('myapp') // 数据库名称
  return { client, db }
}

4. 增加数据(Create)

插入单条记录

创建 pages/api/users/create.js

复制代码
import { connectToDatabase } from '../../../lib/db'

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    const { db } = await connectToDatabase()
    const collection = db.collection('users')
    
    // 插入单条记录
    const newUser = {
      name: req.body.name,
      email: req.body.email,
      age: req.body.age,
      createdAt: new Date()
    }
    
    const result = await collection.insertOne(newUser)
    
    res.status(201).json({
      success: true,
      insertedId: result.insertedId,
      data: { ...newUser, _id: result.insertedId }
    })
  } catch (error) {
    res.status(500).json({ success: false, error: error.message })
  }
}

使用示例:

复制代码
// 前端调用
const createUser = async () => {
  const response = await fetch('/api/users/create', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      name: 'John Doe',
      email: 'john@example.com',
      age: 30
    })
  })
  const result = await response.json()
  console.log(result)
}

插入多条记录

创建 pages/api/users/create-many.js

复制代码
import { connectToDatabase } from '../../../lib/db'

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    const { db } = await connectToDatabase()
    const collection = db.collection('users')
    
    // 插入多条记录
    const users = req.body.users.map(user => ({
      ...user,
      createdAt: new Date()
    }))
    
    const result = await collection.insertMany(users)
    
    res.status(201).json({
      success: true,
      insertedCount: result.insertedCount,
      insertedIds: result.insertedIds
    })
  } catch (error) {
    res.status(500).json({ success: false, error: error.message })
  }
}

5. 查询数据(Read)

查询所有记录

创建 pages/api/users/index.js

复制代码
import { connectToDatabase } from '../../../lib/db'

export default async function handler(req, res) {
  if (req.method !== 'GET') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    const { db } = await connectToDatabase()
    const collection = db.collection('users')
    
    // 查询所有记录
    const users = await collection.find({}).toArray()
    
    res.status(200).json({
      success: true,
      count: users.length,
      data: users
    })
  } catch (error) {
    res.status(500).json({ success: false, error: error.message })
  }
}

查询单条记录

创建 pages/api/users/[id].js

复制代码
import { connectToDatabase } from '../../../lib/db'
import { ObjectId } from 'mongodb'

export default async function handler(req, res) {
  const { id } = req.query

  if (req.method === 'GET') {
    try {
      const { db } = await connectToDatabase()
      const collection = db.collection('users')
      
      // 通过 _id 查询单条记录
      const user = await collection.findOne({ _id: new ObjectId(id) })
      
      if (!user) {
        return res.status(404).json({ success: false, message: 'User not found' })
      }
      
      res.status(200).json({
        success: true,
        data: user
      })
    } catch (error) {
      res.status(500).json({ success: false, error: error.message })
    }
  }
}

条件查询

创建 pages/api/users/search.js

复制代码
import { connectToDatabase } from '../../../lib/db'

export default async function handler(req, res) {
  if (req.method !== 'GET') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    const { db } = await connectToDatabase()
    const collection = db.collection('users')
    
    const { name, email, minAge, maxAge, page = 1, limit = 10 } = req.query
    
    // 构建查询条件
    let query = {}
    
    // 模糊查询名称
    if (name) {
      query.name = { $regex: name, $options: 'i' }
    }
    
    // 精确查询邮箱
    if (email) {
      query.email = email
    }
    
    // 年龄范围查询
    if (minAge || maxAge) {
      query.age = {}
      if (minAge) query.age.$gte = parseInt(minAge)
      if (maxAge) query.age.$lte = parseInt(maxAge)
    }
    
    // 分页
    const skip = (parseInt(page) - 1) * parseInt(limit)
    
    // 执行查询
    const users = await collection
      .find(query)
      .sort({ createdAt: -1 }) // 按创建时间倒序
      .skip(skip)
      .limit(parseInt(limit))
      .toArray()
    
    // 获取总数
    const total = await collection.countDocuments(query)
    
    res.status(200).json({
      success: true,
      data: users,
      pagination: {
        page: parseInt(page),
        limit: parseInt(limit),
        total,
        pages: Math.ceil(total / parseInt(limit))
      }
    })
  } catch (error) {
    res.status(500).json({ success: false, error: error.message })
  }
}

查询特定字段

创建 pages/api/users/names.js

复制代码
import { connectToDatabase } from '../../../lib/db'

export default async function handler(req, res) {
  if (req.method !== 'GET') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    const { db } = await connectToDatabase()
    const collection = db.collection('users')
    
    // 只查询 name 和 email 字段,排除 _id
    const users = await collection
      .find({}, { projection: { name: 1, email: 1, _id: 0 } })
      .toArray()
    
    res.status(200).json({
      success: true,
      data: users
    })
  } catch (error) {
    res.status(500).json({ success: false, error: error.message })
  }
}

6. 更新数据(Update)

更新单条记录

pages/api/users/[id].js 中添加 PUT 方法:

复制代码
import { connectToDatabase } from '../../../lib/db'
import { ObjectId } from 'mongodb'

export default async function handler(req, res) {
  const { id } = req.query

  if (req.method === 'PUT') {
    try {
      const { db } = await connectToDatabase()
      const collection = db.collection('users')
      
      const updateData = {
        ...req.body,
        updatedAt: new Date()
      }
      
      // 移除不能更新的字段
      delete updateData._id
      delete updateData.createdAt
      
      // 更新单条记录
      const result = await collection.updateOne(
        { _id: new ObjectId(id) },
        { $set: updateData }
      )
      
      if (result.matchedCount === 0) {
        return res.status(404).json({ success: false, message: 'User not found' })
      }
      
      // 返回更新后的数据
      const updatedUser = await collection.findOne({ _id: new ObjectId(id) })
      
      res.status(200).json({
        success: true,
        modifiedCount: result.modifiedCount,
        data: updatedUser
      })
    } catch (error) {
      res.status(500).json({ success: false, error: error.message })
    }
  }
}

更新多条记录

创建 pages/api/users/update-many.js

复制代码
import { connectToDatabase } from '../../../lib/db'

export default async function handler(req, res) {
  if (req.method !== 'PUT') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    const { db } = await connectToDatabase()
    const collection = db.collection('users')
    
    const { filter, update } = req.body
    
    // 批量更新记录
    const result = await collection.updateMany(
      filter,
      { 
        $set: {
          ...update,
          updatedAt: new Date()
        }
      }
    )
    
    res.status(200).json({
      success: true,
      matchedCount: result.matchedCount,
      modifiedCount: result.modifiedCount
    })
  } catch (error) {
    res.status(500).json({ success: false, error: error.message })
  }
}

使用示例:

复制代码
// 将所有年龄小于18的用户状态设为未成年
const updateMinors = async () => {
  const response = await fetch('/api/users/update-many', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      filter: { age: { $lt: 18 } },
      update: { status: 'minor' }
    })
  })
  const result = await response.json()
  console.log(result)
}

7. 删除数据(Delete)

删除单条记录

pages/api/users/[id].js 中添加 DELETE 方法:

复制代码
export default async function handler(req, res) {
  const { id } = req.query

  if (req.method === 'DELETE') {
    try {
      const { db } = await connectToDatabase()
      const collection = db.collection('users')
      
      // 删除单条记录
      const result = await collection.deleteOne({ _id: new ObjectId(id) })
      
      if (result.deletedCount === 0) {
        return res.status(404).json({ success: false, message: 'User not found' })
      }
      
      res.status(200).json({
        success: true,
        deletedCount: result.deletedCount,
        message: 'User deleted successfully'
      })
    } catch (error) {
      res.status(500).json({ success: false, error: error.message })
    }
  }
}

删除多条记录

创建 pages/api/users/delete-many.js

复制代码
import { connectToDatabase } from '../../../lib/db'

export default async function handler(req, res) {
  if (req.method !== 'DELETE') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    const { db } = await connectToDatabase()
    const collection = db.collection('users')
    
    const { filter } = req.body
    
    // 批量删除记录
    const result = await collection.deleteMany(filter)
    
    res.status(200).json({
      success: true,
      deletedCount: result.deletedCount,
      message: `${result.deletedCount} users deleted successfully`
    })
  } catch (error) {
    res.status(500).json({ success: false, error: error.message })
  }
}

8. 高级查询操作

聚合查询

创建 pages/api/users/statistics.js

复制代码
import { connectToDatabase } from '../../../lib/db'

export default async function handler(req, res) {
  if (req.method !== 'GET') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    const { db } = await connectToDatabase()
    const collection = db.collection('users')
    
    // 聚合查询示例
    const statistics = await collection.aggregate([
      {
        $group: {
          _id: null,
          totalUsers: { $sum: 1 },
          averageAge: { $avg: '$age' },
          minAge: { $min: '$age' },
          maxAge: { $max: '$age' }
        }
      }
    ]).toArray()
    
    // 按年龄分组统计
    const ageGroups = await collection.aggregate([
      {
        $group: {
          _id: {
            $switch: {
              branches: [
                { case: { $lt: ['$age', 18] }, then: 'minor' },
                { case: { $lt: ['$age', 65] }, then: 'adult' },
                { case: { $gte: ['$age', 65] }, then: 'senior' }
              ],
              default: 'unknown'
            }
          },
          count: { $sum: 1 }
        }
      }
    ]).toArray()
    
    res.status(200).json({
      success: true,
      data: {
        statistics: statistics[0],
        ageGroups
      }
    })
  } catch (error) {
    res.status(500).json({ success: false, error: error.message })
  }
}

文本搜索

创建 pages/api/users/text-search.js

复制代码
import { connectToDatabase } from '../../../lib/db'

export default async function handler(req, res) {
  if (req.method !== 'GET') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    const { db } = await connectToDatabase()
    const collection = db.collection('users')
    
    const { q } = req.query // 搜索关键词
    
    if (!q) {
      return res.status(400).json({ success: false, message: 'Search query required' })
    }
    
    // 首先需要创建文本索引
    // await collection.createIndex({ name: 'text', email: 'text' })
    
    // 执行文本搜索
    const users = await collection
      .find({ $text: { $search: q } })
      .sort({ score: { $meta: 'textScore' } })
      .toArray()
    
    res.status(200).json({
      success: true,
      query: q,
      count: users.length,
      data: users
    })
  } catch (error) {
    res.status(500).json({ success: false, error: error.message })
  }
}

9. 在组件中使用

React 组件示例

复制代码
// components/UserList.js
import { useState, useEffect } from 'react'

export default function UserList() {
  const [users, setUsers] = useState([])
  const [loading, setLoading] = useState(true)
  const [newUser, setNewUser] = useState({ name: '', email: '', age: '' })

  // 获取用户列表
  const fetchUsers = async () => {
    try {
      const response = await fetch('/api/users')
      const result = await response.json()
      if (result.success) {
        setUsers(result.data)
      }
    } catch (error) {
      console.error('Error fetching users:', error)
    } finally {
      setLoading(false)
    }
  }

  // 创建用户
  const createUser = async (e) => {
    e.preventDefault()
    try {
      const response = await fetch('/api/users/create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newUser)
      })
      const result = await response.json()
      if (result.success) {
        setUsers([...users, result.data])
        setNewUser({ name: '', email: '', age: '' })
      }
    } catch (error) {
      console.error('Error creating user:', error)
    }
  }

  // 删除用户
  const deleteUser = async (id) => {
    if (!confirm('确定要删除这个用户吗?')) return
    
    try {
      const response = await fetch(`/api/users/${id}`, {
        method: 'DELETE'
      })
      const result = await response.json()
      if (result.success) {
        setUsers(users.filter(user => user._id !== id))
      }
    } catch (error) {
      console.error('Error deleting user:', error)
    }
  }

  useEffect(() => {
    fetchUsers()
  }, [])

  if (loading) return <div>Loading...</div>

  return (
    <div>
      <h2>User Management</h2>
      
      {/* 创建用户表单 */}
      <form onSubmit={createUser}>
        <input
          type="text"
          placeholder="Name"
          value={newUser.name}
          onChange={(e) => setNewUser({...newUser, name: e.target.value})}
          required
        />
        <input
          type="email"
          placeholder="Email"
          value={newUser.email}
          onChange={(e) => setNewUser({...newUser, email: e.target.value})}
          required
        />
        <input
          type="number"
          placeholder="Age"
          value={newUser.age}
          onChange={(e) => setNewUser({...newUser, age: parseInt(e.target.value)})}
          required
        />
        <button type="submit">Create User</button>
      </form>

      {/* 用户列表 */}
      <div>
        {users.map(user => (
          <div key={user._id} style={{ border: '1px solid #ccc', margin: '10px', padding: '10px' }}>
            <h3>{user.name}</h3>
            <p>Email: {user.email}</p>
            <p>Age: {user.age}</p>
            <button onClick={() => deleteUser(user._id)}>Delete</button>
          </div>
        ))}
      </div>
    </div>
  )
}

10. 错误处理和最佳实践

统一错误处理

创建 lib/errorHandler.js

复制代码
export function handleApiError(error, res) {
  console.error('API Error:', error)
  
  if (error.name === 'MongoServerError') {
    if (error.code === 11000) {
      // 重复键错误
      return res.status(400).json({
        success: false,
        error: 'Duplicate key error',
        details: error.keyValue
      })
    }
  }
  
  if (error.name === 'CastError') {
    // ObjectId 格式错误
    return res.status(400).json({
      success: false,
      error: 'Invalid ID format'
    })
  }
  
  // 通用错误
  return res.status(500).json({
    success: false,
    error: 'Internal server error'
  })
}

数据验证

创建 lib/validation.js

复制代码
export function validateUser(userData) {
  const errors = []
  
  if (!userData.name || userData.name.trim().length < 2) {
    errors.push('Name must be at least 2 characters long')
  }
  
  if (!userData.email || !isValidEmail(userData.email)) {
    errors.push('Valid email is required')
  }
  
  if (!userData.age || userData.age < 0 || userData.age > 150) {
    errors.push('Age must be between 0 and 150')
  }
  
  return {
    isValid: errors.length === 0,
    errors
  }
}

function isValidEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  return emailRegex.test(email)
}

这个完整指南涵盖了 Next.js 中使用 MongoDB 的所有基本操作,包括:

  1. 数据库连接配置 - 如何正确配置和管理数据库连接
  2. 增删改查操作 - 完整的 CRUD 操作示例
  3. 条件查询 - 各种查询条件和过滤器的使用
  4. 字段选择 - 如何只查询需要的字段
  5. 分页和排序 - 处理大量数据的最佳实践
  6. 聚合查询 - 复杂的数据统计和分析
  7. 错误处理 - 统一的错误处理机制
  8. 前端集成 - 在 React 组件中如何使用这些 API

每个示例都包含了详细的注释说明,可以根据实际需求进行修改和扩展。

相关推荐
程序员编程指南16 分钟前
Qt 开发自动化测试框架搭建
c语言·开发语言·c++·qt
三小尛27 分钟前
C++赋值运算符重载
开发语言·c++
籍籍川草30 分钟前
JVM指针压缩的那些事
java·开发语言·jvm
小徐不徐说38 分钟前
C++ 模板与 STL 基础入门:从泛型编程到实战工具集
开发语言·数据结构·c++·qt·面试
艾莉丝努力练剑39 分钟前
【C/C++】类和对象(上):(一)类和结构体,命名规范——两大规范,新的作用域——类域
java·c语言·开发语言·c++·学习·算法
froginwe111 小时前
WebPages PHP:深入解析PHP在网页开发中的应用
开发语言
R-G-B2 小时前
【33】C# WinForm入门到精通 ——表格布局器TableLayoutPanel【属性、方法、事件、实例、源码】
开发语言·c#·c# winform·表格布局器·tablelayoutpane
cos2 小时前
FE Bits 前端周周谈 Vol.1|Hello World、TanStack DB 首个 Beta 版发布
前端·javascript·css
郝学胜-神的一滴2 小时前
Spring Boot Actuator 保姆级教程
java·开发语言·spring boot·后端·程序人生
赵英英俊2 小时前
Python day31
开发语言·python