Skip to content

Express

安装

bash
npm install express

创建 web 服务器

javascript
const express = require('express')
const app = express()

app.listen(3000, () => {
  console.log('Server is running')
})

中间件

中间件(Middleware)是一种函数或一组函数,用于在请求到达路由处理程序之前或之后执行特定的任务。中间件允许您在处理请求和响应之间执行一些操作,例如身份验证、日志记录、数据解析等。中间件函数具有访问请求对象 (request)响应对象 (response)下一个中间件函数 (next) 的能力,可以根据需要执行操作并决定是否继续传递控制权给下一个中间件或路由处理程序。

请求对象 APIs

  • req.baseUrl:用于获取当前路由处理程序的基本 URL 路径。它表示当前路由处理程序的路径,不包括主机名和查询参数,但包括应用程序的基本路径。

  • req.body:用于获取客户端发送的请求体数据。如果请求的 Content-Typeapplication/jsonreq.body 将包含解析后的 JSON 数据;如果请求的 Content-Typeapplication/x-www-form-urlencodedreq.body 将包含解析后的表单数据;如果请求的 Content-Typetext/plainreq.body 将包含请求体中的文本数据。通常需要使用 express.json() 解析 JSON 数据和 express.urlencoded() 解析表单数据。

  • req.params:用于获取路由参数的值。如果您在路由路径中定义了参数,可以使用 req.params 访问它们。例如,对于路由 /users/:userId,可以使用 req.params.userId 访问 userId 参数。

  • req.cookies:用于获取客户端发送的请求中包含的 Cookie 数据;需要使用 cookie-parser 中间件,以便正确解析请求中的 Cookie 数据;否则,req.cookies 可能会为一个空对象或包含无效的数据。

  • req.query:用于获取客户端发送的查询参数。查询参数是 URL 中的键值对,通常用于向服务器传递额外的数据,例如在 GET 请求中。这些参数通常位于 URL 的问号后面,如 ?key1=value1&key2=value2。

  • req.signedCookies:用于获取客户端发送的签名的 Cookie 数据。需要使用 cookie-parser 中间件来签名和解析 Cookie。

  • req.fresh:用于检查客户端发送的请求是否仍然有效,或者是否可以使用缓存的响应来满足请求;如果请求是有效的(即可以使用缓存的响应),则返回 true,否则返回 false。

  • req.stale:用于检查客户端请求是否已经过时,即客户端发送的请求是否仍然有效,或者是否可以使用缓存的响应来满足请求。

  • req.hostname:用于获取发送当前请求的客户端的主机名(hostname);主机名通常是客户端的域名或 IP 地址的表示形式,它标识了客户端的位置或来源。

  • req.ip:用于获取发送当前请求的客户端的 IP 地址;IP 地址是客户端在 Internet 上的唯一标识,它表示了客户端的位置或来源。

  • req.ips:用于获取发送当前请求的客户端的 IP 地址列表。这个属性返回一个数组,其中包含了客户端的 IP 地址列表,如果请求经过多个代理服务器,则会记录每个代理服务器的 IP 地址。

  • req.method:用于获取客户端发送当前请求所使用的 HTTP 方法。HTTP 方法定义了客户端对服务器执行的操作,例如 GET、POST、PUT、DELETE 等。

  • req.originalUrl:用于获取客户端发送请求的原始 URL,即客户端请求的完整 URL,包括查询参数和路径。

  • req.path:用于获取客户端发送请求的路径部分,即 URL 中的路径部分,不包括主机名和查询参数。

  • req.protocol:用于获取客户端发送请求的协议名称,例如 "http" 或 "https"。

  • req.route:用于获取与当前请求匹配的路由的相关信息。这个属性返回一个包含路由信息的对象,包括路由的路径、HTTP 方法、处理程序等。

  • req.secure:用于检查客户端请求是否通过 HTTPS 协议进行的,即是否是安全连接。

  • req.subdomains:用于获取客户端请求域名中的子域名数组。

  • req.xhr:用于检查客户端请求是否是通过 XMLHttpRequest 发起的 AJAX 请求。如果客户端请求是通过 XMLHttpRequest 发起的 AJAX 请求,则返回 true,否则返回 false。

  • req.accepts(types):用于检查客户端请求的 Accept 头部,以确定客户端是否接受指定的响应内容类型(MIME 类型)。

  • req.acceptsCharsets(charset [, ...]):用于检查客户端请求的 Accept-Charset 头部,以确定客户端是否接受指定的字符集编码。

  • req.acceptsEncodings(encoding [, ...]):用于检查客户端请求的 Accept-Encoding 头部,以确定客户端是否接受指定的内容编码。

  • req.acceptsLanguages(lang [, ...]):用于检查客户端请求的 Accept-Language 头部,以确定客户端是否接受指定的自然语言或语言标签。

  • req.get(field):用于获取客户端指定请求头的值。例如,req.get('User-Agent') 可以用于获取用户代理头的值

  • req.is(type):用于检查客户端请求的 Content-Type 头部,以确定请求的主体内容是否是指定的媒体类型(MIME 类型)。如果匹配,方法返回 true;否则,返回 false。

  • req.range(size[, options]):用于处理客户端发起的范围请求(Range Requests)。范围请求是一种 HTTP 请求,允许客户端请求资源的一部分而不是整个资源,通常用于支持断点续传、分片下载等功能。

响应对象 APIs

  • res.headersSent:用于检查响应头是否已经被发送到客户端。这个属性返回一个布尔值,如果响应头已经被发送,则返回 true,否则返回 false。

  • res.locals:用于在请求处理过程中传递局部变量(local variables)给视图模板。局部变量是在请求-响应周期内可用的数据,可以在视图模板中访问,用于渲染视图。

  • res.append(field [, value]):用于向响应头中添加一个或多个字段(header fields)或修改现有字段的值。这个方法允许在发送响应之前设置响应头的内容,以满足特定的需求。

  • res.attachment([filename]):用于设置响应头中的 Content-Disposition 标头字段设置为 attachment,以便浏览器提示用户下载响应的内容,而不是在浏览器中直接打开。

  • res.cookie(name, value [, options]):用于设置 HTTP 响应中的 cookie。需要使用 cookie-parser 中间件来解析和设置。

options 选项

属性类型说明
domainStringcookie 的域名,默认为应用的域名
encodeFunction用于 Cookie 值编码的同步函数,默认值为 encodeURIComponent
expiresDateCookie 的到期日期(GMT);如果未指定或设置为 0,则创建会话 Cookie
httpOnlyBoolean将 Cookie 标记为只能由网络服务器访问
maxAgeNumber设置相对于当前时间的到期时间(以毫秒为单位)的便捷选项
pathStringcookie 的路径,默认为“/”
priorityString“优先级”设置-Cookie 属性的值
secureBoolean标记仅与 HTTPS 一起使用的 Cookie
signedBoolean指示是否应对 Cookie 进行签名
sameSiteBoolean or String“SameSite” Set-Cookie 属性的值
  • res.clearCookie(name [, options]):用于从客户端浏览器中删除指定名称的 cookie。需要使用 cookie-parser 中间件来解析和设置。

  • res.download(path [, filename] [, options] [, fn]):用于将文件作为附件发送给客户端,触发浏览器的文件下载操作。

  • res.send([body]):用于向客户端发送 HTTP 响应。

  • res.sendFile(path [, options] [, fn]):用于发送文件作为响应给客户端。这个方法通常用于向客户端提供静态文件,例如图像、CSS、JavaScript 文件或其他资源文件。

  • res.sendStatus(statusCode):用于发送指定 HTTP 状态码的响应,同时设置响应主体内容为该状态码对应的状态消息。

  • res.end([data] [, encoding]):用于结束响应并将最终数据发送给客户端。这个方法通常在请求处理函数的最后调用,以完成响应的发送。

  • res.format(object):用于根据客户端的首选内容类型(Accept 头部)发送不同格式的响应。

  • res.get(field):用于获取响应头中指定字段(header field)的值。

  • res.json([body]):用于向客户端发送 JSON 格式的响应。这个方法将 JavaScript 对象或数组转换为 JSON 字符串,并设置适当的响应头,以确保客户端正确解析响应的内容。

  • res.jsonp([body]):用于向客户端发送 JSONP 格式的响应。

  • res.location(path):用于设置响应头中的 "Location" 字段,以指示客户端将请求重定向到指定的 URL。

  • res.redirect([status,] path):用于执行 HTTP 重定向。

  • res.render(view [, locals] [, callback]):用于渲染视图模板并将其作为响应发送给客户端。这个方法通常用于构建基于模板的网页,以便在服务器端生成 HTML 内容。

  • res.set(field [, value]):用于设置响应头字段的值。通过这个方法,可以在响应中自定义或修改 HTTP 响应头的各种字段。

  • res.status(code):用于设置响应的 HTTP 状态码。

  • res.type(type):用于设置响应的 Content-Type 头部字段,以指定响应的内容类型。通常是一个 MIME 类型字符串,例如 "text/html"、"application/json"、"image/png" 等。

  • res.vary(field):用于指定响应头中的 "Vary" 字段,以表示响应内容的变化依赖于请求头中指定的字段。这个方法通常用于缓存控制,以告诉缓存代理(如代理服务器或 CDN)在哪些请求头字段发生变化时应重新请求资源。

next()

用于将控制权传递给中间件链中的下一个中间件函数,并且通常不携带额外的参数。

next(error)

用于处理发生了错误的请求,并将控制权传递给错误处理中间件。

next('route')

用于跳过当前路由中的所有中间件和处理程序,然后将请求传递给下一个匹配的路由。这通常用于条件性地跳过当前路由并执行其他路由的处理程序。

js
const express = require('express')
const app = express()

// 第一个路由
app.get('/example', (req, res, next) => {
  console.log('First route')
  next('route') // 跳过当前路由,执行下一个匹配的路由
})

// 第二个路由
app.get('/example', (req, res) => {
  console.log('Second route')
  res.send('Hello from the second route!')
})

// 第三个路由
app.get('/another', (req, res) => {
  console.log('Another route')
  res.send('Hello from another route!')
})

app.listen(3000, () => {
  console.log('Express 服务器已启动在端口 3000')
})

/**
 * 当客户端请求 "/example" 时,输出如下:
 * First route
 * Another route
 */

中间件类型

内置中间件

express.static()

用于提供静态文件服务。它允许你将特定目录下的静态文件(如 HTML、CSS、JavaScript 文件、图像文件等)映射到 Express 应用程序的 URL 路由上,使这些文件可以通过浏览器访问。

  • 创建静态资源目录
javascript
app.use(express.static('public'))
/**
 * 可以访问public目录中的文件
 * http://localhost:3000/images/kitten.jpg
 * http://localhost:3000/css/style.css
 * http://localhost:3000/js/app.js
 * http://localhost:3000/images/bg.png
 * http://localhost:3000/hello.html
 */

TIP

Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,存放静态文件的目录名不会出现在 URL 中。

  • 创建多个静态资源目录
javascript
app.use(express.static('public'))
app.use(express.static('files'))

TIP

访问静态资源文件时,express.static 会根据目录的添加顺序查找所需的文件。

  • 创建虚拟路径前缀
javascript
app.use('/static', express.static('public'))
/**
 * 可以从 /static 路径前缀加载 public 目录中的文件
 * http://localhost:3000/static/images/kitten.jpg
 * http://localhost:3000/static/css/style.css
 * http://localhost:3000/static/js/app.js
 * http://localhost:3000/static/images/bg.png
 * http://localhost:3000/static/hello.html
 */

DANGER

注意:

  • 虚拟路径实际上并不存在于文件系统中
  • express.static 提供的路径相对于启动 node 进程的目录,若从另一个目录运行 express 应用,则使用要运行目录的绝对路径会更安全
js
const path = require('path')
app.use('/static', express.static(path.join(__dirname, 'public')))
express.json()

用于解析客户端发送 POST 请求并包含 JSON 数据时,会将请求体解析为 JavaScript 对象,并将其附加到 Express 请求对象 (req.body) 中,以便在后续的路由处理中使用。

js
app.use(express.json())
express.urlencoded()

用于解析客户端以 application/x-www-form-urlencoded 格式编码发送 POST 请求并通过 HTML 表单提交数据时,将其解析为 JavaScript 对象,并将其附加到 Express 请求对象 (req.body) 中,以便在后续的路由处理中使用。

js
app.use(express.urlencoded({ extended: false }))

TIP

  • extended: false(默认行为)

当你将 express.urlencoded({ extended: false }) 中间件配置为 false 时,它会使用 Node.js 内置的 querystring 模块来解析 URL 编码数据。此时,解析后的数据会采用一种更加简单的形式,例如 { 'foo': 'bar' }。这意味着如果表单字段名中包含了特殊字符,它们将被解析为字符串。`

js
// 表单字段名包含特殊字符时(如 <input type="text" name="name[first]">)
// 解析后的数据格式如下:{ 'name[first]': 'John' }
  • extended: true

当你将 express.urlencoded({ extended: true }) 中间件配置为 true 时,它会使用第三方库 qs(或 querystringify)来解析 URL 编码数据。此时,解析后的数据会采用一种更加复杂的形式,例如 { 'name': { 'first': 'John' } }。这意味着表单字段名中包含的特殊字符会被解析为嵌套对象。

js
// 表单字段名包含特殊字符时(如 <input type="text" name="name[first]">)
// 解析后的数据格式如下:{ 'name': { 'first': 'John' } }
express.Router()

用于创建可重用的路由模块。允许将一组相关的路由和处理程序组织成一个单独的模块,然后将该模块挂载到主应用程序或其他路由器上。

js
const router = express.Router()

应用程序级中间件

使用 app.use() and app.METHOD() 函数将应用程序级中间件绑定到应用程序对象的实例,其中 METHOD 中间件函数以小写形式处理的请求(例如 GET、PUT 或 POST)的 HTTP 方法。

js
const express = require('express')
const app = express()

app.use((req, res, next) => {
  console.log('Time:', Date.now())
  next()
})

app.use('/user/:id', (req, res, next) => {
  console.log('Request Type:', req.method)
  next()
})

app.get('/user/:id', (req, res, next) => {
  res.send('USER')
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

TIP

要跳过路由器中间件堆栈中的其余中间件功能,请调用 next('route') 以将控制权传递给下一个路由。注意: next('route') 仅适用于使用 app.METHOD() 或 router.METHOD() 函数加载的中间件函数。

js
app.get(
  '/user/:id',
  (req, res, next) => {
    if (req.params.id === '0') next('route')
    else next()
  },
  (req, res, next) => {
    res.send('regular')
  }
)

app.get('/user/:id', (req, res, next) => {
  res.send('special')
})
APIs
  • app.locals:用于在整个应用程序范围内共享变量或数据。这些变量在所有的路由处理函数、中间件和视图中都可以访问,因为它们存储在应用程序级别的上下文中。通常,app.locals 用于存储全局数据,如配置信息、常量或共享的模板变量。

  • app.all(path, callback [, callback ...]):用于指定一个或多个中间件函数,这些中间件函数将在所有 HTTP 方法(GET、POST、PUT、DELETE 等)的请求上执行,并且只要请求路径与指定的 path 匹配,就会执行这些中间件。

DANGER

请注意,app.all 中间件将在路由处理函数之前执行,因此如果有多个路由匹配到相同的路径,app.all 中间件将首先执行。这可以用于在所有请求上共享相同的逻辑,例如身份验证、日志记录或跨域处理。

  • app.get(path, callback [, callback ...]):用于定义处理 HTTP GET 请求的路由处理程序的方法。

  • app.post(path, callback [, callback ...]):用于定义处理 HTTP POST 请求的路由处理程序。

  • app.put(path, callback [, callback ...]):用于定义处理 HTTP PUT 请求的路由处理程序。

  • app.delete(path, callback [, callback ...]):用于指定处理 HTTP DELETE 请求的路由处理函数。

  • app.disable(name):用于禁用应用程序的设置或功能。

  • app.disabled(name):用于检查指定的设置或功能是否已被禁用。它返回一个布尔值,表示指定的设置是否被禁用。

  • app.enable(name):用于启用应用程序的设置或功能。

  • app.enabled(name):用于检查指定的设置或功能是否已被启用。它返回一个布尔值,表示指定的设置是否已启用。

  • app.engine(ext, callback):用于注册自定义视图引擎。

  • app.listen([port[, host[, backlog]]][, callback]):用于启动 HTTP 服务器的方法。这个方法通常在 Express 应用程序中使用,用于监听指定的端口和主机,以便处理传入的 HTTP 请求。

  • app.param([name], callback):用于在路由处理中定义动态参数的回调函数的方法。这个方法允许在路由处理程序中执行一些逻辑,以处理特定的参数值,然后将结果传递给路由处理程序的下一步。

  • app.render(view, [locals], callback):通过 callback 函数返回视图呈现的 HTML。它接受一个可选参数,该参数是包含视图局部变量的对象。它类似于 res.render(),只是它不能将渲染的视图自行发送到客户端。

TIP

可以将其视为 app.render() 用于生成呈现视图字符串的实用程序函数。 res.render() 内部用于 app.render() 呈现视图。

  • app.route(path):用于创建和组织路由的方法。它允许为特定的路由路径创建一个路由对象,然后在该对象上定义多个 HTTP 请求方法的处理程序,以便更好地组织代码和避免重复定义相同的路由路径。

  • app.set(name, value):用于配置应用程序设置的方法。

  • app.use([path,] callback [, callback...]):用于添加中间件(middleware)的方法。

路由器级中间件

路由器级中间件的工作方式与应用程序级中间件相同,只是它绑定到的 express.Router() 实例。

路由模块化
  1. 创建路由器,在路由器对象上定义路由,可以使用 HTTP 请求方法如 get、post、put、delete 等。这些路由可以具有特定的路径和处理程序。
js
// router.js
const express = require('express')
const router = express.Router()

router.use('/user/:id', (req, res, next) => {
  console.log('Request URL:', req.originalUrl)
  next()
})

router.get('/', (req, res) => {
  res.send('Birds home page')
})

module.exports = router
  1. 挂载路由器,将路由器挂载到主 Express 应用程序上,以便在主应用程序的特定路径下使用它。使用 app.use() 方法来完成挂载。
js
// main.js
const express = require('express')
const app = express()

app.use('/api', require('./router'))

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})
APIs
  • router.all(path, [callback, ...] callback):用于定义路由处理程序,以处理指定路径上的所有 HTTP 请求方法(GET、POST、PUT、DELETE 等)的方法。这意味着无论客户端发送的是什么类型的请求方法,都会由路由处理程序来处理。

  • router.get(path, callback [, callback ...]):用于定义处理 HTTP GET 请求的路由处理程序的方法。

  • router.post(path, callback [, callback ...]):用于定义处理 HTTP POST 请求的路由处理程序。

  • router.put(path, callback [, callback ...]):用于定义处理 HTTP PUT 请求的路由处理程序。

  • router.delete(path, callback [, callback ...]):用于指定处理 HTTP DELETE 请求的路由处理函数。

  • router.param(name, callback):用于定义路由参数的中间件。这个方法允许在路由处理程序中定义参数,以便在多个路由中复用相同的参数处理逻辑。

DANGER

注意: 与 app.param() 不同, router.param() 不接受路由参数数组。

  • router.route(path):用于创建和组织路由的方法,类似于 app.route(path)

  • router.use([path], [function, ...] function):用于添加中间件(middleware)到路由对象的方法。

错误处理中间件

错误处理中间件始终采用四个参数。必须提供四个参数才能将其标识为错误处理中间件函数。即使不需要使用该 next 对象,也必须指定它以维护签名。否则,该 next 对象将被解释为常规中间件,并且无法处理错误。

javascript
const express = require('express')
const app = express()

app.get('/error', (req, res, next) => {
  const error = new Error('Custom error')
  next(error)
})

app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something went wrong')
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

第三方中间件

helmet

专门用于增强 Web 应用程序的安全性。它通过设置 HTTP 标头来帮助防止各种 Web 安全漏洞和攻击,使你的应用程序更加安全。

  • 安装
bash
npm install helmet
  • 使用
js
const express = require('express')
const helmet = require('helmet')
const app = express()

// 使用helmet中间件
app.use(helmet())

标头

  • Content-Security-Policy (CSP):

    用于配置哪些资源可以加载到网页中以减少 XSS 攻击的风险。

  • Cross-Origin-Opener-Policy:

    用于控制网页的执行上下文,以帮助减少不同窗口或标签页之间的安全问题。

  • Cross-Origin-Resource-Policy:

    用于配置哪些资源可以被跨域请求获取,以增加对跨站点数据泄漏的保护。

  • Origin-Agent-Cluster:

    用于控制浏览器是否允许资源与其他代理集群共享。

  • Referrer-Policy:

    用于控制浏览器在发出请求时传递的 Referer 标头,以限制信息泄漏。

  • Strict-Transport-Security (HSTS):

    用于强制使用 HTTPS 与服务器通信,以减少中间人攻击的风险。

  • X-Content-Type-Options:

    阻止浏览器在识别响应内容类型时进行 MIME 类型嗅探,提高 XSS 防护。

  • X-DNS-Prefetch-Control:

    控制浏览器是否可以预解析域名,以减少 DNS 预取攻击的风险。

  • X-Download-Options:

    防止 IE 浏览器执行可执行文件(例如.exe、.msi)等文件。

  • X-Frame-Options:

    用于控制是否允许在<frame><iframe><object>中加载网页,以防止点击劫持攻击。

  • X-Permitted-Cross-Domain-Policies:

    用于配置跨域策略文件的位置和是否允许使用 Flash 等。

  • X-Powered-By:

    通常是一个漏洞,因为它可以透露你的应用程序使用的技术。最好禁用或隐藏它。

  • X-XSS-Protection:

    启用浏览器内置的跨站点脚本(XSS)过滤器,以防止 XSS 攻击。

passport

用于处理用户身份验证和授权。它被广泛用于构建基于 Node.js 的 Web 应用程序,特别是 Express.js 应用程序。

  1. Local Strategy:
  • 安装
bash
npm install passport passport-local
  • 配置策略
js
passport.use(
  new LocalStrategy(function (username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) {
        return done(err)
      }
      if (!user) {
        return done(null, false)
      }
      if (!user.verifyPassword(password)) {
        return done(null, false)
      }
      return done(null, user)
    })
  })
)
  • 持久会话
js
passport.serializeUser(function (user, done) {
  done(null, user.id)
})

passport.deserializeUser(function (id, done) {
  User.findById(id, function (err, user) {
    done(err, user)
  })
})
  • 中间件
js
const app = express()

app.use(passport.initialize())
app.use(passport.session())
  • 验证请求
js
app.post(
  '/login',
  passport.authenticate('local', { failureRedirect: '/login' }),
  (req, res) => {
    res.redirect('/')
  }
)
  1. JWT Strategy
  • 安装
bash
npm install passport passport-jwt
  • 配置策略
js
const JwtStrategy = require('passport-jwt').Strategy
const ExtractJwt = require('passport-jwt').ExtractJwt

const opts = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: 'secret'
}

passport.use(
  new JwtStrategy(opts, function (jwt_payload, done) {
    User.findOne({ id: jwt_payload.sub }, function (err, user) {
      if (err) {
        return done(err, false)
      }
      if (user) {
        return done(null, user)
      } else {
        return done(null, false)
      }
    })
  })
)

TIP

  1. opts 选项:
  • secretOrKey:是一个包含用于验证令牌签名的密钥(对称)或 PEM 编码的公钥(非对称)的字符串或缓冲区。如果未提供 secretOrKeyProvider,则此项是必需的。
  • secretOrKeyProvider:是一个回调函数,格式为 function secretOrKeyProvider(request, rawJwtToken, done),应该调用 done 并提供给定密钥和请求组合的密钥或 PEM 编码的公钥(非对称)。done 接受的参数格式为 function done(err, secret)。请注意,解码 rawJwtToken 的工作由实施者负责。除非提供了 secretOrKey,否则此项为必填项。
  • jwtFromRequest (REQUIRED):是一个接受请求作为唯一参数的函数,它要么返回 JWT 字符串,要么返回 null。
  • issuer:如果已定义,将根据此值验证令牌颁发者 (iss)。
  • audience:如果已定义,将根据此值验证令牌受众 (aud)。
  • algorithms:包含允许算法名称的字符串列表,例如:[“HS256”、“HS384”]。
  • ignoreExpiration:如果为 true,则不验证令牌的过期时间。
  • passReqToCallback:如果为 true,则请求将传递给验证回调,即验证(请求、jwt_payload、done_callback)。
  • jsonWebTokenOptions:passport-jwt 使用 jsonwebtoken 验证令牌。 传递一个选项对象,用于传递 jsonwebtoken 验证器的任何其他选项,例如:maxAge 。
  1. callback 选项:
  • jwt_payload 是包含解码 JWT 有效负载的对象文本。
  • done 是一个回调函数,接受 3 个参数(即:err、user、info)。
  1. passport-jwt.ExtractJwt 方法:
  • fromHeader(header_name): 创建一个新的提取器,用于在给定的 HTTP 标头中查找 JWT
  • fromBodyField(field_name): 创建一个新的提取器,用于在给定的 body 字段中查找 JWT。必须配置 body-parser 才能使用此方法。
  • fromUrlQueryParameter(param_name): 创建一个新的提取器,用于在给定的 URL 查询参数中查找 JWT。
  • fromAuthHeaderWithScheme(auth_scheme): 创建一个新的提取程序,该提取器在 Authorization 标头中查找 JWT,与 auth_scheme 匹配。
  • fromAuthHeaderAsBearerToken(): 创建一个新的提取器,该提取器在带有 Bearer 前缀的 Authorization 标头中查找 JWT 。
  • fromExtractors([array of extractor functions]): 使用提供的提取器数组创建一个新的提取器。 每个提取器都会按顺序执行,直到某个提取器返回一个 Token 。
  • 验证请求
js
app.post(
  '/profile',
  passport.authenticate('jwt', { session: false }),
  function (req, res) {
    res.send(req.user.id)
  }
)
  1. OAuth2.0 Strategy
  • 安装
bash
npm install passport passport-oauth2
  • 配置策略
js
passport.use(
  new OAuth2Strategy(
    {
      authorizationURL: 'https://www.example.com/oauth2/authorize',
      tokenURL: 'https://www.example.com/oauth2/token',
      clientID: EXAMPLE_CLIENT_ID,
      clientSecret: EXAMPLE_CLIENT_SECRET,
      callbackURL: 'http://localhost:3000/auth/example/callback'
    },
    function (accessToken, refreshToken, profile, cb) {
      User.findOrCreate({ exampleId: profile.id }, function (err, user) {
        return cb(err, user)
      })
    }
  )
)
  • 验证请求
js
app.get('/auth/example', passport.authenticate('oauth2'))

app.get(
  '/auth/example/callback',
  passport.authenticate('oauth2', { failureRedirect: '/login' }),
  function (req, res) {
    // Successful authentication, redirect home.
    res.redirect('/')
  }
)

::: 第三平台常用策略

  • passport-qq:QQ 登录
  • passport-weixin:微信登录

:::

jsonwebtoken

jsonwebtoken 是一个用于生成和验证 JSON Web Tokens (JWT) 的库。JWT 是一种用于在不同系统之间安全传输信息的令牌。jsonwebtoken 允许您在服务器端生成令牌,并验证接收到的令牌是否有效和未被篡改。

  • 安装
bash
npm install jsonwebtoken
  • 使用
javascript
const jwt = require(jsonwebtoken)
  • APIs
js
//加密
jwt.sign(payload, secretOrPrivateKey, [options, callback])

//解密
jwt.verify(token, secretOrPublicKey, [options, callback])
jwtDecode

jwtDecode 是一个用于解码 JWT 的工具,通常用于从 JWT 中提取信息,以便在应用程序中进行验证和授权。该工具可以将 JWT 字符串解码为包含头部、载荷和签名的 JSON 对象,使得开发人员可以轻松访问和使用其中的信息。

  1. 安装
bash
npm install jwt-decode
  1. 使用

    js
    const jwtDecode = require('jwt-decode');
    const decodedToken = jwtDecode(token);

TIP

JWT(JSON Web Token)是一种用于在网络上安全地传输信息的开放标准(RFC 7519)。JWT由三部分组成,分别是头部(Header)、载荷(Payload)和签名(Signature)。这三部分通过点号(.)连接在一起,形成一个编码后的字符串,,成为最终的 JWT。

  • 头部(Header)
    • alg(Algorithm): 表示用于签名令牌的算法,例如 HMAC SHA256 或 RSA。
    • typ(Type): 表示令牌的类型,即 "JWT"。
  • 载荷(Payload): JSON 对象,包含有关令牌的声明和信息。
    • exp(Expiration Time): 令牌的过期时间,表示令牌在哪个时间点之后将会失效。
    • iss(Issuer): 令牌的发行者。
    • sub(Subject): 令牌的主题,通常是用户的唯一标识符。
    • aud(Audience): 令牌的受众,即预期使用令牌的一方。
    • nbf(Not Before): 令牌的生效时间,表示在此时间之前令牌不可用。
    • iat(Issued At): 令牌的签发时间。
    • jti(JWT ID): 令牌的唯一标识符,用于防止重放攻击。
csrf

csrf 是一个 Express.js 中间件,用于防止跨站请求伪造(CSRF)攻击。CSRF 攻击是一种恶意攻击,攻击者通过利用用户已登录的身份在用户不知情的情况下执行不期望的操作。csrf 中间件通过生成和验证 CSRF 令牌来保护应用程序免受此类攻击。

  1. 安装
bash
npm install csrf
  1. 用法
js
const express = require('express')
const csrf = require('csrf')

const app = express()
const tokens = new csrf()

// 设置 CSRF 令牌
app.use((req, res, next) => {
  const secret = tokens.secretSync()
  const token = tokens.create(secret)
  req.csrfSecret = secret
  res.locals.csrfToken = token
  next()
})

// 路由处理程序
app.get('/', (req, res) => {
  // 渲染包含 CSRF 令牌的表单
  res.send(`
    <form method="POST" action="/process">
      <input type="hidden" name="_csrf" value="${res.locals.csrfToken}">
      <input type="text" name="data" placeholder="Enter data">
      <button type="submit">Submit</button>
    </form>
  `)
})

app.post('/process', express.urlencoded({ extends: false }), (req, res) => {
  // 验证 CSRF 令牌
  if (!tokens.verify(req.csrfSecret, req.body._csrf)) {
    return res.status(403).send('CSRF Token Invalid')
  }

  // 处理 POST 请求
  const data = req.body.data
  res.send(`Data processed successfully: ${data}`)
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})
hpp

hpp(HTTP Parameter Pollution)是用于检查请求中的查询参数和请求体中的表单字段,确保它们没有出现重复的参数名,以防止 HTTP 参数污染攻击的 Express.js 中间件。HTTP 参数污染攻击是一种安全漏洞,攻击者试图通过篡改或者重复参数来混淆应用程序的处理,从而可能导致安全问题。

  1. 安装
bash
npm install hpp
  1. 用法
js
const express = require('express')
const hpp = require('hpp')

const app = express()

app.use(
  hpp({
    /**
     * 一个布尔值,用于指定是否检查查询参数。
     * 如果设置为 true(默认值),则 hpp 中间件将检查查询参数;
     * 如果设置为 false,则不会检查查询参数。
     */
    checkQuery: true,
    /**
     * 一个布尔值,用于指定是否检查请求体中的表单字段。
     * 如果设置为 true(默认值),则 hpp 中间件将检查请求体中的表单字段;
     * 如果设置为 false,则不会检查请求体中的字段。
     */
    checkBody: true,
    /**
     * 一个包含字符串的数组,用于指定哪些查询参数不应受到 hpp 中间件的影响。
     * 这些参数将被排除在外,不会进行重复处理。
     */
    whitelist: ['page', 'limit']
  })
)
rate-limiter-flexible

rate-limiter-flexible 是一个用于 Node.js 的速率限制库,可以帮助你限制应用程序接受的请求速率,以防止滥用和恶意请求。它具有高度的灵活性,可以根据需要进行配置,以满足不同的应用程序需求。

  1. 安装
bash
npm install rate-limiter-flexible
  1. 用法
  • 使用内存作为存储引擎(默认选项)
js
const express = require('express')
const { RateLimiterMemory } = require('rate-limiter-flexible')

const app = express()

// 创建速率限制器实例
const limiter = new RateLimiterMemory({
  points: 5, // 允许的点数(请求数)
  duration: 1 // 时间窗口的秒数
})

// 定义中间件来执行速率限制
const rateLimitMiddleware = (req, res, next) => {
  limiter
    .consume(req.ip) //使用客户端的 IP 地址来区分不同的请求者,1个请求消耗 1个请求点数
    .then((rateLimiterRes) => {
      next() // 请求在速率限制内,继续处理
    })
    .catch((rateLimiterRes) => {
      // 请求超出了速率限制,返回错误响应
      res.status(429).send('Too Many Requests')
    })
}

// 应用速率限制中间件
app.use(rateLimitMiddleware)

// 路由处理程序
app.get('/', (req, res) => {
  res.send('Hello, Rate-Limited World!')
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})
  • 使用数据库作为存储引擎(以 Redis 为例)
js
const express = require('express')
const { RateLimiterMemory, RateLimiterRedis } = require('rate-limiter-flexible')
const redis = require('redis')

const app = express()

// 连接 Redis
const redisClient = redis.createClient({ host: 'localhost', port: 6379 })

// 创建速率限制器实例
const limiter = new RateLimiterRedis({
  storeClient: redisClient, // 使用 Redis 客户端
  keyPrefix: 'redis',
  points: 5, // 允许的点数(请求数)
  duration: 1 // 时间窗口的秒数
})

// 定义中间件来执行速率限制
const rateLimitMiddleware = (req, res, next) => {
  limiter
    .consume(req.ip) // 使用客户端的 IP 地址来区分不同的请求者
    .then((rateLimiterRes) => {
      next() // 请求在速率限制内,继续处理
    })
    .catch((rateLimiterRes) => {
      // 请求超出了速率限制,返回错误响应
      res.status(429).send('Too Many Requests')
    })
}

// 应用速率限制中间件
app.use(rateLimitMiddleware)

// 路由处理程序
app.get('/', (req, res) => {
  res.send('Hello, Rate-Limited World!')
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

RateLimiterRes 对象

  • msBeforeNext:这表示在下一个请求可以被允许之前,需要等待的毫秒数
  • remainingPoints:这表示客户端在当前时间窗口内剩余的点数
  • consumedPoints:这表示客户端在当前时间窗口内已经消耗的点数
  • isFirstInDuration:这表示当前请求是否是在当前时间窗口内的第一个请求
  1. APIs:
  • RateLimiterMemory: 用于创建基于内存的速率限制器实例
js
const limiter = new RateLimiterMemory(options)
  • RateLimiterRedis: 用于创建基于 Redis 的速率限制器实例
js
const limiter = new RateLimiterRedis(options)

options 选项

  • points:默认为 4 ,在持续时间内可以消耗的最大点数。Limiter 将此数字与 limiter.consume(key, points) 中配置消耗的点数 points 进行比较,以确定是否应拒绝或解决操作。
  • duration: 默认为 1 ,重置消耗 points 之前的持续时间(单位:秒),如果为 0 ,则密钥永不过期。
  • keyPrefix: 默认为 'rlflx' ,使用存储引擎(如内存或 Redis)来存储 limiter 的数据时,用于区分不同 limiter 的标识前缀;如果设置为空字符串 '' , limiter 则不带前缀存储。
  • blockDuration: 默认为 0 ,如果客户端超出速率限制,需要被阻塞的时间,以秒为单位。默认为 0,表示不会被阻塞。如果设置为大于 0 的值,客户端在超出限制后会被阻塞指定的时间。
  • storeClient:默认为 undefined,使用内存存储,也可以自定义存储引擎(必须是 redis memcached mongodb pg mysql2 mysql ioredis )来存储客户端的 limiter 数据。
  • consume: 用于消耗指定数量的点数(points)来验证请求是否受到速率限制。
js
limiter.consume(key, points)

TIP

  • key: 通常是 IP 地址或一些唯一的字符串
  • points: 发起请求时所消耗的点数,默认值为 1
cors

cors 是一个中间件,用于处理跨域资源共享 (CORS)。它为 Express 应用程序添加了一些 HTTP 标头,以允许来自其他域的请求访问您的服务器资源。

  • 安装
bash
npm install cors
  • 使用
javascript
const express = require('express')
const cors = require('cors')

const app = express()

app.use(cors())
  • 配置
javascript
cors({
  /**
   * 指定允许的请求来源
   * 配置 "Access-Control-Allow-Origin" CORS 标头,可选值:
   *  1. Boolean:将 origin 设置为 true(默认值)服务器会根据请求的来源(通过 req.header('Origin') 获取)来决定是否允许跨源请求。设置为 false 服务器将禁用 CORS 支持,不会检查请求的来源;
   *  2. String:将 origin 设置为指定请求的来源 。例如,如果将其设置为 "http://example.com" 仅允许来自“http://example.com”的请求;
   *  3. RegExp:将 origin 设置为一个正则表达式模式时,服务器将使用该模式来测试请求的来源(通过 req.header('Origin') 获取)。如果请求的来源与正则表达式模式匹配,那么服务器将允许跨源请求(例如,模式 /example\.com$/ 将允许来自以“example.com”结尾的来源的请求),反之则不允许;
   *  4. Array:将 origin 设置为一个包含有效请求的来源的数组。每个请求的来源可以是一个字符串(String)或正则表达式(RegExp)。这允许服务器指定一组允许的来源,从而决定是否允许跨源请求(例如,数组 ["http://example1.com", /\.example2\.com$/] 将允许来自 "http://example1.com" 和以 ".example2.com" 结尾的来源的请求);
   *  5. Function:将 origin 设置为一个自定义逻辑的函数。这个函数会接受请求的来源作为第一个参数,并接受一个回调函数(callback)作为第二个参数,该回调函数期望有特定的签名,即 (err, allow),其中 err 是一个对象,allow 是一个布尔值;
   */
  origin: '*',

  /**
   * 指定允许的请求方式
   * 配置 "Access-Control-Allow-Methods" CORS 标头,可选值:逗号分隔的字符串(例如:'GET,PUT,POST')或数组(例如:['GET', 'PUT', 'POST'])
   */
  methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',

  /**
   * 指定允许的请求头
   * 配置 " Access-Control-Allow-Headers" CORS 标头,可选值:逗号分隔的字符串(例如:'Content-Type,Authorization')或数组(例如:['Content-Type', 'Authorization'])
   * 如果未指定,则默认允许请求的 Access-Control-Request-Headers 标头中指定的标头
   */
  allowedHeaders: ['Content-Type,X-Requested-With,Authorization'],

  /**
   * 指定哪些响应头部应该暴露给浏览器
   * 配置 "Access-Control-Expose-Headers" CORS 标头,可选值:逗号分隔的字符串(例如:'Content-Range,X-Content-Range')或数组(例如:['Content-Range', 'X-Content-Range'])
   * 如果未指定,则不会暴露任何自定义标头
   */
  exposedHeaders:'Authorization'

  /**
   * 是否允许发送 Cookie
   * 配置 "Access-Control-Allow-Credentials" CORS 标头,设置为 true 以传递标头,否则省略。
   */
  credentials: true,

  /**
   * 指定浏览器缓存 CORS 预检请求的时间(以秒为单位)
   * 配置 "Access-Control-Max-Age" CORS 标头,设置为一个整数以传递标头,否则省略。
   */
  maxAge:1000*60*10

  /**
   * 是否将 CORS 预检请求的响应传递给下一个请求处理程序(handler)
   * 设置为 false,服务器将不会将预检请求的响应传递给下一个请求处理程序,而是直接发送响应给客户端
   * 设置为 true,服务器将继续处理预检请求的响应,并将它传递给下一个请求处理程序
   */
  preflightContinue:false,

  /**
   * 指定在成功的 OPTIONS 请求中使用的 HTTP 状态码
   * 通常只在需要支持特定旧版浏览器或设备时才会用到,大多数现代浏览器都支持 HTTP 204 作为预检请求成功的状态码
   */
  optionsSuccessStatus:204
})

TIP

前端设置请求是否携带跨域请求时的凭证信息(包括 Cookies、HTTP 认证信息等):

  • 在 htmlHttpRequest 对象上设置 withCredentials 属性为 true
javascript
var xhr = new htmlHttpRequest()
xhr.withCredentials = true
xhr.open('GET', 'http://example.com/', true)
xhr.send(null)
  • 在 Axios 对象上设置 withCredentials 属性为 true(后端也要同步设置)
javascript
axios.defaults.withCredentials = true

axios
  .get('http://api.example.com/data')
  .then((response) => {
    console.log(response.data)
  })
  .catch((error) => {
    console.error(error)
  })
  • 在 fetch API 中设置 credentials 选项为 'include'
javascript
fetch('http://example.com/', {
  method: 'GET',
  credentials: 'include'
})
  • 在创建 WebSocket 对象时设置 withCredentials 选项为 true
javascript
var socket = new WebSocket('ws://example.com/')
socket.withCredentials = true

cookie-parser 是一个中间件,用于解析和处理 HTTP 请求中的 Cookie。它将请求中的 Cookie 解析为 JavaScript 对象,以方便在应用程序中使用。

  • 安装
bash
npm instal cookie-parser
  • 使用
javascript
const express = require('express')
const cookieParser = require('cookie-parser')

const app = express()

app.use(cookieParser())
express-session

express-session 是一个中间件,用于在 Express 应用程序中处理会话管理。它为每个客户端创建唯一的会话标识符,并将相关数据存储在服务器上,以便跟踪用户的状态和身份验证信息。

  • 安装
bash
npm install express-session
  • 使用
js
const session = require('express-session')
  • APIs

session(options)

js
session({
  cookie:{
    // 指定Cookie的域名,可以限制Cookie只在特定的域名下发送。
    domain:127.0.0.1,

    /**
     * 设置Session的过期时间,通常以毫秒为单位。当Session过期后,用户需要重新登录。
     * 注意:
     *  1. 如果在选项中同时设置 expires 了 和 maxAge ,则对象中定义的最后一个是使用的。
     *  2. 不应直接设置该 expires 选项;而是仅使用该 maxAge 选项。
     */
    expires:1000*60*10,

    // 布尔值,如果设置为true,只有服务器端可以访问Session Cookie,客户端的JavaScript无法访问它,以增加安全性。
    httpOnly:true,

    // Session的最大寿命,通常以毫秒为单位。与expires类似,指定Session的有效期。
    maxAge:1000*60*10,

    // 指定Cookie的路径,用于限制Cookie只在特定路径下发送。
    path:'/',

    /**
     * 用于设置 Cookie 的属性,用于控制 Cookie 的发送行为
     * 可选值:
     *  true:将SameSite属性设置为Strict,以进行严格的同站点强制。这意味着Cookie仅在同一站点的请求中发送,防止跨站点请求伪造(CSRF)攻击。
     *  false:不设置SameSite属性,使Cookie按默认行为工作。这可能是浏览器的默认行为,通常是Lax模式。
     *  'lax':将SameSite属性设置为Lax,以进行宽松的同站点强制。Cookie会在导航到链接的情况下发送,但在POST请求中不会发送,从而减少一些CSRF攻击。
     *  'none':将SameSite属性设置为None,允许显式的跨站点Cookie。通常需要与Secure属性一起使用,以确保Cookie只通过HTTPS连接发送。
     *  'strict':将SameSite属性设置为Strict,以进行严格的同站点强制,与true的效果相同。
     * 注意:这是一个尚未完全标准化的属性,将来可能会更改。
     */
    sameSite:false,

    /**
     * 布尔值,如果设置为true,Cookie只有在HTTPS连接下才会发送,增加安全性。
     * 请注意, secure: true 是推荐的选项。但是,它需要一个支持 https 的网站,即 HTTPS 对于安全 cookie 是必需的。
     * 如果设置了 secure,并且通过 HTTP 访问您的站点,则不会设置 cookie。
     * 如果node.js位于代理后面并且使用secure: true,则需要在express中设置“信任代理”:app.set('trust proxy', 1)
     */
    secure:false,
  }

  // 布尔值,表示是否在每次请求时都重新保存Session数据,一般设置为false以减少服务器负担。
  resave:false,

  /**
   * 自定义Session ID 生成函数,可以用于生成唯一的Session ID。
   * 默认值是使用 uid-safe 库生成 ID 的函数。
   * 注意 请小心生成唯一 ID,以便 Session 不会冲突。
   */
  genid:function(req) { return genuuid() },

  // Session的名称,用于设置Cookie的名称,默认为"connect.sid"。
  name:'SessionOne',

  /**
   * 布尔值,用于指示是否应信任反向代理的请求。
   * 可选值:
   *  true:表示Express会考虑请求头中的"X-Forwarded-Proto"标头。
   *  false:表示Express将忽略所有请求头,仅考虑是否存在直接的TLS/SSL连接来判断连接是否安全。
   *  undefined:表示Express将根据其配置中的"trust proxy"设置来确定是否信任代理。
   */
  proxy:undefined,

  /**
   * 布尔值,如果设置为true,表示每次请求都会重新设置Session的过期时间,用于延长Session的有效期。
   * 注意 如果此选项设置为 true ,但 saveUninitialized 该选项设置为 false ,则不会在具有未初始化 Session 的响应上设置 cookie。此选项仅修改为请求加载现有 Session 时的行为。
   */
  rolling:false,

  // 布尔值,表示是否在Session中存储未初始化的数据。一般设置为false,以减少存储开销。
  saveUninitialized:false,

  // 必需选项,用于签名Session的密钥,以增加安全性。
  secret:'koency',

  /**
   * 用于存储Session数据的存储引擎,可以选择使用默认的MemoryStore或其他存储引擎,如MongoDB、Redis。
   * 需要引入 const MongoStore = require('connect-mongo')
   */
  store:MongoStore.create({ mongoUrl: 'mongodb://127.0.0.1:27017/session' }),

  /**
   * 表示Session在销毁时是否将Cookie删除。
   * 可选值:
   *  'destroy':这个选项表示会话将在响应结束时被销毁(删除)。
   *  keep':这个选项表示会话将保留在存储中,但在请求期间对会话所做的任何修改都会被忽略,并且不会保存。
   */
  unset:keep,
})

注意

  1. Session 数据不保存在 Cookie 本身中,仅保存在 Session ID 中。
  2. 默认情况下会创建一个名为 "connect.sid" 的 Cookie 来固定存储(不可更改) Session ID 和相关的会话数据。
  3. 默认 Session 数据存储在服务器端 MemoryStore 中,而 MemoryStore 并非特意为生产环境而设计。在大多数情况下,它会泄漏内存,不会扩展到单个进程之后,并且用于调试和开发。
  4. 从版本 1.5.0 开始,此模块不再需要使用 cookie-parser 中间件即可工作。此模块现在直接在 req / res 上读取和写入 cookie。如果此模块和 cookie-parser 之间的 secret 不同,则使用 cookie-parser 可能会导致问题。

req.session

js
// 用于跟踪用户访问网站的次数或页面浏览次数
req.session.views

// 用于重新生成会话(session)
req.session.regenerate(callback)

// 用于销毁用户的会话(session)
req.session.destroy(callback)

// 从存储中重新加载 Session 数据并重新填充 req.session 对象
req.session.reload(callback)

//用于手动保存会话(session)数据
req.session.save(callback)

// 用于更新会话(session)的过期时间
req.session.touch()

// 用于获取当前会话(session)的唯一标识符(ID)
req.session.id

// 用于访问当前会话(session)的 Cookie 配置信息
req.session.cookie
winston

Winston 是一个流行的 Node.js 日志库,用于在应用程序中记录日志信息。它提供了灵活的日志记录功能,允许你配置不同的传输方式(如控制台、文件、数据库等),选择日志级别,并支持日志格式化

  1. 安装:
bash
npm install winston
morgan

Morgan 是一个 Node.js 中间件,通常用于记录 HTTP 请求的日志信息。它是一个流行的日志记录工具,广泛用于 Express.js 和其他 Node.js Web 框架中。Morgan 可以帮助你跟踪应用程序的 HTTP 请求和响应,记录访问日志,以便监视应用程序的行为和性能。

  1. 安装:
bash
npm install morgan
multer

multer 是一个用于处理 Node.js 中的文件上传的中间件。用于处理 multipart/form-data 类型的表单数据(例如上传文件等),不会处理任何非 multipart/form-data 类型的表单数据。

  • 安装
bash
npm install multer
  • 配置选项

dest:用于指定上传文件的存储目录

js
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })

diskStorage:自定义文件的存储位置和文件名生成方法

js
const multer = require('multer')
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/') // 文件保存的目录
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + '-' + file.originalname) // 生成文件名,省略则生成随机文件名
  }
})

const upload = multer({ storage: storage })

注意

  • Multer 不会附加任何文件扩展名,函数应该返回一个带有文件扩展名的文件名。
  • 每个函数都传递请求( req )和一些关于文件 ( file ) 的信息以帮助决策。
  • req.body 可能尚未完全填充,这取决于客户端将字段和文件传输到服务器的顺序。

memoryStorage:将上传的文件存储在内存中而不是磁盘上

js
const multer = require('multer')
const storage = multer.memoryStorage()
const upload = multer({ storage: storage })

注意

使用内存存储时,文件信息将包含一个名为 buffer 的字段,该字段包含整个文件。上传非常大的文件,或者非常快速地上传大量相对较小的文件,可能会导致应用程序在使用内存存储时内存不足。

fileFilter:用于自定义文件上传的过滤器函数,允许您决定是否接受或拒绝上传的文件。

js
const multer = require('multer')

const upload = multer({
  // 自定义文件过滤器,只接受 jpg 和 png 格式的图片
  fileFilter: (req, file, cb) => {
    const allowedTypes = ['image/jpeg', 'image/png']
    if (allowedTypes.includes(file.mimetype)) {
      cb(null, true) // 接受文件
    } else {
      cb(new Error('文件类型无效, 仅允许 JPG 和 PNG 文件。'), false) // 拒绝文件
    }
  },

  // 自定义文件大小限制(500KB)
  limits: {
    fileSize: 1024 * 500
  }
})

limits:于设置文件上传的限制,例如文件大小限制

js
const multer = require('multer')

const upload = multer({
  // 自定义文件大小限制(500KB)
  limits: {
    fileSize: 1024 * 500
  }
})

preservePath:用于控制是否保留文件上传时的完整路径

js
const multer = require('multer')

const upload = multer({
  // 保留文件上传时的完整路径
  preservePath: true
})
  • 文件信息属性
属性描述差异
fieldname表单字段的名称,即文件上传的字段名
originalname上传文件的原始文件名,包括文件扩展名
encoding上传文件的编码类型
mimetype上传文件的 MIME 类型
size上传文件的大小,以字节(bytes)为单位
destination文件保存的目录DiskStorage
filename生成的文件名DiskStorage
path文件的完整路径,包括目标目录和文件名DiskStorage
buffer文件内容的缓冲区MemoryStorage
  • APIs

upload.single(fieldname):接受单个文件上传,并且需要明确指定字段名称 fieldname ,通过 req.file 访问上传的单个文件信息。

upload.array(fieldname[, maxCount]):接受同一字段的多个文件上传,并且需要明确指定字段名称 fieldname 和最大文件数量 maxCount ,通过 req.files 访问上传的多个文件信息的数组。

upload.fields(fields):接受使用多个字段名称来访问上传的文件信息。每个字段对应一个文件数组,可以使用字段名称来访问特定字段的文件数组,然后遍历每个文件数组以访问上传的文件。

compression

Compression 是一个 Node.js 中间件,用于在 Web 应用程序中压缩 HTTP 响应数据。它可以有效地减小传输到客户端的数据量,从而提高 Web 应用程序的性能,降低带宽消耗。Compression 中间件通常与 Express.js 或其他 Node.js Web 框架一起使用,以压缩服务器发送的响应数据。

  1. 安装:
bash
npm install compression
http-proxy-middleware

http-proxy-middleware 是一个 Node.js 中间件,用于创建反向代理或代理服务器,可以将客户端的 HTTP 请求转发到不同的目标服务器。这个中间件通常与 Express.js 或其他 Node.js Web 框架一起使用,以实现请求代理、负载均衡、路由重定向等功能,其底层使用了 http-proxy 这个库来处理 HTTP 请求的代理。

  1. 安装:
bash
npm install http-proxy-middleware

路由

路由方式

在 Express.js 中,有基本路由(app.METHOD)、模块化路由(router.METHOD)等方式来定义路由,以处理不同的 HTTP 请求和路径。以下是基本路由方式为例:

app.all()

用于定义一个中间件函数,该函数会拦截所有 HTTP 请求方法(GET、POST、PUT、DELETE 等)的请求,并在这些请求上执行特定的操作或处理程序。

js
app.all('/secret', (req, res, next) => {
  console.log('Accessing the secret section ...')
  next() // pass control to the next handler
})

DANGER

请注意,app.all() 中间件通常应该在具体的路由处理程序之前定义,以确保它在请求到达特定路由之前执行。在中间件中,通过调用 next() 来继续请求处理流程,将控制传递给下一个中间件或路由处理程序。

app.get()

通过 app.get()方法,可以监听客户端的 GET 请求,具体的语法格式如下

javascript
app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.post()

通过 app.post()方法,可以监听客户的 POST 请求,具体语法格式如下

javascript
app.post('/', (req, res) => {
  res.send('Got a POST request')
})

app.put()

javascript
app.put('/user', (req, res) => {
  res.send('Got a PUT request at /user')
})

app.delete()

javascript
app.delete('/user', (req, res) => {
  res.send('Got a DELETE request at /user')
})

app.route()

js
app
  .route('/book')
  .get((req, res) => {
    res.send('Get a random book')
  })
  .post((req, res) => {
    res.send('Add a book')
  })
  .put((req, res) => {
    res.send('Update the book')
  })

路由路径

静态路径

  • 根目录
js
app.get('/', (req, res) => {
  res.send('root')
})
  • 二级目录及以上
js
app.get('/about', (req, res) => {
  res.send('about')
})
  • 指定文件格式
js
app.get('/random.json', (req, res) => {
  res.send('random.json')
})

模糊路径

  • ?
javascript
app.get('/ab?cd', (req, res) => {
  res.send('ab?cd')
})
// output:acd and abcd
  • +
js
app.get('/ab+cd', (req, res) => {
  res.send('ab+cd')
})
// output:abcd, abbcd, abbbcd, and so on
  • *
js
app.get('/ab*cd', (req, res) => {
  res.send('ab*cd')
})
// output:abcd, abxcd, abRANDOMcd, ab123cd, and so on
  • ()
js
app.get('/ab(cd)?e', (req, res) => {
  res.send('ab(cd)?e')
})
// output:abe and abcde
  • 正则表达式
js
app.get(/^\/user\/\d+$/, (req, res) => {
  res.send('User ID route matched')
})
// 匹配以 /user 开头并且后面是数字的路径

路由动态参数

javascript
app.get('/users/:userId/books/:bookId', (req, res) => {
  res.send(req.params)
})
/**
 * Request URL: http://localhost:3000/users/34/books/8989
 * req.params: { "userId": "34", "bookId": "8989" }
 */

app.get('/flights/:from-:to', (req, res) => {
  res.send(req.params)
})
/**
 * Request URL: http://localhost:3000/flights/LAX-SFO
 * req.params: { "from": "LAX", "to": "SFO" }
 */

app.get('/plantae/:genus.:species', (req, res) => {
  res.send(req.params)
})
/**
 * Request URL: http://localhost:3000/plantae/Prunus.persica
 * req.params: { "genus": "Prunus", "species": "persica" }
 */

app.get('/user/:userId(d+)', (req, res) => {
  res.send(req.params)
})
/**
 * Request URL: http://localhost:3000/user/42
 * req.params: {"userId": "42"}
 */

TIP

路由参数的名称必须由“字母、数字、下划线”([A-Za-z0-9_])组成。

路由处理程序

单个回调函数

js
app.get('/example/a', (req, res) => {
  res.send('Hello from A!')
})

多个回调函数

js
app.get(
  '/example/b',
  (req, res, next) => {
    console.log('the response will be sent by the next function ...')
    next()
  },
  (req, res) => {
    res.send('Hello from B!')
  }
)

函数数组

js
const cb0 = function (req, res, next) {
  console.log('CB0')
  next()
}

const cb1 = function (req, res, next) {
  console.log('CB1')
  next()
}

const cb2 = function (req, res) {
  res.send('Hello from C!')
}

app.get('/example/c', [cb0, cb1, cb2])

独立函数与函数数组

js
const cb0 = function (req, res, next) {
  console.log('CB0')
  next()
}

const cb1 = function (req, res, next) {
  console.log('CB1')
  next()
}

app.get(
  '/example/d',
  [cb0, cb1],
  (req, res, next) => {
    console.log('the response will be sent by the next function ...')
    next()
  },
  (req, res) => {
    res.send('Hello from D!')
  }
)

TypeScript

安装

bash
npm install express mongoose @types/node 
npm install -D typescript ts-node nodemon @types/express @types/mongoose tsconfig-paths

TIP

  1. :
  • @types/express:是 Express 框架的 TypeScript 类型定义。它允许你在 TypeScript 项目中编写 Express 应用程序,并且会提供有关 Express 相关 API 的类型提示和类型检查。
  • @types/node:是 Node.js 的 TypeScript 类型定义。它提供了 Node.js 核心模块和 API 的类型声明,这使得你在 TypeScript 中可以获得完整的 Node.js 类型支持。
  • @types/mongoose: 是 Mongoose 的 TypeScript 类型定义,它提供了 Mongoose 库的类型声明,这样在使用 TypeScript 时就可以获得类型提示和类型检查。
  • ts-node:是一个用于在 Node.js 环境中运行 TypeScript 的工具。它允许你直接运行 TypeScript 文件而不需要手动编译成 JavaScript 文件。它会在运行时动态编译 TypeScript 文件,并将其转换为可执行的 JavaScript 代码。
  • tsconfig-paths: 是一个 TypeScript 插件,它允许你在 TypeScript 项目中使用路径映射,简化了模块导入时的路径设置。
  1. CLI:
  • npx tsc:这是用于将 TypeScript 代码编译成 JavaScript 代码的命令。它会使用项目中的 TypeScript 版本,因此您无需全局安装 TypeScript。
bash
npx tsc && node dist/app.js
  • npx ts-node:这是用于在本地运行 TypeScript 文件的命令。它会直接运行 TypeScript 文件而无需进行手动的编译步骤。您也不需要全局安装 TypeScript。
bash
nodemon --watch src/**/*.ts --exec npx ts-node src/app.ts

tsconfig.json

json
{
  "compilerOptions": {
    "target": "ES6",
    "module": "CommonJS",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

注意

实际上,TypeScript 本身并不直接支持路径别名,即使配置了 "path":{"@/*": ["src/*"]} 也依然无效。必须使用某种构建工具来解析 TypeScript 路径别名,并将其转换为有效的模块导入。如果不想使用构建工具,可以使用 tsconfig-paths 第三方库来解析别名:

  • 安装
bash
npm install -D tsconfig-paths
  • 使用
bash
# With ts-node
ts-node -r tsconfig-paths/register src/app.ts

app.ts

ts
// src/app.ts
import express, { Express, Request, Response } from 'express'

const app: Express = express()
const port: number = 3000

app.get('/', (req: Request, res: Response) => {
  res.send('Hello, TypeScript and Express!')
})

app.listen(port, () => {
  console.log(`Server is running on port ${port}`)
})

读书、摄影、画画、弹琴、编程