Node.js --- 详解MongoDB与Mongoose

1. MongoDB的下载与配置

1.1 下载

使用MongoDB需要下载其对应版本的服务端与客户端

服务端:Mongod

https://www.mongodb.com/try/download/community-kubernetes-operator

客户端:使用Mongosh

MongoDB Shell Download | MongoDB

1.2 配置MongoDB

MongoDB的数据会默认存储在'C:/data/db'中,所以用户需要在C盘中创建data文件夹,其中再创建db文件夹

在MongoDB中包含多个数据库,数据库包含多个集合collection,每一个集合包含了多个文档,在操作mongdb中需要进入到相应的数据库,然后针对于对于collection进行操作

使用MongoDB时,需要保证服务端和客户端同步打开,使用cmd命令行启动

2. 数据库命令

以下是在MongoDB客户端中常见的数据库命令操作:

2.1 数据库操作

显示所有数据库

bash 复制代码
show dbs

切换数据库

bash 复制代码
use myDatabase

如果数据库不存在,会在插入数据时自动创建。

bash 复制代码
db

显示当前所在数据库

bash 复制代码
use 库名
db.dropDatabase()

删除当前数据库

2.2 集合命令操作

创建集合

bash 复制代码
db.createCollection('集合名称')

显示当前数据库中所有的集合

bash 复制代码
show collections

删除集合

bash 复制代码
db.集合名.drop()

集合重命名

bash 复制代码
db.集合名.renameCollection('名称')

2.3 文档命令

以myCollection集合为例

插入数据

bash 复制代码
db.myCollection.insertOne({ name: "Alice", age: 25, city: "New York" })

插入多个文档

bash 复制代码
db.myCollection.insertMany([
  { name: "Bob", age: 30, city: "San Francisco" },
  { name: "Charlie", age: 35, city: "Los Angeles" }
])

2.4 查询数据

查询所有文档

bash 复制代码
db.myCollection.find()

按条件查询

bash 复制代码
db.myCollection.find({ age: { $gt: 25 } })  # 查询年龄大于25的文档

运算符

在mongodb中不能直接使用<,>,<=,>=等符号,需要使用替代符号

|-----|------|
| > | gt | | \< | lt |
| >= | gte | | \<= | lte |
| !== | $ne |

逻辑运算

逻辑或

bash 复制代码
db.collectionName.find({$or:[{age:{$lt:30}},{age:{$gt:40}}]})

逻辑与

bash 复制代码
db.collectionName.find({$and:[{age:{%lt:20}},{age:{$gt:15}}]})

更新数据

bash 复制代码
db.myCollection.updateOne({ name: "Alice" }, { $set: { age: 26 } })
bash 复制代码
db.myCollection.updateMany({ city: "New York" }, { $set: { city: "NYC" } })

删除数据

删除单个文档

bash 复制代码
db.myCollection.deleteOne({ name: "Bob" })

删除多个文档

bash 复制代码
db.myCollection.deleteMany({ age: { $lt: 30 } })  # 删除年龄小于30的文档

3. 基于Node.js的驱动 - Mongoose

Mongoose是构建在MongoDB驱动之上的更高级的抽象库

简化了复杂查询和数据操作,支持中间件,钩子等高级功能

3.1 连接MongoDB

在javascript中实现操作MongoDB数据库需要与之进行连接,默认端口为27017,下划线后声明操作的数据库名称

javascript 复制代码
mongoose.connect("mongodb://127.0.0.1:27017/test")

设置事件以及相应的回调函数

javascript 复制代码
mongoose.connection.on('open',()=>{console.log('连接成功')});
mongoose.connection.on('error',()=>{console.log('Error')})
mongoose.connection.on('close',()=>{console.log('链接关闭')})

分别为数据库连接事件,连接失败事件,数据库关闭事件,以及对应的回调函数

设置回调关键字也可以使用once,事件回调函数只执行一次

javascript 复制代码
mongoose.connection.once('open',async ()=>{})

async声明一个异步函数,最终会返回一个Promise对象,函数内部可以使用await来等待异步操作完成。

断开数据库连接,在正式运行时一般不使用该操作

javascript 复制代码
mongoose.disconnect()

3.2 创建文档的结构对象

3.2.1 定义构造器

mongoose.Schema是Mongoose提供的一个构造器,用于定义MongoDB集合中文档的结构,以及字段的验证规则

javascript 复制代码
const BookSchema = new mongoose.Schema({
    name: { type: String, required: true },   // 书名,类型是字符串,必填
    author: { type: String, required: true }, // 作者,类型是字符串,必填
    price: { type: Number, required: true },  // 价格,类型是数字,必填
    pub_time: { type: Date, required: true }, // 出版时间,类型是日期,必填
});
字段类型

|------------|-------------------------------------------|
| 类型 | 描述 |
| String | 字符串 |
| Number | 数字 |
| Boolean | 布尔值 |
| Array | 数组 |
| Date | 日期 |
| Buffer | Buffer对象 |
| Mixed | 任意类型,mongoose.Schema.Types.Mixed指定 |
| ObjectId | 对象ID,mongoose.Schema.Types.ObjectId指定 |
| Decimal128 | 高精度数字, mongoose.Schema.Types.Decimal128指定 |

字段值验证

必填项:

javascript 复制代码
name:{type:String,required:true}

默认值:

javascript 复制代码
name:{type:String, default:'default'}

枚举值:

设置的值必须是数组当中的

javascript 复制代码
name:{type:String,enum:['male','female']}

唯一值:

表示这个值是唯一的,不能产生重复的

javascript 复制代码
name:{type:String, unique:true}

注意:永远不要相信用户的输入!!!

3.2.2 模型对象

模型对象是对MongoDB集合的封装,提供了用于操作集合的方法,增删改查。

javascript 复制代码
const BookModel = mongoose.model("books", BookSchema);

·第一个参数:表示数据库中集合的名称,mongoose会使用集合名称的复数创建集合

·第二个参数:之前所有定义的Schema,用于描述集合中文档的结构

3.3 模型方法

方法中的语句与MongoDB操作语句别无二致,但需要使用到模型对象进行对应操作

插入文档

  • 创建文档实例并保存

只会创建一个文档实例,并不会自动将其保存到数据库

javascript 复制代码
const book = new BookModel({数据})

显式调用 save() 方法才能将该实例保存到数据库中

javascript 复制代码
await book.save()
  • 快速创建插入
javascript 复制代码
        const data = await BookModel.create({
            name: "西游记",
            author: "吴承恩",
            price: 19.9,
            pub_time:new Date()
        });

删除文档

javascript 复制代码
await BookModel.deleteOne({ name: "红楼梦" }); // 删除匹配的单个文档
await BookModel.deleteMany({name:'西游记'})//删除多个文档

更新文档

javascript 复制代码
await BookModel.updateOne({ name: "西游记" }, { price: 22.0 }); // 更新价格
await BookModel.updateMany({ name: "西游记" }, { price: 22.0 }); // 更新价格

读取文档

javascript 复制代码
const book = await BookModel.findOne({ price: { $gte: 20 } }); // 查找单个价格大于等于20的书
const books = await BookModel.find({price:{$gte:20}}) //批量查找
const books = await BookModel.find();

个性化读取

字段筛选
javascript 复制代码
const results = await BookModel.find({}, "name price"); // 只返回 name 和 price

使用字符串定义需要返回的字段属性

数据排序
javascript 复制代码
Model.find(filter).sort({ field: 1 or -1 })

1表示升序;-1表示降序

数据截取

limit():限制查询结果的数量

javascript 复制代码
const results = await BookModel.find().limit(5); // 获取前 5 条文档
console.log(results);

skip():跳过指定数量的文档,常用于分页查询

javascript 复制代码
const page = 2; // 当前页数
const limit = 3; // 每页显示的文档数量

const results = await BookModel.find()
    .skip((page - 1) * limit)
    .limit(limit);

console.log(results);

4. 示例:模拟注册登陆系统

4.1 前端页面

login.html:

html 复制代码
<!DOCTYPE html>>
<head>
    <title>登陆页面</title>
</head>
<body>
    <form method="post" action="/dashboard">
            <!-- 姓名 -->
            <label for="name">姓名</label>
            <input type="text" id="name" name="name" placeholder="请输入你的姓名" required><br><br>
                
            <!-- 年龄 -->
            <label for="age">年龄</label>
            <input type="number" id="age" name="age" placeholder="请输入你的年龄" min="0" required><br><br>
                
            <!-- 提交按钮 -->
            <button type="submit">提交</button>
    </form>
</body>

register.html:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Train Page</title>
</head>
<body>
    <h1>用户信息注册</h1>
    <form method="post" action="/save">
        <!-- 姓名 -->
        <label for="name">姓名</label>
        <input type="text" id="name" name="name" placeholder="请输入你的姓名" required><br><br>
        
        <!-- 年龄 -->
        <label for="age">年龄</label>
        <input type="number" id="age" name="age" placeholder="请输入你的年龄" min="0" required><br><br>
        
        <!-- 提交按钮 -->
        <button type="submit">提交</button>
    </form>
</body>
</html>

page.html:

html 复制代码
<!DOCTYPE html>
<head>
    <title>主页面</title>
</head>
<body>
    <h1>欢迎访问主页</h1>
    <a href="/login">登录</a>
    <a href="/register">注册</a>
</body>

4.2 后端数据存储与验证

javascript 复制代码
const bodyParser = require('body-parser');
const express = require('express');
const mongoose = require('mongoose');
const path = require('path');

const app = express();

// 配置 body-parser
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 连接 MongoDB
mongoose.connect('mongodb://127.0.0.1:27017/test', {
    useNewUrlParser: true,
    useUnifiedTopology: true,
});

mongoose.connection.once('open', () => {
    console.log('数据库连接成功');
});

// 定义用户 Schema 和模型
const userSchema = new mongoose.Schema({
    name: { type: String, required: true },
    age: { type: Number, min: 0, required: true },
});

const User = mongoose.model('User', userSchema);

// 主页面
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname,"page.html"));
});

// 注册页面
app.get('/register', (req, res) => {
    res.sendFile(path.join(__dirname, 'register.html'));
});

// 登录页面
app.get('/login', (req, res) => {
    res.sendFile(path.join(__dirname, 'login.html'));
});

// 保存用户信息
app.post('/save', async (req, res) => {
    try {
        const { name, age } = req.body;
        const newUser = new User({ name, age });
        console.log('保存用户:', name, age);
        await newUser.save();
        res.send('<h1>保存用户成功</h1><a href="/">返回主页</a>');
    } catch (err) {
        console.error('保存用户失败:', err);
        res.status(500).send('<h1>保存失败</h1><a href="/">返回主页</a>');
    }
});

// 登录验证
app.post('/dashboard', async (req, res) => {
    try {
        const { name, age } = req.body;

        // 查找用户
        const user = await User.findOne({ name });
        if (!user) {
            return res.status(400).send('<h1>用户不存在</h1><a href="/login">返回登录</a>');
        }

        // 验证年龄
        if (parseInt(age) === user.age) {
            res.send('<h1>登录成功</h1><a href="/">返回主页</a>');
        } else {
            res.status(400).send('<h1>年龄错误,登录失败</h1><a href="/login">返回登录</a>');
        }
    } catch (err) {
        console.error('登录失败:', err);
        res.status(500).send('<h1>登录失败</h1><a href="/login">返回登录</a>');
    }
});

// 启动服务
app.listen(3000, () => {
    console.log('服务启动,监听端口 3000');
});
相关推荐
我不是疯子是傻子12 小时前
Qt CAN通信周期发送抖动?实测定时器精度校准与时间戳补偿方案
开发语言·数据库·qt
IT_陈寒12 小时前
Vue的双向绑定把我坑惨了,原来这个场景不能用
前端·人工智能·后端
这个DBA有点耶13 小时前
数据库迁移怎么做到“零翻车“:金仓 KDMS/KDTS/KFS 工具链实测
数据库·架构·dba
烬羽13 小时前
从 nest new 到 Hello World:一个最小 NestJS 项目,讲透工厂 + 装饰器 + 模块化
设计模式·node.js·nestjs
315356691314 小时前
DeepSeek Harness 发布后,我没急着跑 Demo,先把 `.agents/` 翻了一遍
前端·后端·github
名字还没想好☜14 小时前
Next.js 用 Server Components 直连数据库:去掉 API 层的边界,和三条别踩的安全红线
前端·javascript·数据库·安全·react·next.js
Nturmoils14 小时前
不在公司,也能连回办公电脑:用 Natapp 打通 Windows 远程桌面
后端
LEE14 小时前
AI Agent 都在疯狂加功能,它说:我全砍了
前端·后端
迷迭香yy14 小时前
行业板块轮动因子实战从板块资金到因子建模的本地化Python全流程
数据库·人工智能·python
桦说编程14 小时前
记一次 Coding Agent 改动带来的bug与启示
后端·agent·vibecoding