利用node.js搭配express框架写后端接口(一)

发布于:2025-02-28 ⋅ 阅读:(8) ⋅ 点赞:(0)

Node.js 凭借其高效的非阻塞 I/O 操作、事件驱动架构以及轻量级的特点,成为了开发高性能服务器应用的热门选择。Express 框架作为 Node.js 上最流行的 Web 应用框架之一,以其简洁的 API 和丰富的中间件生态系统,极大地简化了 Web 后端开发流程。本文将引导你如何使用 Node.js 和 Express 框架构建一个简单的后端接口。

目录

一、初始化项目

1.创建项目

2.配置cors跨域

3.配置解析表单数据的中间件

4.初始化路由相关的文件夹

 5.初始化用户路由模块

6.抽离用户路由模块中的处理函数

二、新建数据库表

1.新建数据库

2.新建表

三、安装并配置MySQL模块

1.安装MySQL模块

2.连接数据库

四、登录、注册

1.注册

1.1检测表单数据是否合法

1.2检测用户名是否被占用

1.3对密码进行加密处理

1.4插入新用户

1.5优化res.send()代码

1.6优化表单数据验证

2.登录

 2.1检测登录表单的数据是否合法

2.2根据用户名查询用户的数据

2.3判断用户输入的密码是否正确

2.4生成jwt的token字符串

2.5配置解析token的中间件


一、初始化项目

1.创建项目

新建一个文件夹作为项目的根目录,并在项目根目录中运行如下命令,初始化包的管理配置文件:

npm i express@4.17.1

然后在项目中新建app.js作为整个项目的入口文件,并初始化如下代码:

//导入express
const express = require('express')
//创建服务器对象
const app = express() 
//启动服务器
app.listen(3007, () => {
    console.log('api server htttp://127.0')
})

2.配置cors跨域

在文件夹的终端运行如下的命令,安装cors中间件:

npm i cors@2.8.5

然后再app.js中导入并配置cors中间件:

//导入并配置cors中间件
const cors = require('cors')
app.use(cors())

3.配置解析表单数据的中间件

通过使用如下代码,配置解析application/x-www-form-urlencoded格式的表单数据的中间件:

//配置解析表单数据的中间件--注意:只能解析application/x-www-from-urlencoded格式的表单数据的中间件
app.use(express.urlencoded({extended:false}))

4.初始化路由相关的文件夹

(1)在项目的根目录中,新建router文件夹,用来存放所有的路由模块。路由模块中,只存放客户端的请求与处理函数之间的映射关系。

 (2)在项目的根目录中,新建router_handler文件夹,用来存放所有的路由处理函数模块,这里专门负责存放每个路由对应的处理函数。

 5.初始化用户路由模块

在router文件夹中,新建user.js文件,作为用户的路由模块,代码如下:

const express = require('express')
//创建路由对象
const router = express.Router()

//注册新用户
router.post('/register', (req, res) => { 
    res.send('注册成功')
})

//登录
router.post('/login', (req, res) => { 
    res.send('登录成功')
})

//导出路由对象
module.exports = router

在app.js中导入并使用该用户路由模块

//导入并注册用户路由模块
const userRouter = require('./routes/user')
app.use('/api', userRouter)

6.抽离用户路由模块中的处理函数

为了保证路由模块的纯粹性,所有的路由处理函数,必须抽离到对应的路由处理函数模块中。

在router_handler/user.js文件中,使用exports对象,分别向外共享如下两个路由处理函数:

//注册用户的处理函数
exports.regUser = (req, res) => {
    res.send('注册成功')
}
//登录的处理函数
exports.login = (req, res) => {
    res.send('登录成功')
}

将router/user.js文件中的代码修改如下结构

const express = require('express')
//创建路由对象
const router = express.Router()

//打入用户路由处理函数模块
const userHandler = require('./router_handler/user')

//注册新用户---原
// router.post('/register', (req, res) => { 
//     res.send('注册成功')
// })
//注册新用户--新
router.post('/register',userHandler.regUser )

//登录----原
// router.post('/login', (req, res) => { 
//     res.send('登录成功')
// })
//登录---新
router.post('/login',userHandler.login)

//导出路由对象
module.exports = router

二、新建数据库表

在创建库表之前确保已经安装了数据库。

1.新建数据库

2.新建表

在新建的test_mysql数据库中,新建ev_users表 。点击新建查询,输入一下SQL语句

use test_mysql;
CREATE TABLE ev_users
(
    id INT NOT NULL AUTO_INCREMENT,
    username VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    nickname VARCHAR(255),
    email VARCHAR(255),
    user_pic TEXT,
    PRIMARY KEY (id)
)

然后点击旁边的运行,下方如果出现OK,就表示创建成功

 然后在表那边点击刷新就可以看见新创建的表了

三、安装并配置MySQL模块

在api接口项目中,需要安装并配置MySQL这个第三方模块,俩连接和操作MySQL数据库

1.安装MySQL模块

在根目录的终端中运行如下命令:

npm i mysql@2.18.1

2.连接数据库

在项目中的根目录下新建db文件夹,并在其下方新建index.js文件,在此文件中创建数据库的连接对象

//导入MySQL模块
const mysql = require('mysql')
//创建数据库连接对象
const db = mysql.createPool({
    host: '127.0.0.1',       // 数据库主机名
    user: '你的数据库用户名',            // 数据库用户名
    password: '你的数据库密码', // 数据库密码
    database: 'test_mysql' // 数据库名
})
// 导出连接池
module.exports = db;

四、登录、注册

1.注册

1.1检测表单数据是否合法

在router_handle/user.js这个文件中判断用户名和密码是否为空,具体代码如下:

//注册用户的处理函数
exports.regUser = (req, res) => {
    //获取客户端提交到服务器的用户信息
    const userInfo = req.body
    // console.log(userInfo, '这个信息是:')
    //对表单中的数据,进行合法的校验
    if (!userInfo.username || !userInfo.password) {
        return res.send({
            code: 404,
            message: 'fail',
            detail:'用户名或密码不合法'
        })
    }
}

1.2检测用户名是否被占用

(1)导入数据库操作模块

在router_handle/user.js这个文件中导入数据库模块

//导入数据库
const db = require('../db/index')

(2)定义SQL语句

//定义SQL语句,查询用户名是否被占用
    const sqlStr='select * from ev_users where username=?'

(3)执行SQL语句并根据结果判断用户名是否被占用

db.query(sqlStr, userInfo.username, (err, results) => {
        //执行SQL语句失败
        if (err) {
            return res.send({
                code:500,
                message: 'fail',
                detail:err.message
            })
        }
        //判断用户名是否被占用
        if (results.length > 0) {
            return res.send({
                code:409,
                message: 'fail',
                detail:'用户名被占用,请更换其他用户名'
            })
        }
    })

1.3对密码进行加密处理

为了保证密码的安全性,不建议在数据库以明文的形式保存用户密码,推荐对密码进行加密存储。

在当前所做项目中,使用bcryptjs对用户密码进行加密,优点在于:

  • 加密之后的密码,无法被逆向破解;
  • 同一明文密码多次加密,得到的加密结果各不相同,保证了安全性。

(1)安装bcryptjs

在项目终端运行如下命令:

npm i bcryptjs@2.4.3

 (2)导入bcryptjs

在router_handle/user.js这个文件中导入bcryptjs

//导入bcryptjs
const bcrypt=require('bcryptjs')

(3)使用bcryptjs

在注册用户的处理函数中,确认用户名可用之后,调用bcrypt.hashSync(明文密码,随机盐的长度)方法,对用户的密码进行加密处理:

//调用bcryptjs-hashSync()对密码进行加密
        console.log(userInfo,'加密前:')
        userInfo.password = bcrypt.hashSync(userInfo.password, 10)
        console.log(userInfo,'加密后:')

1.4插入新用户

(1)定义插入用户的SQL语句

 //定义插入新用户的SQL语句
        const sql='insert into ev_users set ?'

(2)调用db.query()执行SQL语句,插入新用户

//调用db.query()执行SQL语句
        db.query(sql, { username: userInfo.username, password: userInfo.password }, (err, results) => {
            //判断SQL语句是否执行成功
            if (err) {
                return res.send({
                    code:500,
                message: 'fail',
                detail:err.message
                })
            }
            //判断影响函数是否为1
            if (results.affectedRows != 1) {
                return res.send({
                    code: 500,
                    message: 'fail',
                    detail:'注册用户失败,请稍后再试'
                })
            } else {
                return res.send({
                    code: 200,
                    message: 'success',
                    detail:'注册成功'
                })
            }
        })

1.5优化res.send()代码

在以上的处理函数中我们可以发现,需要多次调用到res.send()向客户端响应处理失败的结果,为了简化以上代码,这里重新封装了一个res.trans()函数。

在app.js中,所有路由之前,声明一个全局中间件,为res对象挂载一个res.trans()函数

//封装res.trans
app.use((req, res, next) => {
    //code默认值为500,表示失败的情况
    //err可能是一个错误对象,也可能是一个错误的描述字符串
    res.trans = (err,code=500) => {
        res.send({
            code,
            message: 'fail',
            detail:err instanceof Error?err.detail:err
        })
    }
    next()
})

在router_handle/user.js这个文件中有关注册这个处理函数进行重新修改

//注册用户的处理函数
exports.regUser = (req, res) => {
    //获取客户端提交到服务器的用户信息
    const userInfo = req.body
    // console.log(userInfo, '这个信息是:')
    
    //对表单中的数据,进行合法的校验
    if (!userInfo.username || !userInfo.password) {
        // return res.send({
        //     code: 400,
        //     message: 'fail',
        //     detail: '用户名或密码不合法'
        // })
        return res.trans('用户名或密码不合法',400)
    }
    // } else {
    //     return res.send({
    //         code: 200,
    //         message: 'success',
    //         detail:''
    //     })
    // }
    //定义SQL语句,查询用户名是否被占用
    const sqlStr='select * from ev_users where username=?'
    db.query(sqlStr, userInfo.username, (err, results) => {
        //执行SQL语句失败
        if (err) {
            //原--未封装
            // return res.send({
            //     code:500,
            //     message: 'fail',
            //     detail:err.message
            // })
            //现--分装后
            return res.trans(err)
        }
        //判断用户名是否被占用
        if (results.length > 0) {
            //原--未封装
            // return res.send({
            //     code:409,
            //     message: 'fail',
            //     detail:'用户名被占用,请更换其他用户名'
            // })
            //现--分装后
            return res.trans('用户名被占用,请更换其他用户名',409)
        }
        //调用bcryptjs-hashSync()对密码进行加密
        // console.log(userInfo,'加密前:')
        userInfo.password = bcrypt.hashSync(userInfo.password, 10)
        // console.log(userInfo, '加密后:')
        //定义插入新用户的SQL语句
        const sql = 'insert into ev_users set ?'
        //调用db.query()执行SQL语句
        db.query(sql, { username: userInfo.username, password: userInfo.password }, (err, results) => {
            //判断SQL语句是否执行成功
            if (err) {
                // return res.send({
                //     code:500,
                // message: 'fail',
                // detail:err.message
                // })
                 //现--分装后
                return res.trans(err)
            }
            //判断影响函数是否为1
            if (results.affectedRows != 1) {
                // return res.send({
                //     code: 500,
                //     message: 'fail',
                //     detail:'注册用户失败,请稍后再试'
                // })
                 //现--分装后
            return res.trans('注册用户失败,请稍后再试')
            } else {
                return res.send({
                    code: 200,
                    message: 'success',
                    detail:'注册成功'
                })
            }
        })
    })
}

1.6优化表单数据验证

表单验证的原则:前端验证为辅,后端验证为主,后端不能相信前端提交过来的任何内容。在实际开发中,前后端都需要对表单的数据进行合法性的验证,而且,后端做为数据合法性验证的最后一个关口,在拦截非法数据方面,起到了至关重要的作用。
单纯的使用 if...e1se...的形式对数据合法性进行验证,效率低下、出错率高、维护性差。因此,推荐使用第三方数据验证模块,来降低出错率、提高验证的效率与可维护性,让后端程序员把更多的精力放在核心业务逻辑的处理上。

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

npm i joi

(2)安装@escook/express-joi中间件,来实现自动对表单数据进行验证的功能

npm i @escook/express-joi

(3)新建/schema/user.js用户信息验证规则模块,并初始化代码

//导入joi
const joi = require('joi')
/**
 * string 值必须是字符串
 * alphanum 值只能是包含a-zA-Z0-9的字符串
 * min(length)最小长度
 * max(length)最大长度
 * required() 值是必填项,不能为undefined
 * pattern(正则表达式) 值必须符合正则表达式的规则
 */
//定义用户名和密码的验证
const username = joi.string().alphanum().min(1).max(10).required()
const password = joi.string().pattern(/^[\S]{6,12}$/).required()

//定义验证注册和登录表单数据的规则对象
exports.reg_login_schema = {
    body: {
        username,
        password
    }
}

(4)修改router/user.js中的代码

const express = require('express')
//创建路由对象
const router = express.Router()

//打入用户路由处理函数模块
const userHandler = require('../router_handler/user')

//导入验证数据的中间件
const expressJoi = require('@escook/express-joi')
//导入需要的验证规则对象
const {reg_login_schema} = require('../schema/user')


//注册新用户--新
//1.在注册新用户的路由中,声明局部中间件,对当前请求中携带的数据进行验证
//2.数据验证通过后,会把这次请求流转给后面的路由处理函数
//3.数据验证失败后,终止后端代码的执行,并抛出一个全局的error错误,进入全局错误级别中间件中进行处理
router.post('/register',expressJoi(reg_login_schema),userHandler.regUser )


//登录---新
router.post('/login',userHandler.login)

//导出路由对象
module.exports = router

(5)在app.js的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端

//导入joi
const joi = require('joi')
//中间部分的省略

//定义错误级别的中间件
app.use((err, req, res) => {
    //验证失败导致的错误
    if (err instanceof joi.ValidationError) {
        return res.trans(err)
    }
    //未知的错误
    res.trans(err)
        
})

(6)在router_handler/user.js文件中,注册用户处理函数这一块注释掉下面的代码

// if (!userInfo.username || !userInfo.password) {
    //     return res.trans('用户名或密码不合法',400)
    // }

2.登录

2.1检测登录表单的数据是否合法

将router/user.js中登录的路由代码修改如下

//登录---新
router.post('/login',expressJoi(reg_login_schema),userHandler.login)

2.2根据用户名查询用户的数据

(1)接收表单数据

//获取客户端提交到服务器的用户信息
    const userInfo = req.body

(2)定义SQL语句

//定义SQL语句,查询用户名是否被占用
    const sqlStr='select * from ev_users where username=?'

(3)执行SQL语句,查询用户的数据

db.query(sqlStr, userInfo.username, (res, results) => {
        //执行SQL语句失败
        if (err) {
            return res.trans(err)
        }
        //执行SQL语句成功,但是查询到数据条数不等于1
        if (results.length != 1) {
            return res.trans('登录失败')
        }
    })

2.3判断用户输入的密码是否正确

核心:调用bcrypt.compareSync(用户提交的密码,数据库中的密码)方法比较密码是否一致。

 db.query(sqlStr, userInfo.username, (err, results) => {
        //执行SQL语句失败
        if (err) {
            return res.trans(err)
        }
        //执行SQL语句成功,但是查询到数据条数不等于1
        if (results.length != 1) {
            return res.trans('登录失败',405)
        }
        //用户输入的密码和数据库中存储的密码进行对比
        const compareResult = bcrypt.compareSync(userInfo.password, results[0].password)
        console.log('这是:',compareResult,results[0].password,userInfo.password)
        //如果对比的结果等于false,则证明用户输入的密码错误
        if (!compareResult) {
            return res.trans('登录失败',400)
        } 
        res.send('ok')
    })

2.4生成jwt的token字符串

注意:在生成token字符串的时候,一定要提出密码和头像的值

(1)通过es6的高级语法,快速剔除密码和头像的值,该代码书写于上述对比输入密码语句之后

//在服务器端生成token字符串
 const user = { ...results[0] ,password:'',user_pic:''}
 console.log(user,'这是:')

(2)安装生成token字符串的包

npm i jsonwebtoken

(3)在router_handler/user.js文件的头部区域,导入jsonwebtoken包

//导入生成的token包
const jwt=require('jsonwebtoken')

(4)创建config.js文件,并向外共享加密和还原token的jwtSecretKey字符串

module.exports = {
    //加密和解密的token密钥
    jwtSecretKey: 'test',
    //token有效期
    expiresIn:'10h'
}

(5)将用户信息对象加密成token字符串

//导入密钥配置文件
const config=require('../config.js')

//对用户的信息进行加密,生成token字符串
const tokenStr = jwt.sign(user, config.jwtSecretKey, { expiresIn: config.expiresIn })
console.log('这是:', tokenStr)

(6)将生成的token字符串响应给客户端

 //调用res.send()将token响应给客户端
 res.send({
      code: 200,
      message: 'success',
      token: 'Bearer '+tokenStr,
      detail:'登
})

2.5配置解析token的中间件

(1)安装解析token的中间件

npm i express-jwt@5.3.3

(2)在app.js中注册路由之前,配置解析token的中间件

//一定要在路由之前配置解析token的中间件
const { expressJwt } = require('express-jwt')
const config = require('./config')
app.use(expressJwt({ secret: config.jwtSecretKey}).unless({path:[/^\/api/]}))

(3)在app.js中的错误级别中间件中,铺货并处理token认证失败后的错误

//定义错误级别的中间件---暂不使用
app.use((err, req, res,next) => {
    //验证失败导致的错误
    // if (err instanceof joi.ValidationError) return res.trans(err)
    if(err.name==='UnauthorizedError') return res.trans('身份认证失败',401)
    // //未知的错误
    res.trans(err)    
})