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})}//判斷影響函數是否為1if (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)}//判斷影響函數是否為1if (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語句成功,但是查詢到數據條數不等于1if (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語句成功,但是查詢到數據條數不等于1if (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)
})