Express+MongoDB服务端开发教程
本项目源码地址 my_express_server
参考资料 NodeJS服务端开发极速入门
准备工作
安装一些必要的全局依赖
# 全局暴力设置淘宝源
npm config set registry http://registry.npm.taobao.org/# 安装热更新工具nodemon(代码更新自动重启服务器)
npm i -g nodemon
创建Express工程
创建工程
# 创建文件夹并使用终端开发
cd my_express_server# 初始化工程
npm init -y# 安装依赖
npm i express mongodb multer jsonwebtoken
编辑入口文件
src/app.js
/* 引入依赖 */
const express = require("express");/* 创建express实例 */
const app = express();/* 定义路由接口 */
app.get("/", function (req, res) {res.send("Hello Express");
});/* 挂载到指定端口 */
const server = app.listen(8002, function () {const host = server.address().address;const port = server.address().port;console.log("应用实例,访问地址为 http://%s:%s", host, port);
});
配置启动脚本
package.json
"scripts": {"test": "echo \"Error: no test specified\" && exit 1","start": "nodemon src/app.js"},
运行工程
npm run start
路由派发
定义路由模块
定义首页路由
src/views/indexRouter.js
const express = require("express");const router = express.Router();/* 定义路由接口 */
// GET /
router.get("/", function (req, res) {res.send("Hello From indexRouter");
});// GET /headers
router.get("/headers", (req, res, next) => {res.send(JSON.stringify({headers: req.headers,}));
});/* 使用自定义中间件 */
router.get("/testtoken", loginCheck, (req, res, next) => res.end("test ok"));module.exports = router;
定义用户路由
src/views/userRouter.js
const express = require("express");
const controller = require("../controllers/userController");
const { getUser, updateUser } = require("../models/userModel");const userRouter = express.Router();userRouter.get("/", function (req, res, next) {res.send("用户首页");
});userRouter.post("/register", async (req, res) => {res.send("register")
});userRouter.post("/login", async (req, res) => {res.send("login")
});module.exports = userRouter;
派发路由到指定模块
src/app.js 核心代码
/* 引入路由 */
const indexRouter = require("./views/indexRouter");
const userRouter = require("./views/userRouter");
const fileRouter = require("./views/fileRouter");/* 添加路由中间件 */
app.use("/", indexRouter);
app.use("/user", userRouter);
app.use("/file", fileRouter);
使用Postman测试接口
GET http://localhost:8002/get
POST http://localhost:8002/post
POST http://localhost:8002/user/register
POST http://localhost:8002/user/login
读取GET/POST数据
配置全局中间件
src/app.js
const multer = require("multer");/* 添加全局中间件 */
app.use(express.json());//json读写支持
app.use(express.urlencoded({ extended: false }));//表单支持
读取查询参数
// GET http://localhost:8002/get?a=12&b=34
router.get("/get", function (req, res) {res.send(JSON.stringify({...req.query,}));
});
读取请求体
// POST http://localhost:8002/post
// router.post("/post", urlencodedParser, function (req, res) {router.post("/post", function (req, res) {// res.send(// JSON.stringify({// msg:"msg from /post",// ...req.body// })// );res.json({msg: "msg from /post",...req.body,});
});
MongoDB的安装和基本使用
MongoDB数据库简介
- 非关系型数据库(不能做关联查询),但小快轻,适合中小复杂度的工程
- 存储形式为【集合+Bson文档】,可以理解为【文件夹+JSON文件】(Bson即二进制存储的Json)
- Json数据形式为对象和数组的嵌套组合,跟JS语言具有天然亲和性
- 因此,NodeJS后台 + MongoDB数据库成为常用的黄金组合
下载安装MongoDB
社区版下载地址
- windows版一路傻瓜式安装即可
- mac版安装请参见:https://www.runoob.com/mongodb/mongodb-osx-install.html
MongoDB官方文档
MongoDB中文网文档
初始化数据库
- 使用客户端MongoDB Compass手动创建数据库
- 在Compass终端中输入命令
use 数据库名称;
即可切换到指定数据库
基本增删改查CRUD命令
# MongoDB数据存储结构
# MongoDB数据服务(后台进程-无界面)
# 其它数据库
# my_express_server数据库
# user collection(文件夹)
# xx.bson 文档(文件)
# MongoDB Compass(前台进程-有界面)# 切换到指定数据库(不存在则创建)
use my_express_server;# CRUD操作
# Create创建/增加 Retrieve获取 Update更新 Delete删除# 添加一条数据(如果集合user不存在会默认创建)
db.user.insertOne({ username:"heige",password:"123456" }
);# 一次性插入多条数据(如果集合products不存在会默认创建)
db.products.insertMany( [{ item: "card", qty: 15 },{ item: "envelope", qty: 20 },{ item: "stamps" , qty: 30 },{ item: "stamps" , qty: 40 },{ item: "stamps" , qty: 50 },
] );# 删除一条数据
db.products.deleteOne( { "_id" : ObjectId("62eb3d94ad6e2095b8ea917c") }
);# 删除多条数据
db.products.deleteMany( { "item" : "stamps" }
);# $gt = greaterThan
# $lt = lessThan gte
# $gte = greaterThan or Equal
# $lte = lessThan or Equal
# qty>40的producets数据都死光光
db.products.deleteMany( { "qty" : { $gte : 40 } }
);
# name为heige 且qty位于[40,80)区间
db.products.deleteMany( { "name":"heige","qty" : { $and:[{ $gte : 40 },{ $lt : 80 },]} }
);
# name为heige 且qty小于40或大于80
db.products.deleteMany( { "name":"heige","qty" : { $or:[{ $lt : 40 },{ $gt : 80 },]} }
);# 修改数据:item为card的第一条数据 设置qty为3
db.products.updateOne({ "item" : "card" },{ $set: { "qty" : 3 } }
);# 将所有item为card数据记录的qty设为3
db.products.updateMany({ "item" : "card" },{ $set: { "qty" : 3 } }
);# 查询所有产品数据
db.products.find();
db.products.find({});# 按条件查询:查询存货小于20的所有产品数据
db.products.find( { qty: { $lt: 20 } }
);# 每页10条 取第5页 (跳过前40条然后取10条)
db.products.find( { qty: { $lt: 20 } }
)
.skip(40)
.limit(10)
NodeJS操作MongoDB数据库
安装依赖
npm i mongodb
基本CRUD
参考 菜鸟教程
工具封装
基本配置
src/db/dbconfig.js
const url = "mongodb://localhost:27017/";
const dbname = "my_express_server"
const collections = {user:"user",
}module.exports = {url,dbname,collections
}
增删改查工具封装
src/db/operation.js
const { MongoClient, ObjectId } = require("mongodb");
const { url, dbname } = require("./config");/* 获取指定集合的连接对象 */
function getCollection(collectionName) {return new Promise((resolve, reject) => {MongoClient.connect(url, function (err, db) {// if (err) throw err;if (err) {reject(err);} else {resolve({collection: db.db(dbname).collection(collectionName),db,});}});});
}/* 通用回调:无论成败皆关闭数据库连接 */
function callback({ db, resolve, reject }, { res, err }) {console.log("db callback:res/err=",res,err);db.close();if (err) {reject(err);} else {resolve(res);}
}/* 向指定集合中添加数据 */
function execCreate(collectionName, content) {return new Promise(async (resolve, reject) => {try {const { collection, db } = await getCollection(collectionName);collection.insertOne(content, function (err, res) {callback({ db, resolve, reject }, { res, err });});} catch (err) {reject(err);}});
}/* 根据条件从指定集合查询数据 */
function execRetrieve(collectionName, whereOption = {}, { skip, limit } = {}) {console.log("db execRetrieve:whereOption", whereOption);console.log("db execRetrieve:skip/limit", skip, limit);const { MongoClient } = require("mongodb");// const url = "mongodb://localhost:27017/";return new Promise((resolve, reject) => {MongoClient.connect(url, function (err, db) {// if (err) throw err;if (err) {reject(err);} else {const dbo = db.db(dbname);let data = dbo.collection(collectionName).find(whereOption);if (skip && limit) {data = data.skip(skip).limit(limit);}data.toArray(function (err, result) {db.close();// 返回集合中所有数据// if (err) throw err;if (err) {reject(err);} else {// console.log("db execRetrieve:result=", result);resolve(result);}});}});});
}/* 更新数据 */
function execUpdate(collectionName, id, content) {console.log("db execUpdate:id/content=", id, content);return new Promise(async (resolve, reject) => {try {const { collection, db } = await getCollection(collectionName);collection.updateOne({ _id: ObjectId(id) },{$set:content},function (err, res) {callback({ db, resolve, reject }, { res, err });});} catch (err) {console.log("err=",err);reject(err);}});
}/* 删除数据 */
function execDelete(collectionName, id) {return new Promise(async (resolve, reject) => {try {const { collection, db } = await getCollection(collectionName);collection.deleteOne({ _id: ObjectId(id) }, function (err, obj) {db.close();if (err) {reject(err);} else {// console.log("文档删除成功");resolve({ msg: "删除文档成功" });}});} catch (err) {reject(err);}});
}/* 对外导出增删改查四大操作 */
module.exports = {execCreate,execRetrieve,execUpdate,execDelete,
};
MVC架构简介
- M = model = 模型层 = 只负责数据的CRUD操作
- V = view = 视图层 = 只负责对接用户请求
- C = controller = 控制层 = 负责具体业务逻辑的处理,其上游是视图层,下游是模型层;
- 视图层与模型层通常具有高度可复用性,不同工程的业务逻辑处理不同,只需修改控制层代码即可;
- 一个用户请求的真正流转次序是:View=>Controller=>Model,即视图层=>调度控制层=>数据模型层;
- MVC架构设计思想广泛应用于各种Web项目的前后端开发中;
实现注册
视图层实现
src/views/userRouter.js
...
const controller = require("../controllers/userController");
userRouter.post("/register", async (req, res) => {const ret = await controller.register(req.body);res.json(ret)
});
...
控制层实现
src/controllers/userController.js
const model = require("../models/userModel");/* 实际处理注册请求 */
async function register({ username, password }) {// 首先查询用户名是否存在const users = await model.getUser({ username });console.log("register:existedUsers=", users);if (!users.length) {return model.addUser({ username, password });} else {return Promise.resolve({ msg: "用户名已存在" });}
}module.exports = {register,
};
模型层实现
src/models/userModel.js
const db = require("../db/operation");
const collectionName = "user"function addUser(user) {console.log("userModel addUser");return db.execCreate(collectionName, user);
}module.exports = {addUser,
};
Token验证登录
什么是JWT登录鉴权
请参考 面试官系列
JWT-Token工具封装
src/utils/jwtUtil.js
const jsonwebtoken = require("jsonwebtoken");
// const jwtSecret = "test_key";
const { jwtSecret } = require("../config");class JWT {/* 生成token 返回token*/static generate(value, expires = "7 days") {console.log("JWT generate value",value);// value 为传入值, expires为过期时间,这两者都会在token字符串中题先try {return jsonwebtoken.sign(value, jwtSecret, { expiresIn: expires });} catch (e) {console.error("jwt sign error --->", e);return "";}}/* 校验token 返回载荷或false*/static verify(token) {try {// 如果过期将返回falsereturn jsonwebtoken.verify(token, jwtSecret);} catch (e) {console.error("jwt verify error --->", e);return false;}}
}
module.exports = JWT;/* 小案例 */
// (function () {// /* 载荷(角色/权限描述信息) */
// const payload = {// // uuid: "3455445-acuya7skeasd-iue7",
// // phone: 133409899625,
// username:"admin",
// password:"123456"
// };// // 生成token 有效时长20s
// const token = JWT.generate(payload, "3s");
// console.log("token", token);// // 校验token 得到payload
// const info = JWT.verify(token);
// console.log("verifiedRet", info);// /* 3秒后再次校验 */
// setTimeout(() => {// console.log("检验过期token");
// const info2 = JWT.verify(token);
// console.log("info2", info2); // false
// }, 3000);
// })();
登录实现
视图层实现
src/views/userRouter.js
const express = require("express");
const controller = require("../controllers/userController");const userRouter = express.Router();userRouter.post("/login", async (req, res) => {const ret = await controller.login(req.body);res.json(ret)
});module.exports = userRouter;
控制层实现
src/controllers/userController.js
const model = require("../models/userModel");
const JWT = require("../utils/jwtUtil");/* 实际处理登录请求 */
async function login({ username, password }) {const users = await model.getUser({ username, password });console.log("register:existedUsers=", users);// 如果登录成功 记录之let token = null;if (users.length) {token = JWT.generate({ username, password });console.log("login:token=", token);}return Promise.resolve({code: users.length > 0 ? 1 : 0,msg: users.length > 0 ? "登录成功" : "登录失败",token,});
}module.exports = {login,
};
模型层实现
src/model/userModel.js
const db = require("../db/operation");
const collectionName = "user"function getUser(user) {console.log("userModel getUser");return db.execRetrieve(collectionName,user);
}module.exports = {getUser,
};
登录鉴权
封装登录校验中间件
src/middlewars/loginCheck.js
/* cookie校验登录 */
// const loginCheck = function (req, res, next) {// if (!req.cookies["username"]) {// res.send("请先登录!");
// } else {// next();
// }
// };/* session校验登录 */
// const loginCheck = function (req, res, next) {// console.log("req.session", req.session);
// if (!req.session["username"]) {// res.send("请先登录!");
// } else {// next();
// }
// };/* token校验登录 */
const JWT = require("../utils/jwtUtil");
const regToken = /Bearer (.+)/
const loginCheck = function (req, res, next) {const token = regToken.exec(req.headers["authorization"])[1]const info = JWT.verify(token);console.log("verifiedRet", info);info ? next() : res.json({token,info,msg:"请先登录"});
};module.exports = loginCheck
为接口添加登录守卫
src/views/indexRouter.js
/* 登录校验中间件 */
const loginCheck = require("../middlewares/loginCheck");const router = express.Router();// GET /headers
router.get("/headers", (req, res, next) => {res.json({headers: req.headers,});
});/* 使用自定义中间件 */
router.get("/testtoken", loginCheck, (req, res, next) => res.end("test ok"));
提供静态资源服务
配置全局中间件
src/config.js
const path = require("path");const publicPath = path.resolve("public");
const imgPath = path.join(publicPath, "img");module.exports = {jwtSecret: "jinwandalaohu",publicPath,imgPath,
};
src/app.js
/* 引入配置项 */
const { publicPath } = require("./config");
console.log("publicPath", publicPath);app.use(express.static(publicPath));//静态文件支持
从浏览器发起测试
http://localhost:8002/img/fuckoff.jpg
上传文件
配置全局上传支持中间件
npm install multer
const multer = require("multer");
app.use(multer({ dest: "/tmp/" }).array("avitar"));//上传支持
控制层实现
src/views/fileRouter.js
const express = require("express");
const fs = require("fs");
const { imgPath } = require("../config");const fileRouter = express.Router();fileRouter.post("/upload", function (req, res) {console.log(req.files[0]); // 上传的文件信息// res.json({// files:req.files// })const des_file = imgPath + "/" + req.files[0].originalname;fs.readFile(req.files[0].path, function (err, data) {fs.writeFile(des_file, data, function (err) {if (err) {console.log(err);res.json(err)} else {response = {username:req.body.username,message: "File uploaded successfully",filename: req.files[0].originalname,};}console.log(response);res.json(response);});});});module.exports = fileRouter;
从前端页面发起POST上传请求
public/page/file_upload.html
<h3>单文件上传</h3>
<!-- 注意:enctype type="file" -->
<!-- action="/demo/upload" method="POST" name="avitar" 都要与服务端的配置吻合 -->
<form action="/file/upload" method="POST" enctype="multipart/form-data"><input name="username" type="text"><input type="file" name="avitar"><br><input type="submit" value="单个上传">
</form><h3>多文件上传</h3>
<!-- 注意:enctype type="file" -->
<!-- action="/file/upload" method="POST" name="avitar" 都要与服务端的配置吻合 -->
<form action="/file/upload" method="post" enctype="multipart/form-data"><input name="username" type="text"><input type="file" name="avitar" multiple /><input type="file" name="avitar" multiple /><br /><input type="submit" value="多个上传" />
</form>
获取与修改用户信息
视图层实现
src/views/userRouter.js
const express = require("express");
const controller = require("../controllers/userController");const userRouter = express.Router();/* PUT /user/heige */
userRouter.put(/\w+/, async (req, res) => {const users = await controller.getUser({ username: req.path.slice(1) })const result = await controller.updateUser(users[0]._id,req.body)res.json(result)
});/* GET /user/heige */
userRouter.get(/\w+/, async (req, res) => {const users = await controller.getUser({ username: req.path.slice(1) })res.json(users[0])
});module.exports = userRouter;
控制层实现
src/controllers/userController.js
const model = require("../models/userModel");/* 更新用户信息 */
async function updateUser(id,user){return model.updateUser(id,user)
}/* 获取用户信息 */
async function getUser(user){return model.getUser(user)
}module.exports = {updateUser,getUser
};
模型层实现
src/models/userModel.js
const db = require("../db/operation");
const collectionName = "user"function updateUser(id, user) {console.log("userModel updateUser:id",id);return db.execUpdate(collectionName, id, user);
}function getUser(user) {console.log("userModel getUser");return db.execRetrieve(collectionName,user);
}module.exports = {updateUser,getUser,
};
“这不需要测试,肯定是好的,不必担心”
Express+MongoDB服务端开发教程相关推荐
- NodeJS+Express+mySQL服务端开发详解
NodeJS+Express+mySQL服务端开发详解 随着NodeJS的发展,现在已经被很多人熟知,NodeJS已经成为了前端开发人员必备的技能.本文不会对NodeJS过多介绍 如果你感兴趣可以访问 ...
- github nodejs mysql_GitHub - lizhuohaicode/express: nodejs服务端开发(Express+Mysql)---小k博客...
nodejs服务端开发(Express+Mysql) 项目展示 git clone git@github.com:htmlk/express.git 2.再导入express.sql到数据库,数据库名 ...
- php手游服务端开发教程,【手游服务端】梦想海贼王 卡牌系列一键端服务端游戏源码+教程...
[手游服务端]梦想海贼王 卡牌系列一键端服务端游戏源码+教程 游戏介绍: <梦想海贼王>是一款卡牌类手游,游戏以全球第一超人气动漫<海贼王>为题材,用Q版风格配合新奇多样的玩法 ...
- rds基于什么开发_为什么不学基于TypeScript的Node.js服务端开发?
为什么不学?学不动了吗?!别躺下啊,我扶你起来! 我们早就知道,如今的JavaScript已经不再是当初那个在浏览器网页中写写简单的表单验证.没事弹个alert框吓吓人的龙套角色了.借助基于v8引擎的 ...
- 第13章 Kotlin 集成 SpringBoot 服务端开发(1)
第13章 Kotlin 集成 SpringBoot 服务端开发 本章介绍Kotlin服务端开发的相关内容.首先,我们简单介绍一下Spring Boot服务端开发框架,快速给出一个 Restful He ...
- mysql第五章项目二_Todo List:Node+Express 搭建服务端毗邻Mysql – 第五章(第1节)
点击右上方红色按钮关注"web秀",让你真正秀起来 前言 万丈高楼平地起,我们的Todo List项目也是越来越结实了.Todo List的前面4章内容都是在为Client端开发, ...
- WebFlux响应式编程基础之 5 webflux服务端开发讲解
https://blog.csdn.net/qq_27093465/article/details/64124330 debug技巧 第5章 webflux服务端开发讲解 Spring5 非组塞的开发 ...
- 百万在线:大型游戏服务端开发
进入手游时代,服务端技术也在向前演进.现代游戏服务端既要承载数以万计的在线玩家,又要适应快速变化的市场需求,因此,如何设计合适的架构就成了重中之重.服务端技术并不简单,作为服务端新人,全面掌握服务端技 ...
- 2016届360公司PHP服务端开发笔试和面试之所得所感
这是一篇叙述自己在360公司参加笔试和面试的过程,可能面试的职位并不是你所学的方向,但是如果你能从中学到些什么或者吸取我的教训,那么作者就非常知足了.本着"学习别人是怎么失败的,活着出来的人 ...
最新文章
- 2022-2028年中国汽车印制电路板(汽车PCB)产业深度调研及投资前景预测报告
- pytorch系列 -- 9 pytorch nn.init 中实现的初始化函数 uniform, normal, const, Xavier, He initialization...
- 信道检测手机软件 ios_【手机软件】云听:稀有神器,移动音频的国家队,某拉雅资源它都有!...
- C#心得与经验(二)
- 设置NPM/Electron国内源
- Windows Mobile 5 编程体验4
- Python接口测试
- UnityShader5:基本内置变量
- 打开MSDTC的方法(图解)
- java 调用felix_使用Eclipse启动任务将展开的软件包部署到Apache Felix
- 一款【免费+简单+好用+性能强大】的词云(Wordcloud)制作工具(含详细介绍)
- matlab数字信号处理(1)——正弦信号生成与时域分析
- Java深克隆和浅克隆的原理及实现
- oracle模板数据文件,Oracle EBS如何通过命令上传XML/BI Publisher数据定义文件和模板文件...
- level2买股技巧_同花顺level2使用技巧
- 【音视频】流媒体直播实时视频延迟时间排查和剖析:gop关键帧间隔导致延迟,流媒体和播放器缓存,B帧等导致的延迟
- 繁凡的对抗攻击论文精读(二)CVPR 2021 元学习训练模拟器进行超高效黑盒攻击(清华)
- 微信小程序宠物论坛4
- 点击body提示自由民主的网页代码
- opencv for python的图像梯度算子以及canny边缘检测