昨天我們學會了 Mongoose 的 基本用法:Schema、Model 以及 CRUD 操作。
今天要進一步探討三個實務開發中非常重要的功能:
這些功能能讓我們的專案更有結構、更安全,也更貼近真實應用。
在真實專案中,不能讓使用者隨便丟資料進來。例如:
{ "name": 123, "email": "not-an-email" }
如果沒有驗證,這些髒資料會讓資料庫變得混亂。
Mongoose 提供了強大的 Schema 驗證,可以在寫入前就檢查。
const userSchema = new mongoose.Schema({
name: { type: String, required: true, minlength: 2 },
email: { type: String, required: true, unique: true, match: /.+\@.+\..+/ },
age: { type: Number, min: 0, max: 120 }
});
required: true
→ 必填欄位minlength
/ maxlength
→ 字串長度限制match
→ 用正則表達式檢查格式(例如 email)min
/ max
→ 數值範圍Mongoose 驗證失敗會丟出 ValidationError,可以用 try/catch
捕捉:
try {
await User.create({ name: "A", email: "invalid" });
} catch (err) {
console.error("❌ 驗證錯誤:", err.message);
}
在 Express API 裡,可以統一處理錯誤回傳:
app.use((err, req, res, next) => {
if (err.name === "ValidationError") {
return res.status(400).json({ error: err.message });
}
res.status(500).json({ error: "伺服器錯誤" });
});
有時候我們希望在 資料存入前或存入後,自動做一些處理。
例如:在儲存使用者時,自動把密碼加密。
userSchema.pre("save", function (next) {
console.log("🔒 即將儲存使用者:", this.name);
next();
});
userSchema.post("save", function (doc, next) {
console.log("✅ 已成功儲存:", doc._id);
next();
});
import bcrypt from "bcrypt";
userSchema.pre("save", async function (next) {
if (this.isModified("password")) {
this.password = await bcrypt.hash(this.password, 10);
}
next();
});
好處是 不用每次寫 API 時都重複寫加密程式碼,商業邏輯被集中管理。
雖然 MongoDB 是 NoSQL,但 Mongoose 提供了 關聯 (populate) 功能,
讓我們能像 SQL JOIN 一樣,把資料連結起來。
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: "User" }
});
const Post = mongoose.model("Post", postSchema);
ref: "User"
→ 表示這個欄位參考的是 User
ModelObjectId
→ MongoDB 內建的唯一 IDconst newUser = await User.create({
name: "Alice",
email: "alice@example.com",
password: "123456"
});
await Post.create({
title: "我的第一篇文章",
content: "這是內容",
author: newUser._id
});
const posts = await Post.find().populate("author", "name email");
console.log(posts);
透過 populate
讓 author
欄位做關聯,能夠自動帶出該使用者的 name
與 email
的資料。
介紹完三個功能之後,用個小範例來整合這三個功能
import express from 'express'
import mongoose from 'mongoose'
import bcrypt from 'bcrypt'
const app = express()
app.use(express.json())
// 連線 MongoDB
await mongoose.connect('mongodb://localhost:27017/testdb')
// User Schema
const userSchema = new mongoose.Schema({
name: {type: String, required: true, minlength: 2},
email: {type: String, required: true, unique: true, match: /.+\@.+\..+/},
password: {type: String, required: true, minlength: 6},
})
// Hooks:儲存前加密密碼
userSchema.pre('save', async function (next) {
if (this.isModified('password')) {
this.password = await bcrypt.hash(this.password, 10)
}
next()
})
const User = mongoose.model('User', userSchema)
// Post Schema (關聯 User)
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
})
const Post = mongoose.model('Post', postSchema)
// API:建立使用者
app.post('/users', async (req, res, next) => {
try {
const user = await User.create(req.body)
res.status(201).json(user)
} catch (err) {
next(err)
}
})
// API:建立文章
app.post('/posts', async (req, res, next) => {
try {
const post = await Post.create(req.body)
res.status(201).json(post)
} catch (err) {
next(err)
}
})
// API:查詢文章並帶出作者資訊
app.get('/posts', async (req, res) => {
const posts = await Post.find().populate('author', 'name email')
res.json(posts)
})
// 統一錯誤處理
app.use((err, req, res, next) => {
if (err.name === 'ValidationError') {
return res.status(400).json({error: err.message})
}
res.status(500).json({error: '伺服器錯誤'})
})
// ----------------------
// 測試用假資料 Seeder
// ----------------------
async function seedFakeData() {
const userCount = await User.countDocuments()
if (userCount > 0) {
console.log('📌 假資料已存在,跳過 Seeder')
return
}
const fakeUsers = [
{name: 'Alice', email: 'alice@example.com', password: 'password123'},
{name: 'Bob', email: 'bob@example.com', password: 'password123'},
{name: 'Charlie', email: 'charlie@example.com', password: 'password123'},
]
for (const u of fakeUsers) {
const user = new User(u)
await user.save() // 觸發 pre("save") → 自動加密
}
console.log('✅ 假資料建立完成')
}
seedFakeData()
app.listen(3000, () => console.log('🚀 伺服器運行中:http://localhost:3000'))
今天我們進一步認識了 Mongoose 的功能:
透過這些功能,我們能打造出更穩健、更安全、也更好維護的應用程式。