一、前言
在当今这个信息爆炸的时代,数据无疑已成为企业最宝贵的资产之一。随着数据量的激增和数据类型的多样化,传统关系型数据库在处理某些类型的数据时显得捉襟见肘。正是在这样的背景下,非关系型数据库(NoSQL)应运而生,以其灵活的数据模型、卓越的扩展性和强大的性能处理能力,迅速崛起成为现代应用开发的新宠。MongoDB,作为 NoSQL 数据库的佼佼者,以其文档导向的存储方式和强大的查询语言,为开发者提供了一个高效、易用的数据处理平台。而 Mongoose,作为 Node.js 环境下 MongoDB 的 ODM(对象数据建模)库,进一步简化了数据库操作,让开发者能够更加专注于业务逻辑的实现。本指南旨在提供一个全面的 MongoDB 和 Mongoose 使用入门教程。无论你是数据库领域的新手,还是希望将 MongoDB 和 Mongoose 集成到你的项目中的开发者,本指南都将从基础出发,逐步深入,助你一臂之力。通过本指南的学习,你将能够掌握 MongoDB 和 Mongoose 的核心概念和实用技巧,为成为全栈开发者打下坚实的基础。文章分为上下两篇,当前为"上篇"内容:
二、MongoDB 安装
首先,我们需要安装 MongoDB,安装主要分为本地安装和云端托管两种方法,本指南使用 MongoDB Atlas 进行云端托管。MongoDB Atlas 是由构建 MongoDB 的开发团队提供的多云数据库服务,其旨在简化数据库的部署和管理过程,同时提供必要的多功能性,并且提供了免费集群,足够我们作为学习使用。
创建项目 | 创建集群 | 创建数据库 | 连接集群 |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
javascript
const { MongoClient, ServerApiVersion } = require('mongodb');
const uri = "mongodb+srv://<db_username>:<db_password>@cluster0.wxnku.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0";
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
const client = new MongoClient(uri, {
serverApi: {
version: ServerApiVersion.v1,
strict: true,
deprecationErrors: true,
}
});
async function run() {
try {
// Connect the client to the server (optional starting in v4.7)
await client.connect();
// Send a ping to confirm a successful connection
await client.db("admin").command({ ping: 1 });
console.log("Pinged your deployment. You successfully connected to MongoDB!");
} finally {
// Ensures that the client will close when you finish/error
await client.close();
}
}
run().catch(console.dir);
三、Mongoose 使用
注意点及说明:
- 尽量避免使用箭头函数,因为使用箭头时 this 将无法访问到正确内容!
- 下文仅包含一些较为常用内容,如需更多完整内容请详见 👉 官方文档
3.1 基础知识

一图胜千言。关系型数据库和非关系型数据库的主要区别在于它们存储数据的方式。关系型数据库天然地采用表格形式,数据被组织在行和列构成的数据表中。这些数据表可以通过关系(如外键)相互关联,使得数据的提取和组织变得直观且方便。相对而言,非关系型数据库不适合以行和列的形式存储数据,而是倾向于将数据以整体块的形式存储。非关系型数据通常以集合、文档、键值对或图结构等形式存储,这些结构更灵活,能够适应各种不同的数据模型。
3.1 Schema/Model
javascript
const mongoose = require("mongoose");
const mySchema = new.mongoose.Schema({ name: String }); // 定义 Schema
const MyModel = mongoose.model("Model", mySchema); // 生成 Model
Schema 是 Mongoose 中定义数据模型的结构和规则的对象,它描述了 MongoDB 集合中文档的形状和类型,你可以将 Schema 视为一个蓝图,定义了文档可以包含哪些字段以及这些字段的类型。Model 是基于 Schema 创建的,它代表了 MongoDB 中的一个集合,并且提供了操作这个集合的方法。Model 是 Mongoose 中与数据库交互的主要接口,它封装了底层的 MongoDB 驱动,提供了一个更简洁和面向对象的 API。
3.1.1 类型定义
在 Schema 中,你可以定义文档的字段及其类型,常用有 String、Number、Boolean、Date 等,完整内容详见 👉 文档,示例如下:
javascript
const schema = new Schema({
name: String, // 字符串,这个是简写,完整写法是 { type: String },下同
binary: Buffer, // 缓冲
living: Boolean, // 布尔值
updated: { type: Date, default: Date.now }, // 日期,默认值设置为 Date.now
age: { type: Number, min: 18, max: 65 }, // 数值,设置了允许的最大/最小值
mixed: Schema.Types.Mixed, // 表示什么类型都可以
_someId: Schema.Types.ObjectId,
decimal: Schema.Types.Decimal128,
double: Schema.Types.Double, // 浮点数
int32bit: Schema.Types.Int32, // 32 位整数
array: [], // 数组
ofString: [String], // 数组,等价于 TS 中的 String[]
ofNumber: [Number],
ofDates: [Date],
ofBuffer: [Buffer],
ofBoolean: [Boolean],
ofMixed: [Schema.Types.Mixed],
ofObjectId: [Schema.Types.ObjectId],
ofArrays: [[]],
ofArrayOfNumbers: [[Number]],
nested: { // 嵌套对象
stuff: { type: String, lowercase: true, trim: true }
},
map: Map, // 等价于 JS 中的 Map 类型
mapOfString: {
type: Map,
of: String // of 指定了值类型
}
});
如果想定义一个字段可以是字符串也可以是数值,你可以使用 Schema.Types.Mixed 类型并结合自定义验证器,示例如下:
javascript
const schema = new mongoose.Schema({
data: {
type: mongoose.SchemaTypes.Mixed,
validate: {
validator: function(v) {
return typeof v === 'string' || typeof v === 'number';
},
message: props => `${props.value} is not a valid string or number!`
}
}
});
3.1.2 验证器
Schema 允许为字段添加验证器,以确保数据的完整性和准确性,其本身内置了许多验证器,同时也支持我们自行定义验证器,示例如下:
javascript
const breakfastSchema = new Schema({
name: {
type: String,
required: [true, "A tour must have a name"],
unique: true,
trim: true,
maxlength: [40, "A name must have less or equal then 40 characters"],
minlength: [10, "A name must have more or equal then 10 characters"],
},
eggs: {
type: Number,
min: [6, 'Too few eggs'],
max: 12
},
bacon: {
type: Number,
required: [true, 'Why no bacon?']
},
drink: {
type: String,
required: function() {
return this.bacon > 3;
},
// enum: ['Coffee', 'Tea'],
enum: {
values: ['Coffee', 'Tea'],
message: '{VALUE} is not supported' // {VALUE} 会替换为正在验证的值
}
},
// 自定义验证器
phone: {
type: String,
validate: {
validator: function(v) {
return /\d{3}-\d{3}-\d{4}/.test(v);
},
message: props => `${props.value} is not a valid phone number!`
},
},
// 异步验证器
email: {
type: String,
validate: {
validate: () => Promise.reject(new Error('Oops!')), // Promise 拒绝则认为校验不通过
validator: () => Promise.resolve(false), // Promise 返回 false 也认为校验不通过
message: 'Email validation failed'
}
}
});
3.1.3 可配置项
Schema 有非常多的可配置项可以进行设置,完整内容详见 👉 文档,示例如下:
javascript
// 方法一:构造函数
new Schema({ /* ... */ }, options);
// 方法二:set方法
const schema = new Schema({ /* ... */ });
schema.set(option, value);
常用设置: toJSON、toObject,示例如下:
javascript
new Schema({ /* ... */ }, {
// 当使用 JSON.stringify() 或 model.toJSON() 方法时,所有的虚拟属性会被包含在 JSON 表示中
toJSON: { virtuals: true },
// 当使用 .toObject() 方法时,所有的虚拟属性也会被包含在转换后的 JavaScript 对象中
toObject: { virtuals: true },
});
常用设置:1. 为实例添加公共方法;2. 为 model 添加静态方法,示例如下:
javascript
const animalSchema = new Schema({ name: String }, {
// 公共方法
methods: {
sayHi() {
console.log('Hello!')
}
},
// 静态方法
statics: {
findByName(name) {
return this.find({ name: new RegExp(name, 'i') });
}
}
});
const Animal = mongoose.model('Animal', animalSchema);
const dog = new Animal({ name: 'fido' });
dog.sayHi(); // 实例调用
Animal.findByName('fido'); // model 调用
3.1.4 虚拟字段
Schema 允许定义虚拟字段,这些字段不存储在数据库中,但可以在查询和文档操作中使用,getter 对于格式化或组合字段很有用,而 setter 对于将单个值分解为多个值进行存储非常有用,示例如下:
javascript
const personSchema = new Schema({
name: {
first: String,
last: String
}
}, {
virtuals: {
fullName: {
get() {
return this.name.first + ' ' + this.name.last;
},
set(v) {
this.name.first = v.substr(0, v.indexOf(' '));
this.name.last = v.substr(v.indexOf(' ') + 1);
}
}
}
});
3.1.5 嵌套文档
Schema 支持定义嵌套文档,以表示复杂的数据结构,示例如下:
javascript
const childSchema = new Schema({ name: 'string' });
const parentSchema = new Schema({
// Array of subdocuments
children: [childSchema],
// Single nested subdocuments
child: childSchema
});
const Parent = mongoose.model('Parent', parentSchema);
const parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] });
注意,区别引用文档与嵌套文档,引用文档本身就是单独的顶层文档,而嵌套文档是嵌入在顶层文档中的,当你查询包含引用文档的字段时,Mongoose 默认不会自动填充应用文档的字段,你需要使用 populate 方法来填充这些字段。引用文档示例如下:
javascript
const childSchema = new Schema({ name: 'string' });
const Child = mongoose.model('Child', childSchema);
const parentSchema = new Schema({
child: {
type: mongoose.ObjectId,
ref: 'Child'
}
});
const Parent = mongoose.model('Parent', parentSchema);
// 不包含 child
const doc = await Parent.findOne()
doc.child;
// 使用 populate 填充 child 字段
const doc = await Parent.findOne().populate('child');
doc.child;
四、总结
文本源码 👉 nodejs-learn-natours
本文是指南的上篇-"准备篇",主要介绍了 MondoDB 云端托管的安装方式、Mongoose 的基础知识以及 Mongoose 中所有内容起点"模型定义"。在完成这些准备工作后,我们就要开始真正运用 Mongoose 进行数据库操作了,请跳转阅读《前端玩数据库 👏 MongoDB/Mongoose 入门指南(下)》。希望本文能够作为您使用 Mongoose 的起点,助您在 Node.js 和 MongoDB 的世界中更进一步,另外请不要忘记官方文档一定是最好的资料,多多阅读。