在前后端分离的开发模式中,我们的项目都是分成了前后端两个部分:
- 前端项目:使用 webpack 构建的前端项目;
- 后端项目:基于 express 搭建的后端项目;
以我们的考试系统为例的话,就是 exam-system 前端项目和 exam-system-server 后端项目。
在实际项目开发中,前端和后端一定是由至少两个程序员分别负责开发,所以两个项目会同时进行,不存在先写前端还是先写后端的问题。但是因为现在前后端都只有我们自己一个人开发,所以我们在写代码的时候,就可以根据自己的习惯来选择先写前端还是先写后端。
这里我们以先写前端代码为例,来梳理一下前后端分离开发的代码编写流程。
一、前端发送请求
当我们在前端把页面和样式写好后,就可以开始根据业务需求来发送 AJAX 请求到后端。
1、添加事件
通常,前端要发送 AJAX 请求主要有两种情况:
- 页面刷新一进来时就需要发送 AJAX 请求;
- 在全局先封装好一个方法,直接调用即可。
- 用户在页面中触发了某些事件后,需要发送 AJAX 请求;
- 先给对应的元素节点身上添加事件,在事件中调用请求
2、封装 AJAX
找到前端项目api目录中对应的.js文件,如果没有就自己创建。 api 的.js文件有了之后就可以开始封装相关 AJAX 请求。3、配置请求代理
在前后端分离的开发模式中,通常前端项目都有一个 webpack 的服务器,后端也有一个 express 的服务器。默认情况下,webpack 的服务器是无法发送请求到 express 的服务器中的。存在跨域问题。
所以,我们需要在前端的 webpack 的webpack.dev.config.js配置文件对开发服务器进行处理:
配置完成后,项目中所有的 AJAX 请求的 URL,都应该在原本的 URL 的前面加上module.exports = merge(base, {mode: 'development',devtool: 'inline-source-map',// 开发服务器devServer: {port: '8080', // 端口号open: 'index.html', // 启动项目时默认打开的页面// 默认情况下,前端8080的服务器不能向后端3000的服务器发送请求proxy: {// 匹配所有包含 /api 的请求'/api': {target: 'http://localhost:3000', // 真正处理请求的目标后端服务器地址changeOrigin: true,// 前端所有以 /api 开头的请求,转发到后端服务器后,会自动去掉 /apipathRewrite: {'^/api': ''}}}}})
/api的前缀,例如:// 获取所有分类数据export function getTypesAsync() {return new Promise((resolve, reject) => {$.ajax({// url 通常由两部分组成:/数据名/描述操作url: '/api/types/getTypes',type: 'GET',success(res) {resolve(res);}})})}
4、引入 API 方法
AJAX 封装成功后,回到我们页面的.js文件中。想要调用哪一个功能的 AJAX,就引入封装好的 API 方法。import { 封装好的api方法名 } from '封装api方法路径'
5、调用 API 方法
引入 API 方法后,就可以在对应的位置调用方法发送请求了。
注意:请求是异步执行,封装和调用API时都需要使用Async和Await6、测试 AJAX 请求
前端 AJAX 的调用写好后,我们就可以在页面中进行刷新,或者点击相关按钮触发事件,来检测前端的 AJAX 请求是否发送成功。
说明:在还没有处理后端时,前端请求应该会有 404 的报错。7、检查前端参数
如果当前的请求需要发送参数给后端,在 Network 中点击进入对应的请求,查看参数的传递情况:
请求和参数都没问题后,就可以开始编写后端的代码了。二、后端处理请求
1、检查前端请求
虽然我们还没有开始编写后端的代码,但是前端请求已经可以发送到后端。
正常情况下,当前端发送了请求后,我们在后端项目的终端中可以看到一个 404 的请求日志:
这样才表示前端请求成功的发送到了后端。2、匹配一级路径
前端请求发送到后端,首先会先进入app.js中匹配请求的一级路径。
例如我们前端发送的/types/deleteTypes请求,会先在app.js中查找以下配置:
如果没有以上配置,我们就需要自己在// ...var typesRouter = require('./routes/types');// ...app.use('/types', typesRouter);// ...
routes目录中创建对一个的.js文件,然后在app.js中添加以上配置。
一级路径匹配成功后,我们的请求就会根据一级路径的配置,进入到对应的.js文件:
3、匹配二级路径
进入到 routes 中对应的 .js 文件后,开始匹配请求类型和二级路径:
注意:前端发送的请求,除了一级路径外,还一定要和后端的“请求类型”和“二级路径”都匹配上,参考下图:router.post('/deleteTypes', async function (req, res, next) {console.log('成功匹配到前端的 /types/deleteTypes 请求');});

最终,当一级路径、二级路径和请求类型都匹配成功时,前端的请求才成功的进入到后端。4、获取前端发送的参数
如果这次请求中,前端发送了参数给后端,我们还需要在后端获取到前端发送的参数(如果本次请求前端没有发送参数,可以忽略这一步骤)。router.post('/deleteTypes', async function (req, res, next) {// 获取前端通过 AJAX 的 data 发送给后端的数据const params = req.body;console.log('获取前端发送的参数', params);});
- 先给对应的元素节点身上添加事件,在事件中调用请求
- req.body:可以用来接收前端 POST 请求发送的参数
- req.query:可以用来接收前端 GET 请求发送的参数
5、后端配置数据模型
后端接收到前端发送的请求后,下一步就需要开始通过数据模型来操作数据库。所以,需要先配置好数据模型。
我们在项目的 dao/models 目录中创建一个 typesModel.js 文件,来作为考试分类集合的模型配置文件:// 考试分类集合const { Schema, model } = require('mongoose');// 定义该集合中每一条数据的格式(排除 _id)const typesSchema = new Schema({type: String,icon: String}, { versionKey: false })// 设置数据库的集合名称module.exports = model('typesModel', typesSchema, 'types');
6、引入数据模型
模型配置好后,回到rouets目录的文件中,引入数据模型:const typesModel = require('../dao/models/typesModel');
7、操作数据库
获取到模型后,就可以通过模型去操作数据库了。8、返回结果给前端
根据第 7 步操作数据库的结果,我们需要通过判断等操作,返回一个结果给前端。
例如将获取到的所有分类数据返回给前端:
注意:不管做什么请求,后端最终都必须要通过router.get('/getTypes', async function (req, res, next) {const data = await typesModel.find();// 将处理结果返回给前端res.send({message: '考试分类数据获取成功',code: 1,data: data});});
res.send()返回一个结果给前端,这样,一个请求才能完成,才能继续执行后续的代码。三、前端处理请求结果
后端res.send()返回给前端的结果,最终会发送到 AJAX 的success(res){}的 res 中,然后通过我们的封装处理,将 success 的结果resolve()出来,最终在页面的.js文件中通过await来接收到后端的结果。
