第22章 Next.js + MongoDB 入门

学会用 MongoDB 做后端数据库,用 Next.js API Route 读写数据

作者:zyyc-0336  |  日期:2026-06-12

📋 学习路径建议
  1. 了解数据库是什么
  2. 注册 MongoDB Atlas(免费云数据库)
  3. 安装 mongoose,学会连接数据库
  4. 定义数据模型(Schema & Model)
  5. 在 API Route 中读写 MongoDB
  6. 前端页面调用 API 展示数据
⚠️ 常见错误提示
错误现象原因解决方案
MongooseError: buffering timed out连接字符串写错或网络不通检查 .env.local 中的 MONGODB_URI,确认 IP 白名单
MongoServerError: bad auth用户名或密码错误在 Atlas 面板重新设置数据库用户密码
ECONNREFUSED忘记启动 MongoDB 或连接地址错误Atlas 用户不需要本地启动,检查连接字符串
ValidationError: Path 'xxx' is required保存数据时缺少必填字段检查 Schema 中的 required 设置
数据查出来是空数组 []数据库中确实没有数据先用代码插入几条测试数据
.env.local 不生效文件名写错或忘记重启确认文件名是 .env.local,修改后重启 npm run dev

22.1 数据库是什么?为什么需要它?

22.1.1 大白话解释

想象你开了一家奶茶店:

类比对应技术说明
奶茶店的账本数据库存放所有数据的地方
账本里的一页纸集合(Collection)一组同类数据,比如「订单」
账本上的一行记录文档(Document)一条具体数据,比如「张三点了一杯珍珠奶茶」
写账本的人后端代码(API Route)负责读写数据库

没有数据库会怎样?

每次重启服务器 → 所有数据丢失! 用户A写的数据 → 用户B看不到! 数据存在代码里 → 一改代码数据就没了!

有了数据库:

数据永久保存在云端 → 重启也不丢 所有用户共享数据 → 你写我能看 数据和代码分离 → 改代码不影响数据

22.1.2 为什么选 MongoDB?

对比项MongoDBMySQL
数据格式JSON 风格(灵活)表格(固定列)
学习难度⭐ 简单⭐⭐ 中等
免费方案Atlas 免费 512MB需自建或购买
和 Next.js 配合天然契合(都是 JSON)需要额外转换
适合场景快速开发、原型、中小型项目大型企业、复杂关系
💡 结论:零基础学 MongoDB 最省心,数据格式就是 JSON,和前端无缝对接。

22.1.3 MongoDB 核心概念

MongoDB 数据库 ├── 数据库(Database) ← 一个项目一个库 │ ├── 集合(Collection) ← 一组同类数据,类似"表" │ │ ├── 文档(Document) ← 一条数据,就是 JSON 对象 │ │ ├── 文档(Document) │ │ └── ... │ └── 集合(Collection) └── ...

一条文档长这样:

{
  "_id": "6651a2b3c4d5e6f7a8b9c0d1",
  "title": "我的第一篇文章",
  "content": "Hello MongoDB!",
  "author": "zyyc",
  "createdAt": "2026-06-12T08:00:00.000Z"
}

22.2 注册 MongoDB Atlas 免费云数据库

22.2.1 什么是 MongoDB Atlas?

MongoDB Atlas 是 MongoDB 官方提供的免费云数据库,不用在自己电脑上安装任何东西,注册就能用。

项目说明
官网https://www.mongodb.com/atlas
免费额度512MB 存储,足够学习和小项目
注册方式邮箱注册 / Google 账号一键登录
需要信用卡?❌ 不需要

22.2.2 注册步骤

第1步:打开 https://www.mongodb.com/atlas
第2步:点击 "Try Free" → 用邮箱注册
第3步:创建组织(Organization)→ 随便填名字
第4步:创建项目(Project)→ 随便填名字
第5步:创建集群(Cluster)→ 选 M0 FREE(免费的那个)
第6步:选择云服务商和地区 → 选 AWS + 新加坡(离中国近)
第7步:点击 "Create Cluster" → 等待 1-3 分钟创建完成

22.2.3 设置数据库访问账号

第1步:左侧菜单 → Security → Database Access
第2步:点击 "Add New Database User"
第3步:用户名和密码自己定(记住!后面要用)
第4步:权限选 "Read and write to any database"
第5步:点击 "Add User"
⚠️ 新手易错:密码不要包含 @#% 等特殊字符,否则连接字符串容易出错。建议用纯字母+数字。

22.2.4 设置网络白名单

第1步:左侧菜单 → Security → Network Access
第2步:点击 "Add IP Address"
第3步:点击 "Allow Access from Anywhere"(会自动填 0.0.0.0/0)
第4步:点击 "Confirm"
💡 学习阶段允许所有 IP 访问。上线后要限制为服务器 IP。

22.2.5 获取连接字符串

第1步:回到 Clusters 页面
第2步:点击 "Connect" 按钮
第3步:选 "Drivers" → 选 "Node.js"
第4步:复制连接字符串,格式如下:
mongodb+srv://<用户名>:***@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority

实际替换后:

mongodb+srv://zyyc:***@cluster0.abc123.mongodb.net/?retryWrites=true&w=majority
⚠️ 新手易错
<用户名><密码> 要替换成你自己设置的,不要带 < >
• 密码中的特殊字符要 URL 编码(比如 @%40
• 后面可以加数据库名:...mongodb.net/myblog?retryWrites=true&w=majority

22.3 安装 mongoose 并连接数据库

22.3.1 mongoose 是什么?

概念说明
MongoDB 驱动底层库,直接操作数据库
mongoose基于驱动的高级库,提供模型、验证等功能
类比MongoDB 驱动 = 手动挡,mongoose = 自动挡
💡 结论:零基础直接用 mongoose,简单好用。

22.3.2 安装 mongoose

npm install mongoose

22.3.3 配置环境变量

在项目根目录创建 .env.local 文件:

MONGODB_URI=mongodb+srv://zyyc:***@cluster0.abc123.mongodb.net/myblog?retryWrites=true&w=majority
⚠️ 新手易错
• 文件名是 .env.local,不是 .env,不是 .env.local.txt
• 等号两边不要有空格
• 修改 .env.local 后必须重启 npm run dev
.env.local 已在 .gitignore 中,不会被提交到 Git

22.3.4 创建数据库连接文件

创建 lib/mongodb.js

// lib/mongodb.js
import mongoose from 'mongoose'

const MONGODB_URI = process.env.MONGODB_URI

if (!MONGODB_URI) {
  throw new Error('请在 .env.local 文件中设置 MONGODB_URI 环境变量')
}

// 用一个全局变量缓存连接,避免热重载时重复连接
let cached = global.mongoose

if (!cached) {
  cached = global.mongoose = { conn: null, promise: null }
}

async function dbConnect() {
  // 如果已经连接过了,直接返回
  if (cached.conn) {
    return cached.conn
  }

  // 如果正在连接中,等待它完成
  if (!cached.promise) {
    cached.promise = mongoose.connect(MONGODB_URI).then((mongoose) => {
      return mongoose
    })
  }

  cached.conn = await cached.promise
  return cached.conn
}

export default dbConnect
💡 为什么要缓存连接?
Next.js 开发模式下每次保存文件会热重载,如果不缓存,每次请求都会新建一个数据库连接,很快就会超出 Atlas 的连接数限制。

22.4 定义数据模型(Schema & Model)

22.4.1 核心概念

概念类比作用
Schema(模式)表格的列定义描述数据长什么样
Model(模型)表格本身用来增删改查
Document(文档)表格的一行一条具体数据

关系:Schema 定义结构 → Model 基于 Schema 创建 → Document 是 Model 的实例

22.4.2 创建文章模型

创建 models/Post.js

// models/Post.js
import mongoose from 'mongoose'

// 定义 Schema:文章的数据结构
const PostSchema = new mongoose.Schema({
  title: {
    type: String,        // 标题是字符串
    required: [true, '标题不能为空'],  // 必填
  },
  content: {
    type: String,        // 内容是字符串
    required: [true, '内容不能为空'],
  },
  author: {
    type: String,        // 作者是字符串
    default: '匿名',     // 默认值
  },
  createdAt: {
    type: Date,          // 创建时间是日期
    default: Date.now,   // 默认当前时间
  },
})

// 创建 Model 并导出
// mongoose.models.Post 防止热重载时重复创建模型
export default mongoose.models.Post || mongoose.model('Post', PostSchema)

Schema 类型对照表

Mongoose 类型说明示例值
String字符串"Hello"
Number数字42
Boolean布尔值true
Date日期new Date()
Array数组[1, 2, 3]
ObjectIdMongoDB 特殊 ID"6651a2b3..."
⚠️ 新手易错mongoose.models.Post || mongoose.model('Post', PostSchema) 这行代码必须有!否则 Next.js 热重载时会报 OverwriteModelError

22.5 API Route 中读写 MongoDB

22.5.1 CRUD 操作速查表

操作方法mongoose 语法说明
创建POSTModel.create(data)插入一条数据
读取GETModel.find(filter)查询数据
更新PUTModel.updateOne(filter, data)修改数据
删除DELETEModel.deleteOne(filter)删除数据

22.5.2 创建文章列表接口 GET

创建 app/api/posts/route.js

// app/api/posts/route.js
import { NextResponse } from 'next/server'
import dbConnect from '@/lib/mongodb'
import Post from '@/models/Post'

// GET /api/posts → 获取所有文章
export async function GET() {
  try {
    // 1. 连接数据库
    await dbConnect()

    // 2. 查询所有文章,按创建时间倒序排列
    const posts = await Post.find({}).sort({ createdAt: -1 })

    // 3. 返回数据
    return NextResponse.json(posts)
  } catch (error) {
    return NextResponse.json(
      { error: '获取文章失败:' + error.message },
      { status: 500 }
    )
  }
}

22.5.3 创建新增文章接口 POST

在同一个 app/api/posts/route.js 中添加:

// POST /api/posts → 创建新文章
export async function POST(request) {
  try {
    // 1. 连接数据库
    await dbConnect()

    // 2. 从请求体获取数据
    const body = await request.json()

    // 3. 创建文章
    const post = await Post.create({
      title: body.title,
      content: body.content,
      author: body.author,
    })

    // 4. 返回创建的文章(状态码 201 = 创建成功)
    return NextResponse.json(post, { status: 201 })
  } catch (error) {
    return NextResponse.json(
      { error: '创建文章失败:' + error.message },
      { status: 500 }
    )
  }
}

22.5.4 常用查询方法速查

方法用途示例
find({})查所有Post.find({})
find({ author: 'zyyc' })按条件查Post.find({ author: 'zyyc' })
findById(id)按 ID 查一条Post.findById('6651a2b3...')
findOne({ title: 'xxx' })查第一条匹配的Post.findOne({ title: '标题' })
countDocuments({})统计数量Post.countDocuments({})
limit(10)限制返回条数Post.find({}).limit(10)
skip(5)跳过前 N 条(分页用)Post.find({}).skip(5).limit(10)

22.6 前端页面展示数据库数据

22.6.1 在页面中调用 API

创建 app/posts/page.js

// app/posts/page.js
'use client'

import { useState, useEffect } from 'react'

export default function PostsPage() {
  const [posts, setPosts] = useState([])
  const [loading, setLoading] = useState(true)

  // 页面加载时从 API 获取文章
  useEffect(() => {
    fetch('/api/posts')
      .then((res) => res.json())
      .then((data) => {
        setPosts(data)
        setLoading(false)
      })
      .catch((err) => {
        console.error('获取文章失败:', err)
        setLoading(false)
      })
  }, [])

  if (loading) {
    return 

加载中...

} return (

📚 文章列表(来自 MongoDB)

{posts.length === 0 ? (

暂无文章,快去 /api/posts 接口用 POST 创建一篇吧!

) : ( posts.map((post) => (

{post.title}

{post.content}

作者:{post.author} | 发布于: {new Date(post.createdAt).toLocaleDateString('zh-CN')}
)) )}
) }
⚠️ 新手易错:MongoDB 的 ID 字段叫 _id(带下划线),不是 id!用 post.id 会得到 undefined

22.7 完整项目实战:文章列表从数据库读取

22.7.1 最终项目结构

my-blog/ ├── .env.local ← 环境变量(连接字符串) ├── lib/ │ └── mongodb.js ← 数据库连接 ├── models/ │ └── Post.js ← 文章模型 ├── app/ │ ├── layout.js ← 布局 │ ├── page.js ← 首页 │ ├── posts/ │ │ └── page.js ← 文章列表页 │ └── api/ │ └── posts/ │ └── route.js ← 文章 API └── package.json

22.7.2 从零搭建步骤

# 第1步:创建项目
npx create-next-app@latest my-blog
cd my-blog

# 第2步:安装 mongoose
npm install mongoose

# 第3步:创建 .env.local
# 在项目根目录新建 .env.local,写入连接字符串

# 第4步:创建文件
# lib/mongodb.js     → 数据库连接
# models/Post.js     → 文章模型
# app/api/posts/route.js → API 接口
# app/posts/page.js  → 前端页面

# 第5步:启动项目
npm run dev

# 第6步:测试
# 1) 访问 http://localhost:3000/posts → 应该显示空列表
# 2) 用 POST 请求创建文章
# 3) 刷新页面 → 应该显示文章了

22.7.3 测试 API(用 curl 或 Postman)

创建文章:

curl -X POST http://localhost:3000/api/posts \
  -H "Content-Type: application/json" \
  -d '{"title":"Hello MongoDB","content":"这是我的第一篇数据库文章!","author":"zyyc"}'

查看文章:

curl http://localhost:3000/api/posts

22.7.4 调试技巧

场景方法
查看数据库里有什么数据Atlas 面板 → Clusters → Browse Collections
测试 API 接口浏览器直接访问 localhost:3000/api/posts(GET)
发送 POST 请求用 curl / Postman / Thunder Client(VS Code 插件)
查看服务器日志终端里 npm run dev 的输出
连接失败排查检查 IP 白名单 → 检查用户名密码 → 检查连接字符串格式

22.8 本章复盘

核心知识点总结

知识点说明
MongoDB Atlas免费云数据库,注册即用
连接字符串mongodb+srv://user:***@host/db
.env.local存放敏感配置,不提交到 Git
mongooseMongoDB 的 Node.js 驱动库
Schema定义数据结构(字段、类型、校验)
Model基于 Schema 创建,用于增删改查
dbConnect()连接数据库(带缓存优化)
Model.find()查询数据
Model.create()创建数据
API Route 调用数据库route.js 中连接数据库、执行操作
前端 fetch 调用 APIfetch('/api/posts') 获取数据

新手最容易犯的 5 个错误

#错误正确做法
1.env.local 文件名写错必须是 .env.local,注意有个点
2连接字符串中 <密码> 没替换<> 和里面的内容都换成真实值
3忘记 dbConnect()每个 API Route 都要先调用 await dbConnect()
4post.id 取 IDMongoDB 的 ID 叫 _id(带下划线)
5忘记加 mongoose.models.Post ||热重载会报错,必须防重复创建模型
下一章预告(第23章):我们将学习博客评论功能实现——从数据结构设计到前后端完整联调,给每篇文章加上评论区,让读者可以互动交流。

本文档由 zyyc-0336 编写,最后更新:2026-06-14

22.8 本章复盘

核心知识点总结

知识点说明
MongoDB Atlas免费云数据库,注册即用
连接字符串mongodb+srv://user:***@host/db
.env.local存放敏感配置,不提交到 Git
mongooseMongoDB 的 Node.js 驱动库
Schema定义数据结构(字段、类型、校验)
Model基于 Schema 创建,用于增删改查
dbConnect()连接数据库(带缓存优化)
Model.find()查询数据
Model.create()创建数据
API Route 调用数据库route.js 中连接数据库、执行操作
前端 fetch 调用 APIfetch('/api/posts') 获取数据

下一章预告(第23章):我们将学习博客评论功能实现——从数据结构设计到前后端完整联调,给每篇文章加上评论区,让读者可以互动交流。

本文档由 zyyc-0336 编写,最后更新:2026-06-14