Node.js - 使用 Express 实现服务端的 CRUD
最近突发奇想,试图简要了解一下小型服务端程序的构建方法,理由是每一次构建服务端 API 都只能使用 Spring Boot 结构,虽说 Java 在服务端开发中属于统治级地位,但是对于特殊的小型应用,使用低扩展、高性能的“迷你程序”显然是更好的选择。
于是在阅读了一些对比文章之后,我把焦点放在了动态语言 JavaScript 和 Python 上,它们对应的高性能框架分别为 Express (Node.js) 和 FastAPI。
参考文章: Python Web框架哪家强? Django vs Flask深度对比 - 知乎 (zhihu.com) WEB框架对比——Django、Flask、FastAPI - ''竹先森゜ - 博客园 (cnblogs.com) 用Django开发web后端,真的比SpringBoot要省事吗? - 知乎 (zhihu.com)
本文讲解 Express 访问数据库并实现服务端请求接口的过程。
Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能,提供精简的基本 Web 应用程序功能,而不会隐藏 Node.js 功能。许多流行的开发框架都基于 Express 构建。
环境准备
MySQL@8.0.27
node@12.13.0
npm@8.5.5
express@4.16.1
创建数据库和数据表:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
`account` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '账号',
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '名称',
`password` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
`phone` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '联系方式',
`email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '电子邮箱',
`status` int NOT NULL DEFAULT 1 COMMENT '状态',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1357608370719252483 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;
SET FOREIGN_KEY_CHECKS = 1;
node 项目内安装插件 express、mysql 和 axios:
npm install express --save
npm install mysql --save
npm install axios --save
核心代码
目录结构:
nodejs-express
├── conf │ └── db.config.js ├── dao │ ├── userDao.js │ └── userMapper.js ├── routes │ ├── index.js │ └── user.js ├── utils ├── app.js ├── package.json └── package-lock.json
连接数据库
通过相应的配置,使项目连接到指定的 MySQL 数据库,当连接成功时,控制台会输出“success”字样。
db.config.js
const mysql = require("mysql")
let database = '***' // 数据库名
let mysql_config = {
connectionLimit: 10, // 最大连接数
host: 'localhost',
user: 'root',
password: '******',
port: '3306',
database: database,
connectTimeout: 5000, // 连接超时
multipleStatements: false //是否允许一个query中包含多条sql语句
}
// 创建连接池
let pool = mysql.createPool(mysql_config)
pool.on('connection', msg => {
console.log(msg.state)
})
pool.on('error', err => {
console.log('database error.')
})
module.exports = {
pool
}
SQL 语句封装
根据 CURD 的特性定义五个查询语句。
userMapper.js
// 对数据库中的 user 表进行增,删,查操作语句
module.exports = {
addUser: "INSERT INTO user (account, name, password, phone, email) VALUES (?, ?, ?, ?, ?);",
deleteUser: "DELETE FROM user WHERE id = ?;",
queryUser: "SELECT * FROM user WHERE id = ?;",
queryAllUsers: "SELECT * FROM user;",
updateUser: "UPDATE user SET account=?, name=?, password=?, phone=?, email=? WHERE id=?;"
}
数据访问层(DAO 层)封装
根据查询语句创建相应的功能函数,在函数对请求和响应进行对应的格式化处理。
userDao.js
let { pool } = require("../conf/db.config")
let { addUser, deleteUser, queryUser, updateUser, queryAllUsers } = require('./userMapper')
module.exports = {
query: function (params = {}, callback) { // user 表中查询 user
if (params.id) {
let { id } = params
let sqlparam = [id]
pool.query(queryUser, sqlparam, (err, result) => {
if (err) throw err
callback({ code: 200, data: result })
})
} else {
pool.query(queryAllUsers, (err, result) => {
if (err) throw err
callback({ code: 200, data: result })
})
}
},
add: function (params, callback) { // user 表中增加 user
let sqlparam = [
params.account ? params.account : "",
params.name ? params.name : "",
params.password ? params.password : "",
params.phone ? params.phone : "",
params.email ? params.email : "",
]
pool.query(addUser, sqlparam, (err, result) => {
if (err) throw err
callback({ code: 200, data: result })
})
},
update: function (params, callback) { // user 表中更新指定 user
this.query(params, r => {
if (r.data.length === 0) {
callback({ code: 400, msg: 'No User.' })
} else {
let sqlparam = [
params.account ? params.account : "",
params.name ? params.name : "",
params.password ? params.password : "",
params.phone ? params.phone : "",
params.email ? params.email : "",
params.id
]
pool.query(updateUser, sqlparam, (err, result) => {
if (err) throw err
callback({ code: 200, data: result })
})
}
})
},
delete: function (params, callback) { // user 表中删除指定 user
this.query(params, r => {
if (r.data.length === 0) {
callback({ code: 400, msg: 'No User.' })
} else {
let { id } = params
let sqlparam = [id]
pool.query(deleteUser, sqlparam, (err, result) => {
if (err) throw err
callback({ code: 200, data: result })
})
}
})
}
}
实现请求接口
route/user.js
let express = require('express')
let bodyParser = require('body-parser')
// const { response } = require('../app')
let router = express.Router()
const userDao = require("../dao/userDao") // 数据交互层操作
// 获取指定用户信息 get 请求
router.get('/query', function (req, res, next) {
let urlParam = req.body
userDao.query(urlParam, r => {
if (r.data.length === 0) {
res.writeHead(200, {
'Content-Type': 'text/html;charset=utf-8'
})
res.write('<h2>No Users.</h2>')
res.end()
} else {
res.json(r.data)
}
})
})
// 添加用户 post 请求
router.post('/add', function (req, res, next) {
let urlParam = req.body
userDao.add(urlParam, r => {
if (r.code !== 200) {
res.write('fail.')
res.end()
} else {
res.write('Number of records added: ' + r.data.affectedRows)
res.end()
}
})
})
// 修改用户 post 请求
router.post('/update', function (req, res, next) {
let urlParam = req.body
userDao.update(urlParam, r => {
if (r.code !== 200) {
res.write('fail.')
res.end()
} else {
res.write('Number of records updated: ' + r.data.affectedRows)
res.end()
}
})
})
// 删除指定用户 get 请求
router.delete('/delete', function (req, res, next) {
res.writeHead(200, {
'Content-Type': 'text/html;charset=utf-8'
})
let urlParam = req.body
userDao.delete(urlParam, r => {
if (r.status === 200) {
res.write('Number of records deleted: ' + r.data.affectedRows)
res.end()
} else {
res.write(r.msg)
res.end()
}
})
})
module.exports = router
使用 res.write()
函数可以直接将字符流渲染在 HTML 页面上,别忘了使用 res.end()
结束字符流传输。
app.js
// 部分代码
var express = require('express')
var indexRouter = require('./routes/index')
var userRouter = require('./routes/user')
var app = express()
app.use(logger('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'public')))
app.use('/', indexRouter)
app.use('/user', userRouter)
module.exports = app
使用 Apipost 进行测试
查询操作
修改操作
Tips
使用 nodemon
实现项目热加载
express 项目并不像 react 等前端框架能够实时更新,即热加载,需要安装相关插件实现类似效果。
使用 nodemon 插件可以实现热加载。nodemon 可以检测文件状态,并自动执行程序关闭和启动的操作,当项目文件发生改变时,nodemon 会自动停止项目运行,然后重新启动,无需你自己操作,在使用上相当于是热加载了,但实际上是伪热加载。
安装过程:
npm install --save-dev nodemon # 安装为开发依赖
安装成功后,启动项目不再使用 npm start (等同于 node ./bin/www),而是:
nodemon ./bin/www
运行结果:
> wechat-nodejs@0.0.0 start
> nodemon ./bin/www
[nodemon] 2.0.19
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./bin/www`
热加载效果可以自行测试。
转载自:https://juejin.cn/post/7141670125844824100