在开发基于
Node.js和Express的项目时,使用Mongoose是连接和操作MongoDB数据库的标准方式。本文将系统性地介绍如何定义模型、查询数据库表。
定义数据模型(Model)
在 Mongoose 中,Schema 用于描述文档结构,Model 则是操作集合的接口。
示例:创建 User 模型(可以理解为 mysql 表)
import mongoose from 'mongoose';
const userSchema = new mongoose.Schema(
{
id: { type: Number, required: true, unique: true},
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: Number,
isActive: { type: Boolean, default: true },
createdAt: { type: Date, default: Date.now }
},
{
collection: 'users' // 明确指定集合名称(可以理解为 "表" 名称)
}
);
// 创建并导出 User 模型; 模型名为 User,集合名为 users.
export default mongoose.model('User', userSchema);
通过 Schema 定义了用户文档的结构,并定义了像 name 为必填字符串、 email 为必填字符串、需唯一、 isActive 默认为true、createdAt自动记录创建时间。
导出 mongoose.model('User', userSchema) 则是操作数据库集合(增、删、改、查)。
注: 当 User 第一次被执行时,若 users 集合不存在时,会自动创建集合。
实现数据库功能 users
你可以通过 Mongoose 提供的方法对集合进行查询。
注:下方代码为封装的路由,如不理解请看 Express 路由
- 新增用户
save()
// src/router/api/user.js
import express from 'express';
import User from '../../database/User.js';
const router = express.Router();
router.post('/add', async (req, res, next) => {
const { name, email, age } = req.body;
// 判断用户名,不存在则把问题给到错误处理中间件
if (!name) {
next(new Error('用户名不能为空'));
}
// 判断邮箱,不存在则把问题给到错误处理中间件
if (!email) {
next(new Error('邮箱不能为空'));
}
try {
// 创建 User 模型数据
const newUser = new User({
id: 1, // 这里的 id 自增逻辑需要自己实现(学习时可忽略)。
name,
email,
age,
isActive: true
});
// 保存新增用户
await newUser.save();
res.status(200).json({
code: 0,
message: '用户创建成功',
data: newUser
});
} catch (error) {
const err = new Error('邮箱已存在');
err.statusCode = 400;
next(err);
}
});
export default router;
2. 查询所有用户 find()
// src/router/api/user.js
import express from 'express';
import User from '../../database/User.js';
const router = express.Router();
router.get('/list', async (req, res, next) => {
try {
// 查询所有用户
const users = await User.find();
// 返回 JSON 数据
res.json({
code: 0,
data: users,
});
} catch (error) {
// 如出现错误,交给错误处理中间件进行处理。
next(new Error('获取用户列表失败'))
}
});
export default router;
3. 通过 ID 查询指定用户 findOne()
// src/router/api/user.js
import express from 'express';
import User from '../../database/User.js';
const router = express.Router();
router.get('/:id', async (req, res, next) => {
const { params } = req;
try {
// 通过 findOne 方法查找 id 为 * 的用户并返回
// findOne() 的参数是一个对象,你可以使用多个字段进行查询,它会返回第一个匹配的文档。
const users = await User.findOne({ id: Number(params.id)});
if (!users) {
const err = new Error('用户不存在');
err.statusCode = 400;
next(err);
}
res.json({
code: '0',
data: users,
});
} catch (error) {
console.log(error);
}
});
export default router;
4.通过 ID 修改指定用户 findOne()
// src/router/api/user.js
import express from 'express';
import User from '../../database/User.js';
const router = express.Router();
router.put('/update', async (req, res, next) => {
const { id, name, email, age } = req.body;
const users = await User.findOne({ id });
if (!users) {
const err = new Error('用户不存在');
err.statusCode = 400;
next(err);
}
users.name = name;
users.email = email;
users.age = age;
// 已经通过 const users = await User.findOne({ id }); 获取了一个用户文档的实例;
// 应该使用变量 users 调用 .save() 方法来保存更改
await users.save();
res.json({ code: '0', message: '更新成功', data: users });
});
export default router;
5.** 通过 ID 删除指定用户 ** remove()
import express from 'express';
import User from '../../database/User.js';
const router = express.Router();
router.delete('/delete', async (req, res, next) => {
const { id } = req.body;
const users = await User.findOne({ id });
if (!users) {
const err = new Error('用户不存在');
err.statusCode = 400;
next(err);
}
// 删除用户
await users.remove();
res.json({ code: '0', message: '删除成功' });
});
export default router;
在 EJS 模板中实现数据库操作
注:这里需要用到响应动态页面,如果不了解请看 Express 响应动态页面 EJS 模板引擎
- 模板文件:
user-list.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= title %></title>
</head>
<body>
<!-- 引入公共文件 -->
<%- include('includes/header.html') %>
<div class="content">
<% if (list.length > 0) { %>
<ul class="list">
<% list.forEach((item, index) => { %>
<li class="active">
<p><%= item.name %> - <%= item.age %> - <%= item.email %></p>
<div>
<button class="edit" onclick="setUser('<%= encodeURIComponent(JSON.stringify(item)) %>')">修改</button>
<button class="delete" onclick="deleteUser('<%= item.id %>')">删除</button>
</div>
</li>
<% }) %>
</ul>
<% } else { %>
<p class="noData">暂无数据</p>
<% } %>
</div>
<button class="add" onclick="setUser()">创建用户</button>
<!-- 新增 / 修改 -->
<div id="addForm" class="addForm">
<h3>
<b id="title">新增用户</b>
<span onclick="document.getElementById('addForm').style.display = 'none'">关闭</span>
</h3>
<form id="userForm">
<input type="hidden" name="id" value="" />
<input type="text" name="name" placeholder="姓名" required />
<input type="email" name="email" placeholder="邮箱" required />
<input type="number" name="age" placeholder="年龄" />
<button class="add" type="submit">提交</button>
</form>
</div>
</body>
<script>
document.getElementById('userForm').addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData(this);
const data = Object.fromEntries(formData);
let res;
if (data.id) {
res = await fetch('/user/update', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
} else {
res = await fetch('/user/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
const result = await res.json();
if (result.code === '0') {
alert(`${data.id ? '修改' : '创建'}成功`);
location.reload();
}
});
async function setUser(data) {
const user = data ? JSON.parse(decodeURIComponent(data)) : {};
const addForm = document.getElementById('addForm');
const title = document.getElementById('title');
if (user.id) {
title.innerHTML = '修改用户';
addForm.style.display = 'block';
const inputs = document.querySelectorAll('#userForm input');
inputs.forEach(input => {
if (input.name) {
input.value = user[input.name];
}
})
} else {
title.innerHTML = '新增用户';
addForm.style.display = 'block';
const inputs = document.querySelectorAll('#userForm input');
inputs.forEach(input => {
if (input.type === 'text' || input.type === 'email' || input.type === 'number') {
input.value = '';
} else if (input.type === 'hidden') {
input.value = '';
}
});
}
}
async function deleteUser(id) {
if (confirm('确定要删除该用户吗?')) {
await fetch(`/user/delete/${id}`, { method: 'DELETE' });
location.reload();
}
}
</script>
</html>
2. 样式文件 global.css
*,
*:before,
*:after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
height: 100%;
width: 100%;
background: #f2f2f2;
}
body,
input,
button,
select {
font-family: PingFangSC-Regular, Microsoft YaHei, Roboto, Helvetica, Tahoma,
Arial, serif;
font-size: 14px;
color: #111;
}
input:focus,
select:focus,
button:focus {
outline: none;
}
a {
text-decoration: none;
outline: 0 none;
color: #111;
}
img {
border: 0 none;
}
ol,
ul,
li,
table,
tbody {
margin: 0;
padding: 0;
list-style: none;
}
:root {
--color-white: #fff;
--color-999: #999;
--color-green: green;
--color-red: red;
}
header {
background: var(--color-white);
height: 50px;
line-height: 50px;
text-align: center;
font-size: 18px;
font-weight: 700;
margin-bottom: 12px;
}
.content {
background: var(--color-white);
padding: 10px;
}
.noData {
color: var(--color-999);
text-align: center;
}
.list li {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.list li:last-child {
margin-bottom: 0;
}
.list li .edit {
color: var(--color-green);
border: none;
background: none;
margin-right: 6px;
}
.list li .delete {
color: var(--color-red);
border: none;
background: none;
}
button.add {
display: block;
width: 80%;
padding: 8px 10px;
margin: 10px auto;
border-radius: 4px;
border: none;
background: var(--color-green);
color: var(--color-white);
}
.addForm {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--color-white);
}
.addForm h3 {
position: relative;
height: 50px;
line-height: 50px;
text-align: center;
margin-bottom: 12px;
}
.addForm h3 span {
position: absolute;
right: 10px;
font-weight: 300;
font-size: 14px;
color: var(--color-999);
}
.addForm input {
display: block;
width: 92%;
height: 40px;
margin: 0 auto 12px;
padding: 0 8px;
}
.addForm button {
width: 92%;
height: 40px;
}
3. 路由文件 html.js
import express from 'express';
import User from '../database/User.js';
const router = express.Router();
router.get('/user-list', async (req, res) => {
// 这里获取了 user 表的数据
const users = await User.find();
res.render(
'user-list.html',
{
title: '首页',
list: users,
}
);
});
export default router;
4. 项目 app.js
import express from 'express';
import {urlencoded, json, static as staticMiddleware} from "express";
import ejs from 'ejs';
import {fileURLToPath} from 'url';
import {dirname, join} from 'path';
import cookieParser from 'cookie-parser';
import { connectDB } from './database/db.js';
import htmlRouter from './router/html.js';
import userRouter from './router/api/user.js';
const app = express();
const port = 3000;
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
app.set('views', join(__dirname, '/template'));
app.set('view engine', 'html');
app.engine('html', ejs.__express);
app.use(staticMiddleware(join(__dirname, 'public')));
app.use(urlencoded({ extended: true }));
app.use(json());
app.use(cookieParser('zhang1992'));
// 页面路由
app.use('/', htmlRouter);
// 用户接口路由
app.use('/user', userRouter);
// 错误处理中间件
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
code: '-1',
msg: err.message,
});
})
// 链接数据库并启动项目
connectDB().then(() => {
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
});