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