我是彭于晏爱编程,我喂自己袋盐
记录一次前端转全栈的踩坑之旅,方便自己查阅,也期望对同样想 "速成" 的朋友有所帮助。
Node.js 全栈示例:从 Express 脚手架到 JWT 鉴权
1. 背景与动机
页面仔也想成为全栈,想快速了解后端架构而不想学习其他语言、降低成本,所以选择了 Node.js + Express。Express 官网:www.expressjs.com.cn/
2. 使用 Express Generator 初始化项目
# 全局安装(可选)
# npm install -g express-generator
# 1. 使用 npx 直接执行
npx express-generator --view=pug myapp
# 2. 进入目录 & 安装依赖
cd myapp
npm install
# 3. 启动开发服务器
npm start
目录结构
.
├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.pug
├── index.pug
└── layout.pug
7 directories, 9 files
Tips:若目录截图或结构图在原稿中以图片形式展示,请保留原图片,无需替换。
3. 数据库选型
Express 可配合多种数据库(MySQL、MongoDB、DynamoDB…)。这里选用 MySQL,辅以官方可视化工具 MySQL Workbench。
建库 & 建表(示例)
-- 创建数据库
CREATE DATABASE IF NOT EXISTS express_demo
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
-- 选择数据库
USE express_demo;
-- 创建示例表 project_objects
CREATE TABLE project_objects (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
更多增删改查(CRUD)语句本文不赘述,可参见“附录:常用 SQL 备忘”。
4. 连接数据库
在项目根目录下创建 db/
文件夹,并新增 mysql.js
:
// db/mysql.js
import dbConfig from "../config/default.js";
import mysql from "mysql2";
const connection = mysql.createConnection({
host: dbConfig.db.host,
user: dbConfig.db.user,
password: dbConfig.db.password,
database: dbConfig.db.database,
port: dbConfig.db.port,
connectTimeout: dbConfig.db.connectTimeout, // 10s
});
console.log("完整的 dbConfig:", dbConfig);
connection.connect(err => {
if (err) {
console.error("数据库连接失败:", err);
} else {
console.log("数据库连接成功");
}
});
export default connection;
config/default.js
export default {
port: 8088,
db: {
host: "localhost",
user: "express_user",
password: "smq111222",
database: "express_demo",
port: 3306,
connectTimeout: 50000,
},
// 生成 JWT 的私钥
PRIVATE_KEY: "express-demo",
};
5. 编写接口示例
以 about
相关接口为例,新建 routes/about.js
:
import express from "express";
import db from "../../db/mysql.js";
const router = express.Router();
// POST /about/get-detail
router.post("/get-detail", async (req, res) => {
try {
// 1. 查询数据库
const [dbData] = await db.promise().query("SELECT * FROM project_objects");
res.json({
code: 200,
message: "获取成功",
data: dbData,
});
} catch (err) {
// 失败回退到 mock
res.json({
code: 200,
message: "数据库查询失败,返回备用数据",
data: null,
});
}
});
export default router;
亮点:同样的 JS 语法,前端同学阅读无压力。
6. 登录 & JWT 鉴权
6.1 登录接口(返回 token + userId + role
)
登录成功后前端需把 Bearer <token>
写入 Authorization
头,并在请求中附带 userId
、防重放签名 sig
、一次性随机串 bounce
等安全字段(可按需拓展)。
6.2 鉴权中间件 middleware/auth.js
import jwt from "jsonwebtoken";
import DefaultConfig from "../config/default.js";
function auth(req, res, next) {
// 1. 放行登录
if (req.path === "/newUser/login" && req.method === "POST") {
return next();
}
// 2. 读取 Authorization
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(400).json({
code: 4001,
message: "缺少有效的 Authorization 头(格式应为 Bearer <token>)",
});
}
const token = authHeader.split(" ")[1];
// 3. 校验 userId
const userId = req.body.userId || req.query.userId;
if (!userId) {
return res.status(400).json({
code: 4002,
message: "缺少请求参数 userId",
});
}
// 4. 验证 token
jwt.verify(token, DefaultConfig.PRIVATE_KEY, (err, decoded) => {
if (err) {
return res.status(401).json({
code: 4003,
message: "无效的 token",
});
}
if (decoded.id !== Number(userId)) {
return res.status(403).json({
code: 4004,
message: "token 与用户 ID 不匹配",
});
}
req.user = decoded; // 挂载用户信息
next();
});
}
export { auth };
7. 应用入口 app.js
import express from "express";
import Route from "./routes/index.js";
import defaultConfig from "./config/default.js";
import bodyParser from "body-parser";
import cors from "cors";
import middleware from "./middleware/index.js";
import { auth } from "./middleware/auth.js";
const app = express();
// 记录耗时 & 日志
app.use(middleware.timer);
app.use(middleware.logger);
// 跨域 & 解析 body
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// 静态资源
app.use(express.static("public"));
// 访问示例:http://localhost:3000/public/images/bg.jpg
// 鉴权中间件(登录接口已在 auth 内部放行)
app.use(auth);
// 总路由挂载
app.use("/", Route);
app.listen(defaultConfig.port, () => {
console.log(`服务器启动成功,端口:${defaultConfig.port}`);
});
8. 至此,你已不再是“页面仔”!
- 数据库建库、建表、CRUD —— 拿捏
- 接口编写、JWT 鉴权 —— 拿捏
- 静态资源、日志、跨域、中间件 —— 拿捏
全栈雏形已经跑起来 🎉🎉🎉
PS:本文只是个人学习笔记,若有疏漏欢迎指正。
附录:常用 SQL 备忘(可选)
-- 新增
INSERT INTO project_objects (name, description) VALUES ('Demo', '示例');
-- 查询
SELECT * FROM project_objects;
-- 更新
UPDATE project_objects SET description = '更新' WHERE id = 1;
-- 删除
DELETE FROM project_objects WHERE id = 1;