记录一次配置express框架后端

为什么会有这篇文章

我的毕业设计需要一个简单的后端和数据库来实现一些功能,想着既然有过编写前端JavaScrpit的经验,不如看看nodejs环境下有没有提供什么好用的后端框架,于是就找到了express,再接着就有了这篇文章。在写文章的这个节点上,后端配置和功能编写已经写好了七七八八了,在此做个记录。

首先是环境安装

基于贫穷和电脑性能比较一般(其实还是贫穷)这两个原因,我的毕设后端是跑在WSL上的。关于WSL的安装具体可以去参照微软官方提供的文档,相当的详细,能够解决几乎是99%的问题。
部署完成WSL后需要进行nodejs的安装,我使用的系统镜像是Ubuntu 24.04 LTS,直接用apt就可以完成node的安装,用apt安装的node版本为v18.19.1,对于我的毕设来说影响不大。

1
2
#利用debian系的包管理器apt安装nodejs
sudo apt install nodejs

使用该指令安装nodejs的同时会自动安装包管理器npm,但由于服务器位置的原因,直接使用npm安装新包会有速度问题,这里采用cnpm来对下包速度进行加速,cnpm的具体安装方法如下。

1
2
#使用国内镜像进行安装
npm install cnpm -g --registry=https://registry.npmmirror.com

完成所需基本环境的安装之后,该正式着手实现后端了。在一切的开始,需要建立一个文件夹存放我的后端代码,同时要在这个文件夹导入express框架所需要的库。

1
2
3
mkdir myserver      #我的后端文件夹真的就叫做myserver
cd myserver
cnpm install express --save

环境安装之后是新建文件夹!

为了降低后期维护所需要的精力,代码部分采用模块化开发的思想,将每个功能和配置细分成为几个模块(不过正常开发就得这么做就是了),在经过反复搜索和几十分钟的头脑风暴之后,最终决定整个项目的文件结构如下。

1
2
3
4
5
6
7
├── app.js 这里是主程序的入口.js
├── config
│   └── 这里放一些一些功能的配置代码.js
├── modules
│   └── 这里放数据库的schema.js
└── routes
└── 这里放功能的具体实现.js

既然文件结构里提到了schama,这里补充一下,本项目使用的数据库为MongoDB,至于为什么使用MongoDB,一是我认为JSON格式的数据处理起来比较轻松,二是我确实对其他数据库掌握得还不够。本项目使用MongoDB Community Edition,具体安装和配置在官网有非常非常非常详细的文档,这里就不在赘述。

建完文件之后是编写代码……吗?

由于本人之前只写过serverless项目,对整个后端的知识几乎为零,故在实际编写代码之前仔细啃了一遍express的文档,但是说实话看的似懂非懂,完全就是一知半解的情况,之后也是借助伟大的互联网努力学习了一个晚上。在经过了一个晚上的努力学习之后,也是能让系统跑起来了。鉴于本人的专业知识储备相当不足,对之后代码的理解可以说是极富个人特色,还请多多包涵,毕竟就结果来说,系统还是跑起来了www

正式开始编写代码!

首先是配置服务器

首先从我们的代码入口,app.js开始。在app.js当中,需要完成对其他组件的导入和服务器的基本设置,由于其他功能还没写,那就先在app.js里完成服务器的创建吧。

1
2
3
4
5
6
7
8
9
10
11
//引入express库
const express = require('express')
//利用express创建一个app对象
const app = express()
//设置监听端口
const port = 4000;

//调用app对象的listen方法,创建一个服务器
app.listen(port, () => {
console.log(`Servers listening on port ${port}`)
})

至此完成了服务器的创建,不同于vue前端使用import来导包,nodejs后端使用require()来完成对包的引入。这是因为本次使用的nodejs后端版本为v18.19.1,使用CommonJS规范,而vue前端使用的node版本较高,为ES6标准,需要区分开来。
既然已经完成了服务器设置,那么是时候开始写功能了。我希望这个服务端能提供增和查这两个功能,在讨论增和查之前,我还得有个能够被我增和查的对象,所以我得先引入数据库。在config目录下新建db.js,在这里进行数据库连接的配置。

然后是连接数据库

我选用Mongoose作为后端与MongoDB数据库沟通的中间件(我不知道这个说法对不对),首先用cnpm包管理器安装Mongoose包,之后在db.js中进行导入。db.js中的代码差不多是这个样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//引入mongoose包
const mongoose = require('mongoose');
//完成对数据库的连接,这是一个异步方法
const connectDB = async () => {
try {
//连接到本地的TEST数据库
await mongoose.connect('mongodb://localhost:27017/TEST');
console.log('MongoDB Connected');
}catch (err) {
console.error('MongoDB Connection Error:', err);
process.exit(1); // 退出进程
}
};
//向外导出connectDB模块
module.exports = connectDB;

没什么好说的,到此就连接上了数据库。由于MongoDB中的数据是以BSON格式保存的,且默认没有对数据的格式进行约束,所以我还要编写schema来完成对数据格式的规定。
那什么是schema呢?不如让我们问问AI。以下文字由DeepSeek生成www
JSON Schema 是一种基于JSON格式的声明式规则,用于定义和验证JSON数据结构。它通过描述JSON数据的预期格式(如字段类型、取值范围、必填属性、嵌套结构等),实现数据校验、生成文档、设置默认值等功能,确保数据符合规范,提升开发协作和数据交互的可靠性。
了解了什么是schema之后,让我们来到modules文件夹,在里面新建一个dataSchema.js,让我们来编写dataSchema.js的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//编写schema当然也离不开mongoose库
const mongoose = require('mongoose');

//创建具体的schema来规定数据格式
const dataSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
data: {
temperature: {
type: Number,
required: true
},
humidity: {
type: Number,
required: true
}
},
mac_address: String
})

//这里导出的实际上是一个,已经应用了schema的名为data的Model
module.exports = mongoose.model('data', dataSchema);

根据mongoose官方文档的说法,model方法实际上是以选择的schema为规则,生成一个model。这个model用官方文档的话来说,就是从schema定义编译而来的奇特构造函数,model负责从数据库创建和读取文档。而根据我的说法呢,这个model就是帮助我们操作MongoDB的五星金词条帕鲁。至此,也是正式完成了数据库的配置!

接下来让我们写功能

让我们来到routes文件夹。express框架中,对发起请求的前端设备提供的方法,以路由的形式写在代码中。用文档中的说法就是,路由用于确定应用程序如何响应对特定端点的客户机请求。新建一个data.js文件,让我来编写我所需要的功能吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//由于router方法派生自 HTTP 方法,附加到 express 类的实例。
//所以我们要导入express库,然后调用Router()方法新建一个router对象
const express = require('express');
const router = express.Router();
//导入我们前面创建好的model
const data = require('../modules/dataSchema');

// 查,这边使用的是modle.find()查找数据库中的所有项目
// router对象的.get()方法表示以下的方法是对get请求的响应
router.get('/', async (req, res) => {
try {
const datas = await data.find();
res.json(datas);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 查,这边使用的是modle.findfindById()查找数据库中对应id的项目
router.get('/:id', async (req, res) => {
try {
//根据get请求时传入的:id
const datas = await data.findById(req.params.id);
//判断是否存在数据
if (!datas) return res.status(404).json({ error: 'Item not found' });
res.json(datas);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 增,使用modle.save()在数据库中创建一个新项目……吗?
router.post('/', async (req, res) => {
try {
const datas = new data(req.body);
await datas.save();
res.status(201).json(datas);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// 导出路由模块
module.exports = router;

对于两个查找方法其实没什么好说的,比较值得说到的地方是这个增。按照mongoose开发文档,使用model.save(数据)就可以完成增加数据的操作,但是在实际开发中我发现一个很奇怪的现象,就是如果用最开始引入的data,即导入的那个model,直接使用data.save(数据)会出现数据无法保存的情况,具体原因我也不清楚。但我这里用new新建了一个已经包含了所要保存数据的model对象,然后用new出来的datas.save()就可以正常保存,很是奇怪。

什么?最后居然还是服务器配置!

在完成了全部配置、schema、方法的编写以后,我们还需要回到app.js对我们先前实现的代码一个个引入。最终的app.js大概长这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//引入express库
const express = require('express')
//分别引入连接数据库的方法和配置好的路由
const connectDB = require('./config/db.js')
const dataRouter = require('./routes/data.js')
//利用express创建一个app对象
const app = express()

//初始化数据库
connectDB()
// 启用中间件处理传来的JSON数据
app.use(express.json());
// 路由
app.use('/data', dataRouter);

//设置监听端口
const port = 4000;
//调用app对象的listen方法,创建一个服务器
app.listen(port, () => {
console.log(`Servers listening on port ${port}`)
})

首先导入之前编写好,并导出的每个模块,对于路由我们需要使用app.use()来指定路由的工作地址(我不确定是不是这个说法),和该地址对应的路由。同时,通过网络传输的数据不可能一开始就是一个完美的JSON数据,还需要使用app.use()引入express.json()中间件,提前将接收到的数据处理成JSON,方便后续代码进行操作。到此为止,我们就已经完成了后端代码的全部编写(当然我自己的后端不止这么点东西),接下来让我们回到终端,运行这个服务器吧。

1
node app.js

后端是写完了,但是我该怎么用呢?

还记得我前面说过,我的后端代码是部署在wsl上的吗。Windows11下系统可以直接通过localhost访问wsl中运行的每一个应用,但是很不巧,我是Windows10。所以让我们来看看文档吧。
微软wsl文档中说,使用lan访问wsl2需要像访问虚拟机一样,使用netsh添加端口代理,依照文档的说法,运行如下命令,因为我的后端服务器监听的端口是4000,所以将命令中的端口号改为4000。

1
netsh interface portproxy add v4tov4 listenport=4000 listenaddress=0.0.0.0 connectport=4000 connectaddress=<wslIP>

现在,在我Windows上编写的前端程序可以正常连接后端服务器了!

结语

没有结语