node+express+jwt+sequelize+mysql+本地服务器部署前端+云服务器公网部署:入门教程
前言:展示通过Express应用程序生成器创建服务,连接mysql,前后端接口通信,前端包部署到服务,适合懂前端但是不太懂node创建服务进行前后的交互及服务器部署,为入门教程,很多地方还需优化,大佬勿喷
一、创建Express应用
本教程基于node:20.13.1,前端是react19+vite
首先通过Express应用程序生成器(Express 应用程序生成器 - Express中文文档 | Express中文网)创建应用。
你可以通过 npx
(包含在 Node.js 8.2.0 及更高版本中)命令来运行 Express 应用程序生成器。
$ npx express-generator
对于较老的 Node 版本,请通过 npm 将 Express 应用程序生成器安装到全局环境中并使用:
$ npm install -g express-generator
$ express
例如,如下命令创建了一个名称为 myapp 的 Express 应用。此应用将在当前目录下的 myapp 目录中创建,并且设置为使用 Pug 模板引擎(view engine):
$ express --view=pug myappcreate : myappcreate : myapp/package.jsoncreate : myapp/app.jscreate : myapp/publiccreate : myapp/public/javascriptscreate : myapp/public/imagescreate : myapp/routescreate : myapp/routes/index.jscreate : myapp/routes/users.jscreate : myapp/public/stylesheetscreate : myapp/public/stylesheets/style.csscreate : myapp/viewscreate : myapp/views/index.pugcreate : myapp/views/layout.pugcreate : myapp/views/error.pugcreate : myapp/bincreate : myapp/bin/www
然后安装所有依赖包:
$ cd myapp
$ npm install
二、添加自动重启服务工具nodemon
具体安装express参照官网教程即可,安装完成后再安装一个nodemon
nodemon是一个用于Node.js开发的工具,它能自动监视文件变化并重启应用,显著提升开发效率。它通过替代传统的`node命令运行脚本,无需修改代码即可实现实时监控和自动重启功能。
然后修改package.json中的启动命令:通过nodemon启动可实现实时监控和自动重启功能
三、启动服务
执行npm run dev启动服务默认是http://127.0.0.1:3000 浏览器输入地址即可访问
可以在bin/www.js中修改端口为80,方便访问
展示页面如下:
在app.js中是整个应用的配置,包括路由,全局中间件,token校验等等。
记得安装cors允许跨域:npm i cors(cors - npm)
安装好在app.js引入:
const cors = require('cors');app.use(cors({// origin: 'http://localhost:5173', // 可以限定请求来源// credentials: true, // 允许跨域请求携带 cookie
}));
app.js中可以看到有两个路由:
app.use('/', indexRouter);
app.use('/users', usersRouter);
可以到routes文件夹下看到它的路由配置:
(这里你可以通过res.cookie去设置cookie,req.cookie获取请求头中的cookie,res.clearCookie清除cookie,感兴趣的可以去操作操作,这里不做赘述,后面讲jwt)
当你访问/,他会默认渲染渲染views文件夹下的index页面:
如果你想返回文本或者html或者文件都可以通过send发送:
如:
res.send('<h1 style="color: red">hello world</h1>')
res.sendFile(path.resolve(__dirname, '../public/index.html'))
目前是前后端不分离的,这个先不管,后面我们会配置访问前端页面。
到此,服务启动成功可访问页面
三、安装mysql,访问数据库
前后端数据交互你也可以用本地json文件取模拟数据,这里我们讲mysql
https://dev.mysql.com/downloads/mysql/安装mysql9.3,安装完成后可以到终端运行mysql -V查看是否安装完成:
执行 mysql -u root -p 后输入密码可以连接数据库了。
四、数据库管理Navicat Premium
下载数据库管理Navicat Premium软件,更直观方便可视化的操作数据库,随便你去哪下载,正版还是盗版
Navicat17免安装下载 数据库管理Navicat Premium v17.1.12 中文绿色免费版 64位 下载-脚本之家
下载完成后连接mysql:
连接成功后可查看数据库、表、字段进行操作:
我建了2个表,product和user,用户登录后查看商品。(我不是专业后端,随便建建的,测试学习用),你也可以自己建几个表后面用。
五、node连接mysql,写接口,操作数据库
安装更先进的mysql2:npm i mysql2
你可以通过直接连接mysql写sql语句操作数据:
const mysql = require('mysql2/promise'); // 使用更先进的mysql2
const pool = mysql.createPool({host: 'localhost',user: 'root',port: '3306',password: '759859479',database: 'product', // 数据库名connectionLimit: 10,waitForConnections: true,
});async function query (sql, values) {let connection = await pool.getConnection();try {const [rows, fields] = await connection.query(sql, values);return rows;} catch (err) {throw err;} finally {connection?.release()}
}// 获取列表
router.get('/listPage', async (req, res) => {try {const records = await query('SELECT * FROM product'); // 调用连接数据库查询res.json({code: 200,data: {records},success: true});} catch (err) {res.status(500).json({ error: 'Internal Server Error' });}
});
但是上面这种方式不太方便,我们可以使用sequelize更好的去操作数据库
六、通过Sequelize操作数据库
我们可以使用sequelize更好的去操作数据库Sequelize 简介 | Sequelize中文文档 | Sequelize中文网
1.首先配置sql
2.创建连接
3.定义模型(与数据库字段对应)
4.操作数据库
写一个product路由在routes文件夹,别忘了在app.js引入
app.js引入:
内容如下:
const express = require('express');
const router = express.Router();// 通过sequelize的模型获取mysql数据
const ProductModel = require('../models/product');// 获取列表
router.get('/listPage', async (req, res) => {try {const records = await ProductModel.findAll(); // 调用模型查询res.json({code: 200,data: {records},success: true});} catch (err) {res.status(500).json({ error: 'Internal Server Error' });}
});// 创建
router.post('/create', async (req, res) => {try {await ProductModel.create(req.body);res.json({code: 200,data: true,success: true});} catch (err) {res.status(500).json({ error: 'Internal Server Error' });}
});// 删除
router.delete('/delete/:id', async (req, res) => {try {await ProductModel.destroy({where: {id: req.params.id}});res.json({code: 200,data: true,success: true});} catch (err) {res.status(500).json({ error: 'Internal Server Error' });}
})// 修改
router.post('/update', async (req, res) => {try {await ProductModel.update(req.body, {where: {id: req.body.id}});res.json({code: 200,data: true,success: true});} catch (err) {res.status(500).json({ error: 'Internal Server Error' });}
});module.exports = router;
七、前端调用接口发起请求
axios配置baseurl
import axios from 'axios';const env = import.meta.env.VITE_ENV;
const urlMap = {development: 'http://127.0.0.1', // 本地node服务
};export const BaseURL = urlMap[env];const request = axios.create({baseURL: BaseURL,timeout: 30000,
});
接口文件中配置api,请求路径就是你node服务中的路由
import request from '@/http/axios';export const ProdApi = {getListPageNode: params => request.get(`product/listPage`, { params }),createOrUpdateNode: params => request.post(`product/${params.id ? 'update' : 'create'}`, params),deleteNode: id => request.delete(`product/delete/${id}`),getByIdNode: id => request.get(`product/detail/${id}`),
};
请求成功后浏览器控制台可以看到:
到此成功进行前后端交互,具体后端返回数据解构和前端解析返回的数据的方式根据个人需求自己配置
八、添加鉴权:token,jwt
安装jsonwebtoken:npm i jsonwebtoken(jsonwebtoken - npm)
配置密钥:
新建jwt.js写中间件:
const jwt = require('jsonwebtoken'); // 导入jsonwebtoken
const { secret } = require('../config/config'); // 导入密钥const authJwt = (req, res, next) => {// 从请求头中获取 tokenconst authHeader = req.headers['authorization'];// 检查是否存在 tokenif (!authHeader) {return res.status(401).json({ code: 401, message: '未提供 token' });}// 提取 Bearer tokenconst token = authHeader.split(' ')[1];if (!token) {return res.status(401).json({ code: 401, message: '无效的 token 格式' });}// 验证 tokenjwt.verify(token, secret, (err, decoded) => {if (err) {return res.status(403).json({ code: 403, message: 'token 无效或已过期' });}// 将解码后的用户信息挂载到 req 上,供后续处理函数使用req.user = decoded;next();});
};module.exports = authJwt;
在app.js中导入jwt中间件并执行:
const authJwt = require('./token/jwt.js'); // 导入中间件// token校验,写在路由之前
app.use((req, res, next) => {// 可以根据需要配置白名单跳过 Token 校验if (req.path.startsWith('/product')) {authJwt(req, res, next); // product开通的才执行 Token 校验} else {next(); // 跳过 Token 校验}
});// 路由
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/product', productRouter);
九、创建登录接口返回token给前端
var express = require('express');
var router = express.Router();
const UserModel = require('../models/user'); // mysql的user模型
const jwt = require('jsonwebtoken'); // 导入jwt
const { secret } = require('../config/config'); // 导入jwt的密钥// 创建token,传入token内容,密钥,过期时间
function createToken (username, id) {return jwt.sign({ username, id }, secret, { expiresIn: '4h' });
}// 登录接口
router.post('/token', async (req, res, next) => {try {const { username, password } = req.query; // 获取query参数:用户名和密码const user = await UserModel.findOne({ // 查询数据库where: {username,password}});if (user) { // 找到用户数据const val = user.dataValues// 创建tokenconst token = createToken(val.username, val.userId)// 返回用户信息res.json({code: 200,data: {...val,token},success: true});} else {res.json({code: 2000,message: '用户名或密码错误',success: true});}} catch (err) {console.log(err);res.status(500).json({ error: 'Internal Server Error' });}
});module.exports = router;
前端相关代码:
// 接口文件的登录接口
loginNode: params => request.post(`users/token?${params}`),// 登录页面调用登录接口
const { data } = await LoginApi.loginNode(uRLSearchParams({username, password})).finally(() => setLoading(false));
dispatch(setUser(data));
dispatch(setToken(data.token));// axios配置文件
// http request 拦截器
request.interceptors.request.use(config => {const { token } = store.getState().user;if (config.url.includes('users/token?')) {// 登录接口} else {if (!token) {$notify.error('缺少token');} else {// 请求头添加tokenconfig.headers.Authorization = `Bearer ${token}`;config.headers.token = token;}}return config;},err => {return Promise.reject(err);}
);
这样前端每次请求都会请求头带上token,后端jwt中间件每次都会执行jwt校验做对应处理:
十、前端打包,node本地部署前端项目
前端项目执行npm run build,得到dist包,将dist包放到express项目的public文件夹下:
这是node配置的静态资源目录,里面的文件是可以直接获取的:
比如在浏览器输入:http://127.0.0.1/images/test.png
便可以访问public/images/test.png,访问时不用写public
静态资源目录是在app.js中配置:不一定要public,也可以自定义
app.use(express.static(path.join(__dirname, 'public')));
将dist文件下内容放到public后,当访问http://127.0.0.1/根路径时会默认读取index.html,显示前端页面,执行前端路由跳转了
如果你用的是前端路由用的history模式,部署后刷新页面导致前端路由无效的话,node里安装connect-history-api-fallback:
npm i connect-history-api-fallback
在app.js导入并使用
const history = require('connect-history-api-fallback');
// 使用historyApiFallback中间件,支持 HTML5 History 模式
app.use(history());
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
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')));
如此,直接运行express项目便可
十一、 购买云服务器
和本地部署一样,只不过需要一台云服务,便可以通过公网访问
阿里云:阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
点击云服务器ECS:
可以试用或者买一个:
拿到公网ip替换你的本地的127.0.0.1,便可在公网访问了
如果不能访问配置下安全组:
配置http的80端口用于http请求
配置mysql的3306用于服务器安装mysql
十二、连接云服务器
通过电脑的远程桌面连接:
账号一般是Administrator,密码是你云服务器的密码
连接后是一个windows桌面(我申请的是windows服务器)
接下来就和你之前本地操作一样,安装软件,git,node,mysql,navicat等等,然后启动后端服务部署前端项目,公网访问前端页面。
十三、最后
至此一个简单的基于express+mysql的node服务,前后端分离项目完成,比较简单,很多地方还可以细化优化,这就需要慢慢来了,第一步当然是先跑起来了