Express中文官网:https://www.expressjs.com.cn/
一、安装并初识Express
1、初始化一个npm项目:
npm init -y
2、安装express:
npm install express # 指定版本: npm install express@4.18.2
3、编写一个入门案例app.js:
const express = require('express'); const app = express(); const port = 3000; app.get("/", (req, res) => { res.send("Hello World!"); }) app.get("/user", (req, res) => { res.send("这是/user路径的返回") }) app.listen(port, () => { console.log(`Server running at http://localhost:${port}/`); })
4、运行app.js:
node app.js # 动态运行app.js,当有文件变动时,能够立即生效 nodemon app.js
5、此时访问 http://localhost:3000
如此简单,一个后端Server服务就启动起来了!
二、一个简单的TODO案例,练习路由
1、简单路由设计:
const express = require('express'); const app = express(); const port = 3000; app.get("/todos", (req, res) => { res.send("get /todos"); }) app.get("/todos/:id", (req, res) => { res.send(`get /todos/${req.params.id}`); }) app.post("/todos", (req, res) => { res.send("post /todos"); }) app.patch("/todos/:id", (req, res) => { res.send(`patch /todos/${req.params.id}`); }) app.delete("/todos/:id", (req, res) => { res.send(`delete /todos/${req.params.id}`); }) app.listen(port, () => { console.log(`Server running at http://localhost:${port}/`); })
2、现在没有DB数据库,我们就先把数据存在本地的一个json文件中db.json:
{ "todos": [ { "id" : 1, "title" : "吃饭" }, { "id" : 2, "title" : "睡觉" }, { "id" : 3, "title" : "写代码" } ] }
3、在app.js中使用fs模块,进行db.json文件的操作,封装一个工具类db.js:
const fs = require('fs') const { promisify } = require('util') const path = require('path') //将callBack形式的异步API,转化为promise形式, promise形式可以防止回调地狱 const readFile = promisify(fs.readFile) const writeFile = promisify(fs.writeFile) const dbPath = path.join(__dirname, './db.json') exports.getDb = async () => { const data = await readFile(dbPath, 'utf8') return JSON.parse(data) } exports.saveDb = async db => { // 最后的两个参数可以让写入 db.json 的数据可读性更好 const data = JSON.stringify(db, null, ' ') await writeFile(dbPath, data) }
4、整个增删改查逻辑代码如下app.js:
const express = require('express') const fs = require('fs') const { getDb, saveDb } = require('./db.js') const app = express() // /配置允许解析表单请求体application/json app.use(express.json()) // 配置允许解析表单请求体application/x-www-form-urlencoded app.use(express.urlencoded()) const port = 3000 app.get("/todos", async (req, res) => { try { const db = await getDb() return res.status(200).json(db.todos) } catch ( err ) { return res.status(500).json({ error: err.message }) } }) app.get("/todos/:id", async (req, res) => { try { const db = await getDb() const todo = db.todos.find(todo => todo.id === Number.parseInt(req.params.id)) if (!todo) { return res.status(404).end() } return res.status(200).json(todo) } catch (err) { return res.status(500).json({ error: err.message }) } }) app.post("/todos", async (req, res) => { try { // 1.获取请求体数据 const todo = req.body // 2.验证数据 if (!todo.title) { return res,status(422).json({ error : 'The field title is required' }) } // 3.数据验证通过,保存数据 const db = await getDb() const lastTodo = db.todos[db.todos.length -1] todo.id = lastTodo ? lastTodo.id + 1 : 1 db.todos.push(todo) await saveDb(db) return res.status(200).json(todo) } catch (err) { return res.status(500).json({ error: err.message }) } }) app.patch("/todos/:id", async (req, res) => { try { // 1.获取请求体数据 const todo = req.body // 2.查找到对应的数据 const db = await getDb(); const oldTodo = db.todos.find(todo => todo.id === Number.parseInt(req.params.id)) if (!oldTodo) { return res.status(404).end(); } // 3.合并todo,保存数据 Object.assign(oldTodo, todo) await saveDb(db) return res.status(200).json(oldTodo) } catch (err) { return res.status(500).json({ error: err.message }) } }) app.delete("/todos/:id", async (req, res) => { try { // 1.获取请求体数据 const todo = req.body // 2.查找到对应的数据的索引 const db = await getDb(); const index = db.todos.findIndex(todo => todo.id === Number.parseInt(req.params.id)) if (index === -1) { return res.status(404).end() } // 3.删除对应索引的数据 db.todos.splice(index, 1) await saveDb(db) return res.status(204).end() } catch (err) { return res.status(500).json({ error: err.message }) } }) app.listen(port, () => { console.log(`Server running at http://localhost:${port}/`) })
三、Express中的中间件
1、简单案例:对所有的请求进行访问日志打印(中间件的引入):
const express = require('express') const fs = require('fs') const { getDb, saveDb } = require('./db.js') const app = express() // /配置允许解析表单请求体application/json app.use(express.json()) // 配置允许解析表单请求体application/x-www-form-urlencoded app.use(express.urlencoded()) // 编写我们自己的中间件,实现日志功能 app.use((req, res, next) => { console.log(req.method, req.url, Date.now()) next() }) const port = 3000 app.get("/", async (req, res) => { res.send('/get /') }) app.get("/user", async (req, res) => { res.send('/get user') }) app.post("/save", async (req, res) => { res.send('/post save') }) app.put("/update", async (req, res) => { res.send('/put update') }) app.delete("/remove", async (req, res) => { res.send('/delete remove') }) app.listen(port, () => { console.log(`Server running at http://localhost:${port}/`) })
日志显示结果如下:
[nodemon] starting `node app.js` Server running at http://localhost:3000/ GET /user 1686823299996 POST /save 1686823307997 PUT /update 1686823314216 DELETE /remove 1686823319492
经过上面的实现,我们发现Nodejs中的中间件,与JAVA中的AOP思想很像;
如果有多个中间件,那么就会按照顺序,自上而下地依次执行,中间件中一定要记得通过next()将请求传递给下一个中间件!
中间件use的位置非常重要,如果在某个路由之后使用中间件,那么这个路由对应的请求就不会进入这个中间件!
2、Express中的中间件函数:
在Express中,中间件就是一个可以访问请求对象、响应对象和调用next方法的一个函数;
我们可以看到,在Router路由中,也可以使用中间件函数,将请求处理完成后,交给后续的中间件/路由进行处理;
【注意】如果当前的中间件功能没有结束请求-响应周期,则必须调用next()将控制全传递给下一个中间件功能。否则,该请求将被挂起!
3、Express中中间件的分类:
-
应用程序级别中间件:
-
路由级别中间件:
-
错误处理中间件:
-
内置中间件
-
第三方中间件
Express官方罗列的常见的第三方中间件列表:https://www.expressjs.com.cn/resources/middleware.html