Node.js简单实现后端服务器

该示例没有用到数据库,是通过创建POST表单路由进行处理数据,将数据保存到文件中,以及数据校验
用到的npm包/第三方模块:
Express:npm i express@4.17.1 – Web 开发框架,快速创建 Web 服务器
Joi:npm install joi --save – hapijs 自带的数据校验模块
@escook/express-joi中间件: npm i @escook/express-joi – 实现自动对表单数据进行验证的功能
cors: npm i cors – 配置cors跨域
dayjs: npm install dayjs --save – 格式化时间

全局安装了nodemon工具,可以监听文件的更改,自动重新执行

准备工作

  1. 如果是拿被别人写好的项目,第一步是导入这个项目所用的包,npm i命令
  2. 创建后端项目文件夹如serve
  3. 初始化文件夹npm init -y
    在空项目的根目录中初始化一个package.json的包管理配置文件
  4. 安装必要的包/依赖
    安装Express —— npm i express@4.17.1
  5. 创建服务器文件,命名如app.js

创建简单的web服务器 及其 模块化

  1. 创建基本的express框架 – eser(vscode快捷生成代码//自己配置 – 文件–首选项–配置用户代码片段)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //导入express模块
    const express = require('express')
    //创建express服务器实例
    const app = express()
    const port = 8080;

    //调用app.listen方法,指定端口号并启动web服务器
    app.listen(port,()=>{
    console.log(`Express server running at http://127.0.0.1:${port}`)
    })
  2. 创建路由
    可以先在当前文件尝试是否成功,之后再分离到对应模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //创建express路由实例
    const router = express.Router()
    // 配置路由
    app.use(router)
    // 提交预约表单
    router.post('/form', (req, res) => {
    console.log(req.body);
    res.send('发送表单数据成功')
    })
  3. 使用postman测试请求是否成功
    http://127.0.0.1:8080/form 发送post请求
    响应函数中打印出来的req.body结果是undefined,这是因为我们没有配置解析表单的中间件

  4. 配置解析表单的中间件
    // 配置解析表单数据的中间件,注意:这个中间件,只能解析applicaton/x-www-form-urlencoded格式的表单数据
    app.use(express.urlencoded({extended:false}))
    再次发送请求,我们就得到了表单提交的数据

  5. 分离到路由模块
    创建router文件夹,创建如user.js文件//处理用户路由
    将路由部分写到这个地方,然后把router暴露出去
    module.exports = router
    然后在app.js中导入并使用路由模块

    1
    2
    3
    4
    // 导入并使用用户路由模块
    const userRouter = require('./router/user')
    // 配置路由
    app.use(userRouter)

    测试成功

  6. 分离处理函数到路由处理模块,同5操作
    创建router_handler文件夹,在该目录下创建对应的用户路由处理文件user.js
    把处理函数暴露出去

    1
    2
    3
    4
    exports.formSubmit = (req, res) => {
    console.log(req.body);
    res.send('发送表单数据成功')
    }

    在路由模块导入导入用户路由处理函数对应的模块
    const user_handler = require('../router_handler/user')
    路由的响应函数换成对应的user_handler.formSubmit

数据校验

这里使用joi模块来进行数据验证
Joi 是 hapijs 自带的数据校验模块,他已经高度封装常用的校验功能。

  1. 安装joi包,为表单中携带的每个数据项,定义验证规则

    1
    2
    3
    npm install joi --save
    // ES6写法 import Joi from 'joi'
    const joi = require('joi')
  2. 安装 @escook/express-joi 中间件,来实现自动对表单数据进行验证的功能:
    npm i @escook/express-joi

  3. joi验证规则

    • string() 值必须是字符串
    • number() 值必须是数字
    • alphanum() 值只能是包含 a-zA-Z0-9 的字符串
    • min(length) 最小长度
    • max(length) 最大长度
    • required() 值是必填项,不能为 undefined
    • pattern(正则表达式) 值必须符合正则表达式的规则
    • any() 任意
    • date() 日期
    • time() 时间
    • iso() 要求字符串值采用有效的 ISO 8601 日期格式。
  4. 新建 /schema/user.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
    // 导入joi模块
    const joi = require('joi')

    // 表单校验规则
    const unit = joi.string().required()
    const contact = joi.string().required()
    const phone = joi.string().pattern(/^[1][\d]{10}$/).required()
    const numVisitors = joi.number().min(16).required()
    const guide = joi.string().pattern(/yes|no|是|否/).required()
    const date = joi.date().min('now').iso().required()
    const time = joi.string().pattern(/^[\d]{2}[\s]*:[\s]*[\d]{2}$/).required()

    // 表单的验证规则对象
    exports.form_schema ={
    body:{
    unit,
    contact,
    phone,
    numVisitors,
    guide,
    date,
    time,
    }
    }
  5. 在路由模块添加数据校验

    1
    2
    3
    4
    // 导入验证表单数据的中间件
    const expressJoi = require('@escook/express-joi')
    // 导入需要验证的规则
    const {form_schema} = require('../schema/user')

    在表单提交的路由中,声明局部中间件,对当前请求中携带的数据进行验证
    数据验证通过后,会把这次请求流转给后面的路由处理函数
    数据验证失败后,终止后续代码的执行,并抛出一个全局的 Error 错误,进入全局错误级别中间件中进行处理
    router.post('/form',expressJoi(form_schema), user_handler.formSubmit)

  6. 在 app.js 的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端
    在路由注册之后声明错误级别中间件,注意这里需要在app.js导入joi

    1
    2
    3
    4
    5
    6
    7
    8
    const joi = require('joi')
    // 错误中间件
    app.use(function(err,req,res,next){
    //数据验证失败
    if(err instanceof joi.ValidationError) return res.send(err.message)
    // 未知错误
    res.send(err.message)
    })

    postman测试成功

配置cors 跨域

之前我们都是在postman发送请求测试,如果我们在自己写的页面发送请求会发现报错
这是因为我们的页面打开的是file协议,而服务器则是http协议,

使用cors中间件解决跨域问题
cors 是Express的一个第三方中间件。通过安装和配置cors 中间件,可以很方便地解决跨域问题。
使用步骤分为如下3步:
1. 运行npm install cors安装中间件
2. 使用const cors = require('cors')导入中间件
3. 在路由之前调用app.use(cors())配置中间件

注意:一定要在路由之前配置cors中间件,从而解决接口跨域的问题

  • 前端发送axios请求:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    axios({
    method: 'POST',
    url: 'http://127.0.0.1:8080/form',
    // 如果要携带请求体:
    // 请求体参数,post请求体传参,可选
    data: {
    unit : "00",
    contact : "xxx",
    phone: '12345678901',
    numVisitors: 25,
    guide: 'yes',
    date: '2025-05-15',
    time: '14:30'
    }
    }).then((result) => {
    // .then 用来指定请求成功之后的回调函数
    // 形参中的 result 是请求成功之后的结果
    console.log(result.data);
    })
    注意:axios 使用 post 发送数据时,默认是直接把 json 放到请求体中提交到后端的,然而我们没有配置解析JSON表单数据的中间件,所以后端能够接收到请求,但是拿不到参数

配置解析JSON格式表单数据的中间件:app.use(express.json())
再次在页面发送axois请求,测试成功

处理数据 – date时间格式化

在路由处理模块,我们通过const userInfo = req.body接收了获取的数据
但是打印userInfo出来发现,date: 2025-05-15T00:00:00.000Z,,和我们传入的2025-05-15并不一致
遍历对象打印date又变成了:date: Thu May 15 2025 08:00:00 GMT+0800 (中国标准时间)
这也许是因为数据校验调用了date()导致const date = joi.date().min('now').iso().required()
所以我们需要手动处理一下接收到的date的格式

  1. 下载dayjs组件库,并引入到路由处理模块

    1
    2
    3
    4
    5
    // 下载 dayjs 组件库
    npm install dayjs --save

    // 进行引用
    const dayjs = require('dayjs')
  2. 处理data
    我们可以通过forEach遍历,并打印出每一项的数据(需要把对象的键值对包装成数组)

    1
    2
    3
    4
    5
    6
    7
    8
    // entries()方法 把对象中的 每个键值对 包装成一对数组 最后返回一个数组
    // 返回如[ [key1,value,],[key2,value2] ,.. ]
    Object.entries(userInfo).forEach((item)=>{
    if (item[0] == 'date'){
    item[1] = dayjs(item[1]).format('YYYY-MM-DD');
    }
    console.log(item[0] + ": " + item[1]);
    })

    date数据处理完毕

将数据保存到文件中

  1. 导入fs文件处理模块

    1
    const fs = require('fs')
  2. 向form.txt文件中追加每一项数据,不需要我们手动去创建txt文件,如果没有会自动创建

    1
    2
    3
    4
    5
    6
    // 这里的路径,是以执行的文件为基准的,这里是暴露出去的函数,是在路由里面执行的
    // 而路由是注册在app.js文件的,所以以app.js为基准
    //这里是追加内容的函数,和写入函数writeFile使用一样,如果该文件不存在则会创建该文件
    fs.appendFile('./form.txt',data, (err)=>{
    if(err) return err.message;
    })

    这样就成功向form.txt文件写入数据了,但是数据是没有换行的

  3. 写入数据换行 – 换行符常量 os.EOL
    先引入os模块:const os = require('os')
    然后修改追加的data数据

    1
    2
    3
    4
    5
    // 这里插入数据依旧插入的英文名称,没有进行更改对应中文处理
    let data = item[0] + ": " + item[1] + os.EOL;
    if (index == 0) {// 每一次插入前,都多空上一行 和加上一行分隔符
    data = os.EOL + "####预约申请表####" + os.EOL + data;
    }

增加msg路由模块

比form路由简单些,不需要进行太多数据验证,只需要保证数据不为空就行

这里直接贴出代码

  • 路由模块 msg.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 导入express模块,并创建路由实例
    const express = require('express');
    const msgRouter = express.Router();

    // 导入路由处理模块
    const msg_hander = require('../router_handler/msg')

    // 导入验证表单数据的中间件
    const expressJoi = require('@escook/express-joi')
    // 导入数据校验模块
    const { msg_schema } = require('../schema/msg')

    msgRouter.post('/msg',expressJoi(msg_schema),msg_hander.msgSubmit)

    module.exports = msgRouter
  • 路由处理模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const fs = require('fs')
    const os = require('os')

    exports.msgSubmit = (req,res) => {
    const msg = req.body;
    const data = '####留言####' + os.EOL + msg.msg + os.EOL + os.EOL;
    // 写入文件
    fs.appendFile('./msg.txt',data,(err)=>{
    if(err) return err.message;
    })
    res.send('发送留言数据成功')
    }
  • 数据校验模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 导入joi模块
    const joi = require('joi');
    // 校验规则 msg不少于10个字符,且是必填项
    const msg = joi.string().min(10).required();

    exports.msg_schema = {
    body:{
    msg,//注意必须写在body属性中,因为数据在req.body中
    }
    }

注意:记得要在app.js主程序页面配置路由app.use(msgRouter)