Express 简介
Express 是一个简洁而灵活的 Node.js Web 应用框架,提供一系列强大特性帮助你创建各种 Web 应用
Express 内部还是使用的 http 模块实现服务器创建和监听,对 http 模块进行了二次封装
严格上来说 express 就是 NodeJS 的一个模块 直接下载安装
中文官网 express 中文官网
使用
node 下载 express 模块:
命令 :
初始化服务 : npm init -y
下载 express: npm i express
使用的时候 引入 需要实例化一个 express 应用 使用应用对象创建各种服务
express 初体验 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const express = require ("express" );const app =express ();app.get ("/" ,(req,res,next )=> { res.end ("hello GET express" ); }) app.post ("/" ,(req,res,next )=> { res.end ("hello POST express" ); }) app.listen (3000 ,()=> { console .log ("server is running port is 3000" ); })
express 中间件 中间件基本概念
Express 是一个路由和中间件的 Web 框架,Express 应用程序本质上是一系列中间件函数的调用;那么,中间件是什么呢?
中间件的本质是传递给 express 的一个回调函数;
这个回调函数接受三个参数:
请求对象(request 对象);
响应对象(response 对象);
next 函数(在 express 中定义的用于执行下一个中间件的函数);
中间件中可以执行哪些任务呢?
执行任何代码;
更改请求(request)和响应(response)对象;
结束请求 - 响应周期(返回数据);
调用栈中的下一个中间件;
如果当前中间件功能没有结束请求 - 响应周期,则必须调用 next () 将控制权传递给下一个中间件功能,否则,请求 将被挂起。
app.use 创建最普通中间件 (对 method 和路由均没有限制)
使用 app.use
注册一个最普通的中间件,因为没有对 method
做限制,所以 GET请求
与 POST请求
都可以访问;因为没有对路由
做限制,所以所有路由
都可以访问;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const express = require ("express" );const app = express ();app.use ((req,res,next )=> { console .log ("注册了第01个普通中间件" ); res.end ("over" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
app.use 创建多个最普通中间件
app.use 注册多个最普通的中间件,当发送请求的时候,虽然这几个中间件都可以匹配成功,但是只会执行第 01 个中间件;后面的即使匹配成功了,也不会执行;
即:中间件永远查找匹配成功的第一个中间件,要想别的匹配成功的中间件也执行,需要在上一个匹配成功的中间件执行 next ();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const express = require ("express" );const app = express ();app.use ((req, res, next ) => { console .log ("注册了第01个普通中间件" ); res.end ("over" ); }) app.use ((req, res, next ) => { console .log ("注册了第02个普通中间件" ); }) app.use ((req, res, next ) => { console .log ("注册了第03个普通中间件" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
那么如何执行多个中间件呢?
答:使用 next()
调用栈中的下一个可以匹配成功
的中间件;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const express = require ("express" );const app = express ();app.use ((req, res, next ) => { console .log ("注册了第01个普通中间件" ); res.end ("over" ); next (); }) app.use ((req, res, next ) => { console .log ("注册了第02个普通中间件" ); next (); }) app.use ((req, res, next ) => { console .log ("注册了第03个普通中间件" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
路由中间件
路径匹配中间件:对路由做限制,对 method 不做限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const express = require ("express" );const app = express ();app.use ("/home" ,(req,res,next )=> { console .log ("home middleware 01" ); res.end ("home middleware" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
多个路由中间件同上,需要调用 next () 执行下一个匹配成功的中间件;
路由和方法都匹配的中间件
不使用 app.use
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const express = require ("express" );const app = express ();app.get ("/home" ,(req,res,next )=> { console .log ("home path and method middleware 01" ); res.end ("home middleware" ); }) app.post ("/login" ,(req,res,next )=> { console .log ("login path and method middleware 01" ); res.end ("login middleware" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
连续注册中间件 (koa 不支持)
多个参数,每个参数都是回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const express = require ("express" );const app = express ();app.get ("/home" , (req, res, next ) => { console .log ("home path and method middleware 01" ); res.end ("home middleware" ); }, (req, res, next ) => { console .log ("home path and method middleware 02" ); }, (req, res, next ) => { console .log ("home path and method middleware 03" ); }, (req, res, next ) => { console .log ("home path and method middleware 04" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
要想执行多个匹配成功的中间件,也还是调用 next ();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const express = require ("express" );const app = express ();app.get ("/home" , (req, res, next ) => { console .log ("home path and method middleware 01" ); res.end ("home middleware" ); next (); }, (req, res, next ) => { console .log ("home path and method middleware 02" ); next (); }, (req, res, next ) => { console .log ("home path and method middleware 03" ); next (); }, (req, res, next ) => { console .log ("home path and method middleware 04" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
POST 请求 body 参数解析 中间件应用 - json-urlencoded 解析
注册 express 内置的 body-parser
;使用 req.body
接收参数;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const express = require ("express" );const app = express ();app.use (express.json ()); app.use (express.urlencoded ({extended :true })); app.post ("/login" ,(req,res,next )=> { const info = req.body ; console .log (info); res.end ("使用req.body获取body参数" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
关于 express.urlencoded ({extended:true}) 中的 extended:
true: 那么对 urlencoded 进行解析时,他使用的是第三方库:qs
false:那么对 urlencoded 进行解析时,他使用的是 node 内置模块:querystring
form-data 解析:一般用于上传图片文件;
express 框架没有内置的解析中间件,但是有官方的库 multer
,需要下载安装一下:npm i multer
初步使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const express = require ("express" );const app = express ();const multer = require ("multer" );const upload = multer ();app.use (upload.any ()); app.post ("/upload" ,(req,res,next )=> { console .log (req.body ); res.end ("用户上传成功" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
但是上方写法存在个问题:https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md 写着” 永远不要将 multer 作为全局中间件使用,因为恶意用户可以上传文件到一个你没有预料到的路由,应该只在你需要处理上传文件的路由上使用;
如果你需要处理一个只有文本域的表单,你应当使用 .none()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const express = require ("express" );const multer = require ("multer" );const app = express ();const upload = multer ()app.post ("/upload" , upload.none (), (req, res, next ) => { console .log (req.body ); res.end ("用户上传成功~" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
文件上传
上传图片文件,并且将上传的图片文件放在服务器中某位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const express = require ("express" );const multer = require ("multer" );const app = express ();const upload = multer ({ dest : "./uploadImg/" }); app.post ("/upload" , upload.single ("avatar" ), (req, res, next ) => { console .log (req.file ); console .log (req.body ); res.end ("用户上传成功~" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
此时我们图片没有后缀,而且不能直接打开,我们给图片添加后缀和重命名
磁盘存储引擎 (DiskStorage):磁盘存储引擎可以让你控制文件的存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const express = require ("express" );const multer = require ("multer" );const path = require ("path" );const app = express ();const storage = multer.diskStorage ({ destination : (req, file, cb ) => { cb (null , "./uploadImg/" ) }, filename : (req, file, cb ) => { cb (null , Date .now () + path.extname (file.originalname )); } }) const upload = multer ({ storage }); app.post ("/upload" , upload.single ("avatar" ), (req, res, next ) => { console .log (req.file ); console .log (req.body ); res.end ("用户上传成功~" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
这里使用 diskStorage 处理文件上传的地址和文件名,其中 destination 可以为字符串也可以为函数,但是两者使用上有区别:
1 2 3 4 5 6 7 8 9 10 11 12 const storage = multer.diskStorage ({ destination :"./uploadImg/" , filename :(req,file,cb )=> { cb (null ,Date .now ()+path.extname (file.originalname )) } })
字符串,将根据字符串,创建文件夹;
回调函数中,传入的字符串表示文件放入的地址不会创建该文件夹(若没有的话);
上面是一次上传一张图片的情形,其实还有一次上传多张图片的情形;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const express = require ('express' )const multer = require ('multer' )const upload = multer ({ dest : 'uploads/' })const app = express ()app.post ('/profile' , upload.single ('avatar' ), function (req, res, next ) { }) app.post ('/photos/upload' , upload.array ('photos' , 12 ), function (req, res, next ) { }) const cpUpload = upload.fields ([{ name : 'avatar' , maxCount : 1 }, { name : 'gallery' , maxCount : 8 }])app.post ('/cool-profile' , cpUpload, function (req, res, next ) { })
.single (fieldname):接受一个以 fieldname 命名的文件。这个文件的信息保存在 req.file。
.array (fieldname [, maxCount]):接受一个以 fieldname 命名的文件数组。可以配置 maxCount 来限制上传的最大数量。这些文件的信息保存在 req.files。
.fields (fields):接受指定 fields 的混合文件。这些文件的信息保存在 req.files;fields 应该是一个对象数组,应该具有 name 和可选的 maxCount 属性。
array 和 fields 区别是:array 上传的多张文件名的 key 都是一样的,而 fields 因为参数是数组对象,所以可以多种 key,每个 key 又能限制 count;
Example:
1 2 3 4 [ { name : 'avatar' , maxCount : 1 }, { name : 'gallery' , maxCount : 8 } ]
upload.any () 不作为全局,作为局部中间件;也可以接收一次上传多张的情形,而且还不用指定 key;更加自由;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const express = require ("express" );const multer = require ("multer" );const path = require ("path" );const app = express ();const storage = multer.diskStorage ({ destination :"./uploadImg/" , filename : (req, file, cb ) => { cb (null , Date .now () + path.extname (file.originalname )); } }) const upload = multer ({ storage }); app.post ("/upload" , upload.any (), (req, res, next ) => { console .log (req.files ); console .log (req.body ); res.end ("用户上传成功~" ); }) app.listen (3000 , () => { console .log ("server is running port is 3000" ); })
GET 请求参数解析 params 解析 (路由参数)
/products/:id/:name 的这种的 (http://127.0.0.1:3000/products/fsl/forward),用 req.params 解析
1 2 3 4 5 6 7 8 9 10 11 12 13 const express= require ("express" );const app = express ();app.get ("/products/:id/:name" ,(req,res,next )=> { console .log (req.params ); res.end ("商品的详情数据~" ) }) app.listen (3000 ,()=> { console .log ("server is running port is 3000~" ); })
query 解析
/login 这种的 (http://127.0.0.1:3000/login?name=fsl&age=18),用 req.query 解析
1 2 3 4 5 6 7 8 9 10 11 12 13 const express= require ("express" );const app = express ();app.get ("/login" ,(req,res,next )=> { console .log (req.query ); res.end ("登陆成功~" ) }) app.listen (3000 ,()=> { console .log ("server is running port is 3000~" ); })
响应数据
res.end (xxx):只能返回 字符串、buffer 数据等,不能返回 JSON,数组;
res.json (xxx):可以传入很多的类型:object、array、string、boolean、number、null 等,它们会被转换成 json 格式返回;
res.status (200):用于设置状态码
express 路由
我们可以使用 express.Router()
来创建一个路由处理程序:一个 Router 实例拥有完整的中间件和路由系统;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const express = require ("express" );const router = express.Router (); router.get ("/users" , (req, res, next ) => { res.json (["why" , "kobe" , "fsllala" ]); }) router.get ("/users/:id" , (req, res, next ) => { res.json (`${req.params.id} 用户的信息` ); }) router.post ("/users" , (req, res, next ) => { res.json ("create user success~" ); }) module .exports = router;
1 2 3 4 5 6 7 8 9 const express= require ("express" );const app = express ();const userRouter = require ("./router/user.js" )app.use (userRouter); app.listen (3000 ,()=> { console .log ("server is running port is 3000~" ); })
注册路由的时候,可以有两个参数 app.use (“/ 公共路由”,” 路由”);上面的写法就可以写成下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const express = require ("express" );const router = express.Router (); router.get ("/" , (req, res, next ) => { res.json (["why" , "kobe" , "fsllala" ]); }) router.get ("/:id" , (req, res, next ) => { res.json (`${req.params.id} 用户的信息` ); }) router.post ("/" , (req, res, next ) => { res.json ("create user success~" ); }) module .exports = router;
1 2 3 4 5 6 7 8 9 const express= require ("express" );const app = express ();const userRouter = require ("./router/user.js" )app.use ("/users" ,userRouter); app.listen (3000 ,()=> { console .log ("server is running port is 3000~" ); })
服务端错误处理
处理方式一:不易维护 (后期修改 code 码 /message 信息);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const express = require ("express" );const app = express ();app.use (express.json ()); app.use (express.urlencoded ({ extended : true })); app.post ("/login" , (req, res, next ) => { const { username, password } = req.body ; if (!username || !password) { res.json ({ code : -1001 , message : "请输入用户名或密码!" , }); } else if (username !== "fsllala" || password !== "123456" ) { res.json ({ code : -1002 , message : "用户名或密码错误!" , }); } else { res.json ({ code : 0 , message : "登录成功!" , token :"fasfasf123123aff" }); } }); app.listen (3000 , () => { console .log ("server is running~" ); });
处理方式二:通过 next () 传递给下一个匹配的中间件,其中 next 可以传递 错误信息 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 const express = require ("express" );const app = express ();app.use (express.json ()); app.use (express.urlencoded ({ extended : true })); app.post ("/login" , (req, res, next ) => { const { username, password } = req.body ; if (!username || !password) { next (-1001 ); } else if (username !== "fsllala" || password !== "123456" ) { next (-1002 ); } else { res.json ({ code : 0 , message : "登录成功!" , token :"fasfasf123123aff" }); } }); app.use ((err, req, res, next ) => { const code = err; let message = "未知错误" ; switch (err) { case -1001 : message = "请输入用户名或密码!" ; break ; case -1002 : message = "用户名或密码错误!" ; break ; } res.json ({ code, message }); }); app.listen (3000 , () => { console .log ("server is running~" ); });
参考文献 multer 官方文档
Node 中间件 multer 库学习