️ NodeJS专栏:Node.js从入门到精通
️ 博主的前端之路(源创征文一等奖作品):前端之行,任重道远(来自大三学长的万字自述)
️ TypeScript知识总结:TypeScript 学习笔记(十万字超详细知识点总结)
个人简介:大三学生,一个不甘平庸的平凡人
你的一键三连是我更新的最大动力❤️!
分享博主自用牛客网:一个非常全面的面试刷题求职网站,点击跳转
上一节我们介绍了MongoDB
和Mongoose
,这一节为了巩固Nodejs
操作MongoDB
数据库实现增删改查的功能,本文将带领大家使用Express
+MongoDB
初步制作一个简易的用户管理系统项目,并且在之后的文章中会对这个项目一点点完善,包括业务分层、登录鉴权、头像上传等,记的关注博主第一时间接收更新哦!
话不多说,开干!
在带领大家搭建项目之前,先向大家展示一下这一节搭建的项目最终实现的效果:
因为写这个项目的目的是巩固Nodejs操作数据库,重点在于功能的实现,所以我们并不会过多的去写
CSS
来美化页面。
本篇文章将带领大家去实现这个简易用户管理系统的以下功能:
我们将使用express 应用程序生成器
来搭建项目骨架,控制台输入:
express 简易用户管理系统 --view=ejs
上面的命令将创建一个使用ejs
模板的名为简易用户管理系统
的express
项目。
express 应用程序生成器
的介绍可以查看我的这篇文章:Node.js | 深入讲解 express 应用程序生成器
使用VS Code打开项目,找到package.json
,修改下图所示的地方:
这里修改的目的是:在启动项目时使用
nodemon
指令来启动,这样当我们项目代码更改时它能够自动重新运行(前提是你安装了nodemon
指令,没有安装的可以控制台输入npm i nodemon -g
全局安装nodemon
)。
之后安装mongoose
,在项目根目录下打开终端执行下面这行代码进行安装:
npm i mongoose
再执行以下代码运行项目:
npm run start
浏览器打开http://localhost:3000/
,出现以下页面表示运行成功:
项目根目录下创建config
文件夹(此文件夹用来存放配置文件),并在此文件夹内创建db.config.js
文件(DB
数据的配置文件):
// db.config.js
const mongoose = require("mongoose");
// 连接数据库
// 前缀mongodb:是固定的,后面是你的mongodb的运行端口
// user_test代表数据库名称
mongoose.connect("mongodb://127.0.0.1:27017/user_test");
// 当你插入集合和数据时,数据库user_test会自动创建
user_test
是我定义的数据库的名称,大家根据需要可以自行更改。
之后在app.js
文件中引入我们创建的这个db.config.js
文件:
// app.js中添加以下代码
// 引入数据库模块
require("./config/db.config");
项目根目录下创建model
文件夹(此文件夹用来存放模型),并在此文件夹内创建UserModel.js
文件(用户模型):
// UserModel.js
const mongoose = require("mongoose");
// 字段类型
const UserType = {
username: String,
password: String,
age: Number,
};
// 创建一个模型(user),对应数据库中的集合(表)(users)
const UserModel = mongoose.model("user", new mongoose.Schema(UserType));
// 注意:创建的mongodb的集合名称是加s的
// mongoose.model第二个参数可以通过mongoose.Schema生成的实例来限制集合字段类型
// 因为mongodb过于自由,对类型没有限制,我们在开发中往往需要使用mongoose.Schema来手动限制数据库各种字段类型
// 导出模型
module.exports = UserModel;
根据这个用户模型,Mongoose
能够自动帮我们在数据库中创建一个users
集合,集合内的每条数据包含的字段有:username
、password
、age
、以及自动生成的_id
。
之后运行MongoDB
数据库,再启动项目,MongoDB
的运行窗口出现下图所示就代码我们连接成功了:
将app.js
中的usersRouter
的路由前缀修改为/api
:
// app.use("/users", usersRouter);
app.use("/api", usersRouter); // 使用/api前缀
在routes
目录下的users.js
中引入我们上面创建的用户模型:
// users.js
// 引入用户模型
const UserModel = require("../model/UserModel");
再将users.js
中的下述代码删除:
/* GET users listing. */
router.get("/", function (req, res, next) {
res.send("respond with a resource");
});
我们常使用RES Tful
架构来规范接口的定义,RES Tful
特点包括:
URI
代表1种资源;GET
、POST
、PUT
、DELETE
4个表示操作方式的动词对服务端资源进行操作:GET
用来获取资源,POST
用来新建资源(也可以用于更新资源),PUT
用来更新资源,DELETE
用来删除资源;XML
或者HTML
;使用方式:
请求方式 | 请求地址 | 效果 |
---|---|---|
GET | http://localhost:3000/api/user | 获取用户列表(所有用户信息) |
GET | http://localhost:3000/api/user/{id} | 获取指定id的用户信息 |
POST | http://localhost:3000/api/user | 添加用户信息 |
PUT | http://localhost:3000/api/user/{id} | 修改指定id的用户信息 |
DELETE | http://localhost:3000/api/user/{id} | 删除指定id的用户信息 |
过滤信息:
过滤信息常用于
GET
请求,通过指定一些规则来获取对应的信息。
通用字段 | 说明 | 例子 |
---|---|---|
limit | 返回记录的数量 | http://localhost:3000/api/user/?limit=10 获取10条数据 |
offset | 返回记录的开始位置 | http://localhost:3000/api/user/?offset=10 获取数据库中第10条之后的数据 |
page | 指定第几页 | 常配合per_page使用 |
per_page | 每页的记录数 | http://localhost:3000/api/user/?page=2&per_page=10 按照每页10条数来算,获取第二页数据 |
sortby | 指定返回结果按照哪个属性排序 | 常配合order使用 |
order | 指定排序顺序 | http://localhost:3000/api/user/?sortby=name&order=asc 获取按照name字段升序排序的数据 |
state | 指定筛选条件 | http://localhost:3000/api/user/?state=close 获取state为close的数据 |
多个过滤字段可以相互结合使用(通过&
分割),并以URL
参数的形式出现在请求地址之后。
添加用户信息我们使用post
接口,users.js
中添加以下代码:
// 添加数据
router.post("/user", function (req, res, next) {
// 获取请求体中的参数
const { username, password, age } = req.body;
// 插入数据库
UserModel.create({ username, password, age })
.then((data) => {
res.send(data);
})
.catch((error) => {
console.log(error);
res.send({ mag: "添加信息出错!" });
});
});
使用Api
调试工具测试一下接口是否正常运行,这里使用Apifox
进行测试:
Apifox的使用教程
先将测试环境的前置URL
设置为我们项目运行的地址:
http://127.0.0.1:3000就是http://localhost:3000/
定义接口 | 运行接口 |
---|---|
结果显示接口运行成功,在MongoDB
可视化工具中能够查看到新添加的数据:
删除用户信息我们使用delete
接口,users.js
中添加以下代码:
// 删除数据
router.delete("/user/:userId", function (req, res, next) {
// 获取动态路由参数
const { userId } = req.params;
// 删除数据
UserModel.deleteOne({ _id: userId })
.then((data) => {
res.send({ msg: "删除成功!", ...data });
})
.catch((error) => {
console.log(error);
res.send({ msg: "删除失败!" });
});
});
使用Apifox
测试:
定义接口 | 运行接口 | 效果 |
---|---|---|
修改用户信息我们使用put
接口,users.js
中添加以下代码:
// 修改数据
// 动态路由,获取前端传来的id
router.put("/user/:userId", function (req, res, next) {
// 获取请求体中的参数
const { username, password, age } = req.body;
// 获取动态路由参数
const { userId } = req.params;
// 更新数据
UserModel.updateOne(
{ _id: userId },
{
username,
password,
age,
}
)
.then((data) => {
res.send({ msg: "更新成功!", ...data });
})
.catch((error) => {
console.log(error);
res.send({ msg: "更新失败!" });
});
});
使用Apifox
测试,定义接口:
定义接口 | 运行接口 | 效果 |
---|---|---|
查询用户信息使用get
接口,users.js
中添加以下代码:
这里的查询先是获取到数据的总数量,然后再根据前端传来的
page
、limit
字段查询到指定数据,之后将查询的数据和数据总数量一并返回给前端,从而实现分页查询的功能。
// 查询数据:分页查询的接口
router.get("/user", function (req, res, next) {
// 获取路由参数(过滤信息)
const { page, per_page} = req.query;
UserModel.find()
.count() // count方法,获取数据的总数量
.then((dataCount) => {
// find查询,第二个参数数组指定获取的字段,这里是获取username和age以及id(默认具有),不获取password
UserModel.find({}, ["username", "age"])
// sort排序,按照age:1(正序)排序,age:-1为倒叙
.sort({ age: 1 })
// skip方法,代表跳过几条数据开始获取
.skip((page - 1) * per_page)
// limit方法,代表取多少条数据
.limit(per_page)
.then((data) => {
res.send({ data, dataCount });
});
});
});
定义接口 | 运行接口 |
---|---|
至此,整个项目需要用到的Api接口就都定义好了,下面我们开始搭建前端页面。
我们在views
目录下的index.ejs
文件中创建我们的前端页面:
注意:虽然我们这个项目是在
ejs
模板中搭建页面,但我们并不需要使用ejs
语法,只需使用html
语法即可。
<body>
<h1>NodeJS操作mongodb:简易用户管理系统h1>
<div>
用户名:<input type="text" id="username">
div>
<div>
密码:<input type="text" id="password">
div>
<div>
年龄:<input type="number" id="age">
div>
<button id="addBtn">增加用户button>
<p>提示:在输入框中输入信息可选择点击增加用户,也可点击表格中更新按钮来更新指定数据p>
<hr>
<table border="2">
<thead>
<tr>
<td>idtd>
<td>用户名td>
<td>年龄td>
<td>操作td>
tr>
thead>
<tbody>
tbody>
table>
<div id="pageBtn">
div>
<script>script>
body>
获取DOM元素:
const username = document.getElementById('username')
const password = document.getElementById('password')
const age = document.getElementById('age')
const pageBtn = document.getElementById('pageBtn')
const addBtn = document.getElementById('addBtn')
定义变量:
// 数据总条数
let dataCount = 0
// 每页显示条目个数
let pageSize = 2
// 当前页数
let pageNum = 1
// 总页数
let pageCount = 1
渲染列表数据:
// 渲染列表数据
function renderTable(data) {
const tbody = document.querySelector('tbody')
tbody.innerHTML = data.map(item => `
${item._id}
${item.username}
${item.age}
`).join('')
}
renderTable
函数接收的是一个用户数据的数组集合,用来将此数据渲染到tbody
中。
渲染分页按钮:
// 渲染分页按钮
function renderPageBtn(page) {
let str = ''
for (let i = 1; i <= page; i++) {
if (i === pageNum) {
// 添加红色背景
str += ``
} else {
str += ``
}
}
pageBtn.innerHTML = str
}
renderPageBtn
函数接收的参数表示总页数,有多少页就渲染多少个分页按钮,同时对当前所处的页对应的分页按钮做背景变红的处理,并且每个分页按钮都绑定了一个点击事件。
获取数据:
// 查询数据api
function getList(page) {
fetch(`/api/user/?page=${page}&per_page=${pageSize}`).then(res => res.json()).then(res => {
// 设置数据总条数
dataCount = res.dataCount
// 设置总页数
pageCount = Math.ceil(dataCount / pageSize)
// 设置当前页数
pageNum = page
// 渲染列表数据
renderTable(res.data)
// 渲染分页按钮
renderPageBtn(pageCount)
})
}
getList
函数接收的参数表示页数,用来获取指定页的数据,并调用renderTable
将数据渲染,同时调用renderPageBtn
更新分页按钮。
添加数据:
// 添加数据
addBtn.onclick = () => {
if (!(username.value && password.value && age.value)) {
alert('请正确输入输入框信息!')
return
}
fetch('/api/user', {
method: 'post',
body: JSON.stringify({
username: username.value,
password: password.value,
age: age.value
}),
headers: {
"Content-Type": "application/json"
}
}).then(res => res.json()).then(res => {
// 添加完成后获取数据库中最新数据
getList(pageNum);
alert('添加成功!')
// 清空表单
username.value = ''
password.value = ''
age.value = ''
})
}
更新数据:
// 更新数据api
function updateFn(id) {
if (!(username.value && password.value && age.value)) {
alert('请正确输入输入框信息!')
return
}
fetch(`/api/user/${id}`, {
method: 'put',
body: JSON.stringify({
username: username.value,
password: password.value,
age: age.value
}),
headers: {
"Content-Type": "application/json"
}
}).then(res => res.json()).then(res => {
// 添加完成后获取数据库中最新数据
getList(pageNum);
alert('更新成功!')
// 清空表单
username.value = ''
password.value = ''
age.value = ''
})
}
删除数据:
// 删除数据api
function deleteFn(id) {
fetch(`/api/user/${id}`, {
method: 'delete',
}).then(res => res.json()).then(res => {
// 删除完成后获取数据库中最新数据
// newPageCount删除完数据后最新的总页数
let newPageCount = dataCount > 1 ? Math.ceil((dataCount - 1) / pageSize) : 1
if (pageNum > newPageCount) {
// 如果当前页数大于总页数则获取最后一页数据
getList(newPageCount)
} else {
getList(pageNum)
}
alert('删除成功!')
})
}
至此项目就全部完成啦!项目效果与文章开头展示的一样,项目最终的目录结构如下:
经过上面一系列的操作,我们的简易用户管理系统总算是搭建好了,但这时你会发现我们的这个项目写的好乱,比如routes
目录本该存放的是单纯的路由文件,但现在它还包含了操作数据库的代码:
并且这些操作数据库的代码也不是单纯的操作数据库,还掺杂了处理数据,返回数据的代码(如res.send
),这就使整个项目的业务变得特别混乱。
各部分之间相互掺杂,导致耦合度太高,这势必为之后的维护种下了风险的种子。
我们可以使用MVC架构,实现业务分层来解决这些问题,下一篇我们就将深入去学习MVC
架构,并使用它来重构这个简易用户管理系统,敬请期待!
博主的Node.js从入门到精通专栏正在持续更新中,关注博主订阅专栏学习Node不迷路!
如果本篇文章对你有所帮助,还请客官一件四连!❤️
基础不牢,地动山摇! 快来和博主一起来牛客网刷题巩固基础知识吧!