【Node.js实战系列】Express 从零搭建博客系统(一)
前言
哈喽,大家好,这里是胖芙,咱这系列的文章主打的就是一个通俗易懂。这个系列的文章会从零开始教你怎么使用 Express.js 去搭建一个能用的博客系统,是真的从零,手把手的那种!!!让你打通 JS 前后端任督二脉!
本系列文章也会收录在公众号《泡芙学前端》中,持续更新中... 欢迎关注
首先要知道我们现在要做个怎样的系统,比如博客能干嘛?
那它的核心功能肯定是对于文章的增删改查嘛,所以我们就有了以下的几个核心接口:
- 发表博客
- 分页查看博客列表
- 根据id查看博客详情
- 根据id删除博客(软删除)
- 根据id修改博客内容
- 上传文件(比如博客内的图片),前期我们先把图片存本地,后面可以改成上传到 OSS
好了,这几个接口搞完了其实博客系统的雏形就已经有了。
但是哎,好像还不太够,还缺点啥?所以接下来我们继续给博客添加功能,比如用户登录注册功能:
- 用户注册
- 用户登录
嗯,开始有点内味了,到这里一个基本的博客系统就其实已经可以自己用起来了。
接下来我们就继续锦上添花,比如我们为了让博客能分类,来添加一个标签系统,可以给博客打上分类的标签,比如 JavaScript、Node.js、React 等标签
- 新增标签
- 查询标签
- 删除标签(软删除)
到这里为止一个博客的雏形就已经有了,后续可以基于这个雏形来添加更多的功能,比如接入 Redis 缓存进行优化等,不过那都是后话了,我们这里就先把一个雏形从零开始搭建完成。
我们本系列教程只写后端,大家如果有兴趣的话可以自己写一下前端,或者如果后续反馈好,我也可以把前端部分给补上。
废话不多说,直接开始吧。首先我们先来完成基本环境的一个搭建工作哈
1.搭建初始环境
-
找到一个地方创建目录,比如 express-blog
-
进入目录,控制台运行
npm init -y
-
修改 package.json 中的 scripts 字段为下面代码,作为启动入口,推荐安装 nodemon 这个包,当文件变更时它可以自动重启服务
-
npm i nodemon -g
安装到全局 -
"scripts": { "start": "nodemon app.js" },
-
-
安装依赖:
npm install express sequelize mysql2 body-parser cors bcrypt jsonwebtoken multer
- express: 我们要用的服务端框架
- sequelize: 操作数据库的 orm
- mysql2: 数据库驱动
- body-parser: 用来解析 request body 的内容
- cors: 解决跨域
- bcrypt: 密码加密和解密
- jsonwebtoken: 登录时生成 token 下发
- multer: 用于上传文件
上面搞完后,我们就得到了这样的一个 package.json 文件
{
"name": "expree-blog",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"start": "nodemon app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.1.0",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"multer": "1.4.5-lts.1",
"mysql2": "^3.3.3",
"sequelize": "^6.31.1"
}
}
到这里我们就完成了第一步工作,接下来我们就要创建目录结构了,最终项目完成时的目录结构长这个样子
expree-blog // 根目录
├── app.js // 项目主入口
├── config // 数据库配置
│ └── database.js
├── controllers // 控制器,写逻辑的地方
│ ├── authController.js // 登录注册控制器
│ ├── blogController.js // 博客控制器
│ ├── fileController.js // 文件控制器
│ └── tagController.js // 标签控制器
├── middleware // 中间件
│ ├── authMiddleware.js // 鉴权中间件
│ └── fileMiddleware.js // 文件处理中间件
├── models // 数据库模型
│ ├── Blog.js
│ ├── Tag.js
│ └── User.js
├── package.json
├── routes // 路由
│ ├── auth.js
│ ├── blogs.js
│ ├── file.js
│ └── tags.js
└── tempFiles // 存储上传的文件
2.数据库配置
这里可以启动自己的本地 mysql 数据库,然后填入对应信息即可,不知道怎么安装 mysql 的可以网上查下相关教程,可以使用 docker 安装,或者直接去官网下载安装包然后装到本地。我自己的话是用的 Docker
const { Sequelize } = require("sequelize");
const sequelize = new Sequelize(
// 数据库名称
"your_database_name",
// 数据库用户名
"your_username",
// 数据库密码
"your_password",
{
// 如果是远程数据库,可以填写 ip 地址
host: "localhost",
dialect: "mysql",
}
);
module.exports = sequelize;
当数据库启动起来之后,改一下上面的参数即可
3.构建数据库模型
现在我们要在 models/
目录下创建下面这几个模型文件:
User.js
:用户模型Blog.js
:博客模型Tag.js
:标签模型
然后在各个模型中去定义相应的字段:
用户模型
// models/User.js
const { DataTypes, Model } = require("sequelize");
const sequelize = require("../config/database");
class User extends Model {}
User.init(
{
username: {
comment: "用户名",
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
password: {
comment: "密码",
type: DataTypes.STRING,
allowNull: false,
},
nickname: {
comment: "昵称",
type: DataTypes.STRING,
allowNull: false,
},
lastOnlineTime: {
comment: "最后登陆时间",
type: DataTypes.DATE,
allowNull: true,
},
},
{
sequelize,
modelName: "User",
}
);
module.exports = User;
博客模型
// models/Blog.js
const { DataTypes, Model } = require("sequelize");
const sequelize = require("../config/database");
const User = require("./User");
const Tag = require("./Tag");
class Blog extends Model {}
Blog.init(
{
title: {
comment: "标题",
type: DataTypes.STRING,
allowNull: false,
},
content: {
comment: "博客内容",
type: DataTypes.TEXT,
allowNull: false,
},
coverImage: {
comment: "封面图",
type: DataTypes.STRING,
},
isDeleted: {
comment: "是否已经删除",
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
},
{
sequelize,
modelName: "Blog",
}
);
// 一篇博客可以对应多个标签
// 一个标签也可以对应到多篇博客
// 博客与标签的关联关系
Blog.belongsToMany(Tag, {
through: "Blog_Tag",
as: "tags",
});
// 标签和博客的关联关系
Tag.belongsToMany(Blog, {
through: "Blog_Tag",
as: "blogs",
});
// 一篇博客只能属于一个用户
// 一个用户可以拥有多篇博客
// 博客与用户的关联关系
Blog.belongsTo(User, { foreignKey: "userId", as: "user" });
User.hasMany(Blog, { foreignKey: "userId", as: "user" });
module.exports = Blog;
标签模型
// models/Tag.js
const { DataTypes, Model } = require("sequelize");
const sequelize = require("../config/database");
class Tag extends Model {}
Tag.init(
{
name: {
comment: "标签名称",
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
isDeleted: {
comment: "是否已经删除",
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
},
{
sequelize,
modelName: "Tag",
}
);
module.exports = Tag;
4.初始化主入口
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const path = require("path");
const authRoutes = require("./routes/auth");
const blogRoutes = require("./routes/blogs");
const tagRoutes = require("./routes/tags");
const fileRoutes = require("./routes/file");
const sequelize = require("./config/database");
const app = express();
// 中间件
// 用来解析 post body x-www-form-urlencoded 格式数据
app.use(bodyParser.urlencoded({ extended: false }));
// 用来解析 post body json 格式数据
app.use(bodyParser.json());
// 处理跨域
app.use(cors());
// 提供静态文件访问
app.use("/tempFiles", express.static(path.join(__dirname, "tempFiles")));
// 路由
app.use("/auth", authRoutes);
app.use("/blogs", blogRoutes);
app.use("/tags", tagRoutes);
app.use("/upload", fileRoutes);
// 数据库同步
sequelize
.sync()
.then(() => {
console.log("Database synced");
})
.catch((error) => {
console.error("Error syncing database:", error);
});
// 启动服务器
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
5. 初始化路由
这里我们采用 Restful 风格的路由
登录注册路由
// routes/auth.js
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
router.post('/register', authController.register);
router.post('/login', authController.login);
module.exports = router;
博客路由
// routes/blogs.js
const express = require('express');
const router = express.Router();
const blogController = require('../controllers/blogController');
// 创建博客
router.post('/create', blogController.createBlog);
// 查询博客列表
router.get('/query', blogController.getBlogList);
// 根据 id 查询博客详情
router.get('/query/:id', blogController.getBlogById);
// 根据 id 修改博客
router.patch('/update/:id', blogController.updateBlog);
// 根据 id 删除博客
router.delete('/delete/:id', blogController.deleteBlog);
module.exports = router;
标签路由
// routes/tags.js
const express = require('express');
const router = express.Router();
const tagController = require('../controllers/tagController');
// 创建标签
router.post('/create', tagController.createTag);
// 查询所有标签
router.get('/query', tagController.getTags);
// 根据 id 删除标签
router.delete('/delete/:id', tagController.deleteTag);
module.exports = router;
文件路由
// routes/file.js
const express = require("express");
const router = express.Router();
const fileController = require("../controllers/fileController");
// 目前只有一个上传文件接口
router.post("/file", fileController.uploadFile);
module.exports = router;
6.初始化控制器
登录注册
// 注册
async function register(req, res) {
// todo
}
// 登录
async function login(req, res) {
// todo
}
module.exports = { register, login };
博客
async function createBlog(req, res) {
// todo
}
async function getBlogList(req, res) {
// todo
}
async function getBlogById(req, res) {
// todo
}
async function updateBlog(req, res) {
// todo
}
async function deleteBlog(req, res) {
// todo
}
module.exports = {
createBlog,
getBlogList,
getBlogById,
updateBlog,
deleteBlog,
};
标签
async function createTag(req, res) {
// todo
}
async function getTags(req, res) {
// todo
}
async function updateTag(req, res) {
// todo
}
async function deleteTag(req, res) {
// todo
}
module.exports = { createTag, getTags, updateTag, deleteTag };
上传文件
async function uploadFile(req, res) {
// todo
}
module.exports = { uploadFile };
小结
以上就是关于 Express 从零搭建博客系统的初始化相关的全部内容了~
下一篇文章我们将完成登录和注册功能~,来实现 token 的下发和校验功能
如果想要实时收到文章的更新,欢迎关注公众号《泡芙学前端》,同步更新文章中~
转载自:https://juejin.cn/post/7240342069997715511