一、创建Express项目
bash
// 使用express-generator脚手架
npm install -g express-generator@4
// 创建项目
express --no-view clwy-api
// 进入 clwy-api
npm install
// 安装 nodemon
npm intall nodemon
- 将项目中
routes
文件夹中文件的res.render
改为res.json
- 删除
public
文件夹下的index.html
- 将
package.json
中项目的script.start
由"node ./bin/www"
改为"nodemon ./bin/www"
, 让项目可以实时更新
二、使用Docker安装Mysql
- 从官网下载
Docker
- 打开
Docker
的设置->Docker Engine
,添加国内的镜像"registry-mirrors": [ "https://docker.1ms.run" ]
- 进入项目的根目录建立
docker-compose.yml
文件 和config/my.cnf
文件
yml
version: "3.8"
services:
mysql:
image: mysql:8.0
container_name: mysql_dev
environment:
MYSQL_ROOT_PASSWORD: rootpassword # 必填
MYSQL_DATABASE: app_db # 自动创建数据库
MYSQL_USER: dev_user # 自动创建普通用户
MYSQL_PASSWORD: userpass
TZ: Asia/Shanghai # 设置时区[5,6](@ref)
ports:
- "3306:3306" # 主机端口:容器端口
volumes:
- ./mysql_data:/var/lib/mysql # 数据持久化[2,4](@ref)
- ./config/my.cnf:/etc/mysql/conf.d/my.cnf # 自定义配置[3](@ref)
restart: unless-stopped # 异常退出自动重启[3](@ref)
cnf
[mysqld]
character-set-server = utf8mb4 # 支持中文及Emoji
collation-server = utf8mb4_unicode_ci
max_connections = 200 # 最大连接数
default_authentication_plugin=mysql_native_password # 兼容旧客户端[6](@ref)
- 运行
docker-compose up -d
MySQL服务端 就会自动下载,同时在Docker中也能看到clwy-api
项目 - 下载MySQL客户端,
macOS
直接在苹果商店搜索Sequel Ace
进行下载即可。
txt
TCP/IP
Name: 本机
Host: 127.0.0.1
UserName: root
Password: rootpassword // 在docker-compose.yml中写有
点击 Test connection
成功后 点击 Add to Favorites
三、操作数据库
- 创建数据库
- 创建数据表

- 常用SQL语句
sql
-- 插入
insert into 表名 (列1,...) values (值1, ...);
-- 多行插入
insert into 表名 (列1,...) values (值1, ...),(值1, ...);
-- 更新
update 表名 set 列1=值1, 列2=值2, ... where 条件
-- 删除
delete from 表名 where 条件
-- 查询
select * from 表名

四、使用Sequelize ORM
开发过程中一般是不会通过 SQL语句
直接操作数据库的,会使用 ORM
来操作数据库,ORM
是数据库和编程语言之间建立的一种 映射关系
,可以通过十分简单的代码来进行数据库的各种操作
- 安装Sequelize
npm i -g sequelize-cli
npm i sequelize mysql2
sequelize init

- 文件说明
config.json
存放的是sequelize
所需要连接到数据库的配置文件migrations
迁移,对数据表做新增表、修改字段、删除表等等操作models
存放的是模型文件,使用sequelize
执行增删改查操作,每个模型文件都对应数据库的一张表seeders
存放的是种子文件,需要添加到数据表的测试数据
- 模型、迁移和种子
- 修改
config/config.json
文件
json
{
"development": {
"username": "root",
"password": "rootpassword",
"database": "clwy_api_development",
"host": "127.0.0.1",
"dialect": "mysql",
"timezone": "+08:00"
},
"test": {
"username": "root",
"password": null,
"database": "clwy_api_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "clwy_api_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
sequelize model:generate --name Article --attributes title:string,content:text
新建模型文件命令
txt
models/article.js
migrations/20250804092221-create-article.js
sequelize db:migrate
迁移创建数据库表

- 修改
seeders/20250804092904-article.js
文件
js
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
const articles = []
const counts = 100
for(let i = 1; i < counts; i++) {
const article = {
title: `文章的标题${i}`,
content: `文章的标题${i}`,
createdAt: new Date(),
updatedAt: new Date()
}
articles.push(article)
}
await queryInterface.bulkInsert('Articles', articles, {})
},
async down (queryInterface, Sequelize) {
}
};
sequelize db:seed --seed 20250804092904-article
插入数据

五、Apifox的使用
Apifox 用来调试API的工具
六、接口
1. 查询文章列表 GET /admin/articles
js
// routes/admin/articles.js
const express = require('express');
const router = express.Router();
const {Article} = require('../../models')
router.get('/', async function(req, res, next) {
try {
const condition = {
order: [['id', 'DESC']]
}
const articles = await Article.findAll(condition)
res.json({
status:true,
message: '查询文章列表成功',
data: {
articles
}
})
}catch(error) {
res.status(500).json({
status:false,
message: '查询文章列表失败',
errors: [error.message]
})
}
});
module.exports = router;
js
// app.js
const adminArticlesRouter = require('./routes/admin/articles');
app.use('/admin/articles', adminArticlesRouter);
2. 查询单条文章 GET /admin/artilces/:id
js
// routes/admin/articles.js
router.get('/:id', async function(req, res, next) {
try {
const {id} = req.params
const article = await Article.findByPk(id)
if(article) {
res.json({
status:true,
message: '查询文章成功',
data: article
})
} else {
res.status(404).json({
status:false,
message: '文章未找到',
})
}
} catch(error) {
res.status(500).json({
status:false,
message: '查询文章失败',
errors: [error.message]
})
}
})
3. 创建文章 POST /admin/articles
js
router.post('/', async function(req, res, next) {
try {
const articles = await Article.create(req.body)
res.status(201).json({
status:true,
message: '创建文章成功',
data: {
articles
}
})
}catch(error) {
res.status(500).json({
status:false,
message: '创建文章失败',
errors: [error.message]
})
}
});
4. 删除文章 DELETE /admin/artilces/:id
js
router.delete('/:id', async function(req, res, next) {
try {
const {id} = req.params
const article = await Article.findByPk(id)
if(article) {
await article.destroy()
res.json({
status:true,
message: '删除文章成功',
})
} else {
res.status(404).json({
status:false,
message: '文章未找到',
})
}
} catch(error) {
res.status(500).json({
status:false,
message: '删除文章失败',
errors: [error.message]
})
}
})
5. 更新文章 PUT /admin/artilces/:id
js
router.put('/:id', async function(req, res, next) {
try {
const {id} = req.params
const article = await Article.findByPk(id)
if(article) {
await article.update(req.body)
res.json({
status:true,
message: '更新文章成功',
})
} else {
res.status(404).json({
status:false,
message: '文章未找到',
})
}
} catch(error) {
res.status(500).json({
status:false,
message: '更新文章失败',
errors: [error.message]
})
}
})
6. 模糊查询 GET /admin/articles?title=
js
const {Op} = require('sequelize')
router.get('/', async function(req, res, next) {
try {
const query = req.query
const condition = {
order: [['id', 'DESC']]
}
if(query.title) {
condition.where = {
title: {
[Op.like]: `%${query.title}%`
}
}
}
const articles = await Article.findAll(condition)
res.json({
status:true,
message: '查询文章列表成功',
data: {
articles
}
})
}catch(error) {
res.status(500).json({
status:false,
message: '查询文章列表失败',
errors: [error.message]
})
}
});
7. 数据分页 GET /admin/articles?currentPage=&pageSize=
js
router.get('/', async function(req, res, next) {
try {
const query = req.query
const currentPage = Math.abs(Number(query.currentPage)) || 1
const pageSize = Math.abs(Number(query.pageSize)) || 10
const offset = (currentPage - 1) * pageSize
const condition = {
limit: pageSize,
offset
}
const {count, rows} = await Article.findAndCountAll(condition)
res.json({
status:true,
message: '查询文章列表成功',
data: {
articles: rows,
pagination: {
total: count,
currentPage,
pageSize
}
}
})
}catch(error) {
res.status(500).json({
status:false,
message: '查询文章列表失败',
errors: [error.message]
})
}
});
七、问题
1. 白名单过滤表单数据
js
// routes/admin/articles.js
router.post('/', async function(req, res, next) {
try {
const body = filterBody(req)
const articles = await Article.create(body)
res.status(201).json({
status:true,
message: '创建文章成功',
data: {
articles
}
})
}catch(error) {
res.status(500).json({
status:false,
message: '创建文章失败',
errors: [error.message]
})
}
});
function filterBody(req) {
return {
title: req.body.title,
content: req.body.content
}
}
2. 验证表单数据
js
// models/article.js
Article.init({
title: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notNull: {
msg: '标题必须存在'
},
notEmpty: {
msg: '标题不能为空'
},
len: {
args:[2,45],
msg: '标题长度需要在2~45个字符之间'
}
}
},
content: DataTypes.TEXT
}, {
sequelize,
modelName: 'Article',
});
js
// routes/admin/articles.js
router.post('/', async function(req, res, next) {
try {
const body = filterBody(req)
const articles = await Article.create(body)
res.status(201).json({
status:true,
message: '创建文章成功',
data: {
articles
}
})
}catch(error) {
if(error.name === 'SequelizeValidationError') {
const errors = error.errors.map(e => e.message)
res.status(400).json({
status:false,
message: '请求参数错误',
errors
})
} else {
res.status(500).json({
status:false,
message: '创建文章失败',
errors: [error.message]
})
}
}
});
八、对以上增删查改 封装响应优化代码
js
// utils/response.js
class NotFoundError extends Error {
constructor(message) {
super(message)
this.name = 'NotFoundError'
}
}
function success(res, message, data = {}, code = 200) {
res.status(code).json({
status:true,
message,
data
})
}
function failure(res, error) {
if(error.name == 'SequelizeValidationError') {
const errors = error.errors.map(e => e.message)
return res.status(400).json({
status:false,
message: '请求参数错误',
errors
})
}
if(error.name === 'NotFoundError') {
return res.status(404).json({
status:false,
message: '资源不存在',
errors: [error.message]
})
}
res.status(500).json({
status:false,
message: '服务器错误',
errors: [error.message]
})
}
module.exports = {
NotFoundError,
success,
failure
}
js
// routes/admin/articles.js
const express = require('express');
const router = express.Router();
const {Article} = require('../../models')
const {Op} = require('sequelize')
const {NotFoundError, success, failure} = require('../../utils/response')
router.get('/', async function(req, res, next) {
try {
const query = req.query
const currentPage = Math.abs(Number(query.currentPage)) || 1
const pageSize = Math.abs(Number(query.pageSize)) || 10
const offset = (currentPage - 1) * pageSize
const condition = {
order: [['id', 'DESC']],
limit: pageSize,
offset
}
if(query.title) {
condition.where = {
title: {
[Op.like]: `%${query.title}%`
}
}
}
const {count, rows} = await Article.findAndCountAll(condition)
success(res, '查询文章列表成功', {
articles: rows,
pagination: {
total: count,
currentPage,
pageSize
}
})
}catch(error) {
failure(res, error)
}
});
router.get('/:id', async function(req, res, next) {
try {
const article = await getArticle(req)
success(res, '查询文章成功', {article})
} catch(error) {
failure(res, error)
}
})
router.post('/', async function(req, res, next) {
try {
const body = filterBody(req)
const article = await Article.create(body)
success(res, '创建文章成功', {article}, 201)
}catch(error) {
failure(res, error)
}
});
router.delete('/:id', async function(req, res, next) {
try {
const article = await getArticle(req)
await article.destroy()
success(res, '删除文章成功')
} catch(error) {
failure(res, error)
}
})
router.put('/:id', async function(req, res, next) {
try {
const body = filterBody(req)
const article = await getArticle(req)
await article.update(body)
success(res, '更新文章成功')
} catch(error) {
failure(res, error)
}
})
async function getArticle(req) {
const {id} = req.params
const article = await Article.findByPk(id)
if(!article) {
throw new NotFoundError(`ID: ${id}的文章未找到。`)
}
return article
}
function filterBody(req) {
return {
title: req.body.title,
content: req.body.content
}
}
module.exports = router;
学习视频地址:bilibili