初识 express
前言
虽说在下是 springboot 新手,但是作为前端接口调试而言,springboot 实在太过重量级。现在发现了一个作为测试服务器相当好用的玩意: NodeJs。话不多说,开搞。
设置 url 映射,并开启服务器监听
1 | // helloExpress.js |
启动 express 服务器 node helloExpress.js
。
打开浏览器,地址栏输入 localhost:8000
显示页面 nice!
express 可以轻松地帮我们开启一个服务端。
但是使用发现,当服务端代码发生改变,我们需要重新启动服务,有点麻烦,接下来介绍一个热重载工具。
使用 nodemon 帮助我们热更新服务端代码
处理跨域
既然开启了服务器,那就一定要提一下 CORS 跨域。
跨域的意思是违反浏览器的同源策略。浏览器为了用户安全,在前端和服务器端使用不同的协议、端口、主机名进行通信时,会将 response
给 block
掉。
CORS 是一个官方的标准,通过在服务器中设置响应头提供跨域服务。
1 | //这次使用的是 `app.all` 他表示 get/post/put/delete/patch 方法都可以进入到该 controller 中。 |
上面的 url controller
中通过设置响应头 Access-Control-Allow-Origin = *
实现跨域,*
的含义是对所有浏览器请求提供服务。
但是默认情况下,该设置只会开放简单请求的跨域,即满足:
- 请求方法为 GET、POST、HEAD 中的三种
- 请求头不能超出预置的 9 种请求头
如果想让 PUT、PATCH、DELETE 等请求方法也能跨域,或者在自定义请求头时跨域,需要设置如下两个 CORS 响应头
1 | Access-Control-Allow-Headers: * |
它们分别设置了允许发送自定义请求头,允许使用所有方法跨域(默认只允许 get 和 post 跨域)。
最后告诉大家一个好消息,上面所说的全都不用管,有个 express 中间件帮我们解决了以上问题。
npm i cors
1 | const cors = require('cors') |
获取请求参数、查询字符串、请求体
服务器端要完成有效的响应,就需要解析和处理请求过来的数据。
常见的请求数据有三种:
- param,在 url 地址上的请求参数
- query,在 ? 后面的查询字符串,也叫 queryString
- body,请求体
- 获取请求参数 param,我们通过在请求 url 上设置 可变路径 来捕获,然后使用 req.params 获取
1 | app.get('/userInfo/:id', (req, res) => { |
- 获取查询字符串 query,使用 req.query 来获取
1 | app.get('/userInfo', (req, res) => { |
-
获取请求体,这里需要用到内置的中间件 express.json() 还有 express.urlencoded()
1 | app.use(express.json()) |
介绍基础概念
通过上面的例子,我们可以开启一个 express 服务器来做响应了,但是还不够,我们了解一些基础概念才能更好地使用它。
请求对象
express 的 req 是对内置模块 http 的 IncomingMessage 进一步封装,下面是原生 http 的属性
request.method, request.url, request.headers, request.ip
express 扩展的属性和方法
- req.query 获取查询字符串的对象形式
- req.params 获取请求参数的对象形式
- req.body 获取请求体(只有在使用请求体解析中间件时才有)
响应对象
express 的 res 也是对内置模块 http 的 ServerResponse 进一步封装,下面是原生 http 的属性
response.statusCode = 200 设置响应状态码
response.setHeader() 设置响应头
response.end() 添加最后一次数据并结束响应
response.write() 往缓冲区添加数据
express 扩展的属性和方法
- res.send() 添加最后一次数据并结束响应(支持 buffer -> Buffer.from(),json 的自动 stringify,发送 html 文档,发送普通字符串)
- res.json() 添加一次数据,不会结束响应(自动 stringify)
- res.status() 设置 statusCode
- res.cookie(key, value [, config]) 发送 cookie
配套的数据存储方案
express 作为服务器,它的作用是处理请求,发出响应。
在实际开发中,我们更多通过服务器来存储数据,下面介绍一下数据存储方案。
1 | // 通过 db.js 封装文件读写操作 |
路由模块
到目前为止,我们都是在 app 实例上绑定请求处理函数,如果接口增加,server.js 文件会越来越大,浏览和修改都不方便,如何把请求处理函数模块化呢。
使用 express.Router() 创建路由模块,它就相当于 app 的小弟。
1. 将接口按业务做代码分割,划分多个 .js 路由模块文件
2. 每个模块文件中使用 express.Router() 创建路由实例 router。
3. 将请求处理函数绑定到 router 实例上
4. 通过 module.exports 导出路由实例 router
5. 在 app.js 中将路由实例当作中间件来加载
1 | // 这里为路由模块,比如叫 userRouter.js |
1 | // 这里是 app 实例创建的地方,比如叫 server.js |
这样,我们就可以把 API 接口拆分到不同的文件中管理,是不是很方便呢。
中间件
在前面的案例中,无论是解决请求体,还是解决跨域问题,我们都使用到了中间件。
中间件是什么呢?中间件是一个个处理函数,多个中间件函数和路由函数之间共享同一组 request 和 response 对象。
如同废水处理厂处理废水时,需要将废水经过多个处理环节进行处理一样。我们的服务器得到请求后,也是能够分多个阶段来处理数据,中间件既能挂载数据留给下游的阶段,也能集中处理数据,或者起到拦截器或路由守卫的作用,这都取决于你的函数如何定义。
中间件必须接收 3 个参数:
request,response,next
比如下面这个中间件函数
1 | const middleware = function (req, res, next) { |
我们有如下方式使用它
作为全局中间件,通过 app.use 来注册。这样所有的请求进来时,都会优先执行中间件函数,然后再做 url 映射匹配。
1 | app.use(middleware) |
作为局部中间件,将其作为路由函数的形参。只有匹配到该路由时,才会执行中间件函数,然后才到请求处理函数。
1 | app.get('/userInfo', middleware, (req, res) => { |
1 | // 如果想使用多个中间件,可以这样用喔 |
中间件的使用注意事项:
错误级别的中间件
在讲错误级别中间件之前,先看下官方定义的中间件分类:
- 应用级别中间件:绑定在 app 上的中间件
- 路由级别中间件:绑定在 router 上的中间件
- 错误级别中间件:特殊中间件,要配置在所有路由函数之后。
- 内置中间件:在 express 对象上的中间件
- 第三方中间件:需要 npm install 的中间件
不管怎么分类,其实它们的用法都是一样的,但唯独错误级别中间件不同。
我们的路由处理函数,或者中间件函数,在执行发生错误时,会直接崩溃掉。为了防止崩溃,我们通常需要使用 try…catch… 语句来捕获错误。
但是这么多的路由处理函数,这么多的中间件,每一个都去 try…catch… 未免太苦工了。
为此,错误级别中间件油然而生,它专门用来捕获我们整个项目中发生的异常错误,从而防止项目异常崩溃。
它必须接收 4 个参数:
err, request ,response, next
并且它需要放在所有中间件函数和路由函数的后面。
当我们的路由处理函数和中间件函数发生错误时,不需要 try…catch… , 会直接跳到错误级别中间件做集中处理。
1 | app.use(function(err,req,res,next)=>{ |
静态资源托管
express.static() 中间件用于资源托管
路径可以是相对路径
1 | app.use(express.static('public')) |
除了这种方式,也可以手动提供某个资源的 url
比如下面的代码就是用来测试 v-cloak 指令的。用来延迟引入 vue.js
1 | const { resolve } = require('path') |
当然,你也可以不用 sendFile 方法,自己用 fs 模块来发送文件
1 | const fs = require('fs') |