如何Request在 TypeScript 中扩展 Express 对象

叶书
2023-12-01

Express 使用该Request对象向 Node.js 服务器的控制器提供有关 HTTP 请求的数据。因此,应用层可用的请求中的所有数据都依赖于该对象。

您可能需要详细说明您在 HTTP 请求中收到的数据,并向控制器提供自定义信息。在 JavaScript 中,您可以简单地在对象中定义新属性Request并在需要时使用它们。Request在 TypeScript 中,如果要定义自定义属性,则需要扩展类型。

现在让我们了解RequestExpress 中的内容,并深入探讨Request在 TypeScript 中扩展类型有用的原因。然后,让我们看看如何Request通过使用 TypeScript 构建的 Express 演示应用程序来利用扩展对象。

TL;DR:让我们学习如何Request在 TypeScript 中扩展类型以使其实例存储您可以在控制器级别使用的自定义数据。

  • Express 中的 Request 对象是什么?

  • 为什么要扩展请求?

  • 在 TypeScript 中扩展 Express Request 类型

    • 先决条件

    • 将自定义属性添加到请求类型

  • 使用扩展的 Request 对象

  • 测试扩展请求

RequestExpress中的对象是什么?

该Request对象表示客户端对 Express 服务器执行的 HTTP 请求。换句话说,Express 服务器可以通过Request实例读取从客户端接收到的数据。因此,Request有几个属性可以访问 HTTP 请求中包含的所有信息,但最重要的是:

  • query

    :此对象包含请求 URL 中存在的每个查询字符串参数的属性:

    app.get("/users", (req: Request, res: Response) => {
      // on GET "/users?id=4" this would print "4"
      console.log(req.query.id)
    });
  • params

    :此对象包含根据

    Express 路由约定

    在 API URL 中定义的参数:

    app.get("/users/:id", (req: Request, res: Response) => {
      // on GET "/users/1" this would print "1"
      console.log(req.params.id)
    });
  • body

    :此对象包含在 HTTP 请求正文中提交的键值对数据:

    app.get("/users", (req: Request<never, never, { name: string; surname: string }, never>, res: Response) => {
      const { name, surname } = req.body
    ​
      // ...
    })
  • headers:此对象包含请求发送的每个 HTTP 标头的属性。

  • cookies:使用cookie-parserExpress 中间件时,该对象包含请求发送的每个 cookie 的属性

为什么要延长Request?

Express 控制器可以使用该对象访问包含在 HTTP 请求中的所有数据Request。这并不意味着Request对象是与控制器交互的唯一方式。相反,Express 也支持中间件。Express 中间件是可用于添加应用程序级或路由器级功能的功能。

中间件功能与路由器级别的端点相关联,如下所示:

const authenticationMiddleware = require("../middlewares/authenticationMiddleware")
const FooController = require("../controllers/foo")
​
app.get(
  "/helloWorld",
  FooController.helloWorld, // (req, res) => { res.send("Hello, World!") }
  // registering the authenticationMiddleware to the "/helloWorld" endpoint
  authenticationMiddleware,
)

请注意,中间件函数在调用包含 API 业务逻辑的控制器函数之前执行。在此处了解有关它们如何工作以及 Express 中间件可以提供的更多信息。

这里需要注意的重要一点是中间件可以修改Request对象,添加自定义信息以使其在控制器级别可用。iPhone14用户抱怨蓝牙问题、性能问题及潜在问题的修复方法例如,假设您想让您的 API 仅对具有有效身份验证令牌的用户可用。为此,您可以定义一个简单的身份验证中间件,如下所示:

import { Request, Response, NextFunction } from "express"
​
export function handleTokenBasedAuthentication(req: Request, res: Response, next: NextFunction) {
  const authenticationToken = req.headers["authorization"]
​
  if (authenticationToken !== undefined) {
    const isTokenValid = // verifying if authenticationToken is valid with a query or an API call...
​
    if (isTokenValid) {
      // moving to the next middleware
      return next()
    }
  }
​
  // if the authorization token is invalid or missing returning a 401 error
  res.status(401).send("Unauthorized")
}

当 HTTP 请求标头中收到的身份验证令牌Authorization有效时,此值与您的服务的用户唯一关联。换句话说,身份验证令牌允许您识别发出请求的用户,这一点非常重要。修复 Alt+Tab 快捷键在 Windows 11 上不起作用的 10 个修复方法例如,控制器级别的业务逻辑可能会根据用户的角色而改变。

如果多个控制器级函数需要知道执行 API 调用的用户是谁,您必须Authorization在多个位置复制从标头中检索用户所需的逻辑。相反,您应该Request使用属性扩展对象user并在身份验证中间件中为其赋予值。

请注意,TypeScript 中的 ExpressRequest类型不涉及user属性。这意味着您不能简单地扩展Request对象如下:

import { Request, Response, NextFunction } from "express"

export function handleTokenBasedAuthentication(req: Request, res: Response, next: NextFunction) {
  const authenticationToken = req.headers["authorization"]

  if (authenticationToken !== undefined) {
    const isTokenValid = // verifying if authenticationToken is valid with a query or an API call...

    if (isTokenValid) {
      const user = // retrieving the user info based on authenticationToken  

      req["user"] = user // ERROR: Property 'user' does not exist on type 'Request'

      // moving to the next middleware
      return next()
    }
  }

  // if the authorization token is invalid or missing returning a 401 error
  res.status(401).send("Unauthorized")
}

这将导致以下错误:

Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.

同样,您可以使用扩展Request来避免在控制器级别进行类型转换,并使您的代码库更清晰和健壮。WordPress可以承载多少流量?WordPress优化方法有哪些?假设您的后端应用程序仅支持三种语言:英语、西班牙语和意大利语。换句话说,您已经知道Content-LanguageHTTP 标头只能接受en、es和it。当标题被省略或包含无效值时,您希望使用英语作为默认语言。

请记住,req.headers["Content-Language"]返回一个string | string[] | undefined类型。这意味着如果您想将Content-Language标头值用作 a string,则必须将其转换如下:

const language = (req.headers["content-language"] || "en") as string | undefined

用这种逻辑填充你的代码不是一个优雅的解决方案。相反,您应该使用中间件进行Request如下扩展:

import { Request, Response, NextFunction } from "express"

const SUPPORTED_LANGUAGES = ["en", "es", "it"]
// this syntax is equals to "en" | "es" | "it"
export type Language = typeof SUPPORTED_LANGUAGES[number] 

export function handleCustomLanguageHeader(req: Request, res: Response, next: NextFunction) {
  const languageHeader = req.headers["content-language"]

  // default language: "en"
  let language: Language = SUPPORTED_LANGUAGES[0]

  if (typeof languageHeader === "string" && SUPPORTED_LANGUAGES.includes(languageHeader)) {
    language = languageHeader
  }

  // extending the Request object with a language property of type Language...

  return next()
}

这只是两个示例,但在其他几个场景中,Request使用自定义数据进行扩展可以节省您的时间,并使您的代码库更加优雅和可维护。

Request在 TypeScript 中扩展 Express类型

向 ExpressRequest类型定义添加额外的字段只需要几行代码。让我们看看如何通过基于前面介绍的中间件的演示应用程序来实现它并利用扩展对象。

克隆支持本文的 GitHub 存储库并使用以下命令在本地启动示例后端应用程序:

git clone https://github.com/Tonel/extend-express-request-ts-demo
cd extend-express-request-ts-demo
npm i
npm start

继续阅读本文以了解如何利用RequestTypeScript 中的扩展 Express 类型。

先决条件

您需要这些先决条件来复制文章的目标:

  • express>= 4.x

  • @types/express>= 4.x

  • typescript>= 4.x

如果您在 Typescript 中没有 Express 项目,您可以在此处了解如何从头开始设置Express 和 TypeScript 项目。

向Request类型添加自定义属性

扩展类型所需要做的Request就是定义一个index.d.ts文件,如下所示:

// src/types/express/index.d.ts

import { Language, User } from "../custom";

// to make the file a module and avoid the TypeScript error
export {}

declare global {
  namespace Express {
    export interface Request {
      language?: Language;
      user?: User;
    }
  }
}

将此文件放在src/types/express文件夹中。Typescript 使用.d.ts 声明文件来加载有关用 JavaScript 编写的库的类型信息。在这里,TypeScript 将使用index.d.ts global 模块Request来全局扩展 Express 类型。

和自定义类型在文件中定义Language如下:User``src/types/custom.ts

// src/types/custom.ts

export const SUPPORTED_LANGUAGES = ["en", "es", "it"]
// this syntax is equals to "en" | "es" | "it"
export type Language = typeof SUPPORTED_LANGUAGES[number]

export type User = {
    id: number,
    name: string,
    surname: string,
    authenticationToken : string | null
}

这些类型将分别用于中间件功能handleCustomLanguageHeader()和handleTokenBasedAuthentication()中间件功能。让我们看看如何。

使用扩展Request对象

现在,让我们学习如何使用扩展Request对象。首先,让我们完成前面介绍的中间件功能。这authentication.middleware.ts看起来像:

// src/middlewares/authentication.middleware.ts 

import { Request, Response, NextFunction } from "express"
import { User } from "../types/custom"

// in-memory database
const users: User[] = [
    {
        id: 1,
        name: "Maria",
        surname: "Williams",
        authenticationToken: "$2b$08$syAMV/CyYt.ioZ3w5eT/G.omLoUdUWwTWu5WF4/cwnD.YBYVjLw2O",
    },
    {
        id: 2,
        name: "James",
        surname: "Smith",
        authenticationToken: null,
    },
    {
        id: 3,
        name: "Patricia",
        surname: "Johnson",
        authenticationToken: "$2b$89$taWEB/dykt.ipQ7w4aTPGdo/aLsURUWqTWi9SX5/cwnD.YBYOjLe90",
    },
]

export function handleTokenBasedAuthentication(req: Request, res: Response, next: NextFunction) {
    const authenticationToken = req.headers["authorization"]

    if (authenticationToken !== undefined) {
        // using the in-memory sample database to verify if authenticationToken is valid
        const isTokenValid = !!users.find((u) => u.authenticationToken === authenticationToken)

        if (isTokenValid) {
            // retrieving the user associated with the authenticationToken value
            const user = users.find((u) => u.authenticationToken === authenticationToken)

            req.user = user

            // moving to the next middleware
            return next()
        }
    }

    // if the authorization token is invalid or missing returning a 401 error
    res.status(401).send("Unauthorized")
}

为简单起见,身份验证令牌通过内存数据库进行验证。在实际场景中,用一些查询或 API 调用替换这个简单的逻辑。请注意您现在如何将与令牌关联的用户分配给Request自定义user属性。

现在,让我们看看最终的language.middleware.ts中间件文件:

// src/middlewares/headers.middleware.ts

import { Request, Response, NextFunction } from "express"
import { Language, SUPPORTED_LANGUAGES } from "../types/custom"

export function handleLanguageHeader(req: Request, res: Response, next: NextFunction) {
    const languageHeader = req.headers["content-language"]

    // default language: "en"
    let language: Language = SUPPORTED_LANGUAGES[0]

    if (typeof languageHeader === "string" && SUPPORTED_LANGUAGES.includes(languageHeader)) {
        language = languageHeader
    }

    req.language = language

    return next()
}

自Request定义language属性用于存储Language信息。


超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →


接下来,让我们看看Request自定义属性在两个不同的 API 中的作用。这是HelloWorldController对象的样子:

// src/controllers/helloWorld.ts 

import { NextFunction, Request, Response } from "express"

export const HelloWorldController = {
    default: async (req: Request<never, never, never, never>, res: Response, next: NextFunction) => {
        let message

        switch (req.language) {
            default:
            case "en": {
                message = "Hello, World!"
                break
            }
            case "es": {
                message = "¡Hola, mundo!"
                break
            }
            case "it": {
                message = "Ciao, mondo!"
                break
            }
        }

        res.json(message)
    },
    hello: async (req: Request<never, never, never, never>, res: Response, next: NextFunction) => {
        res.json(`Hey, ${req.user?.name}`)
    },
}

如您所见,HelloWorldController定义了两个 API。第一个使用Request自定义language属性,而第二个使用Request自定义user属性。

最后,您必须在路由器文件中注册中间件函数及其端点,如下所示:

// src/routes/index.ts

import { Router } from "express"
import { HelloWorldController } from "../controllers/helloWorld"
import { handleLanguageHeader } from "../middleware/customHeaders.middleware"
import { handleTokenBasedAuthentication } from "../middleware/authentication.middleware"

export const router = Router()

router.get("/", handleLanguageHeader, HelloWorldController.default)
router.get("/hello", handleTokenBasedAuthentication, HelloWorldController.hello)

测试扩展Request

让我们用curl. 首先,使用npm run start.

现在,让我们看看/端点的行为。

如您所见,API 返回的消息的语言Content-Language按预期由标头返回。

现在,让我们测试/hello端点。

Authorization同样,由于标头值,响应取决于加载的用户。

瞧!您刚刚学习了如何扩展 ExpressRequest类型以及如何使用它向控制器提供自定义信息。

结论

在本文中,您了解了 ExpressRequest对象是什么、为什么它如此重要以及何时可能需要扩展它。该Request对象存储有关 HTTP 请求的所有信息。能够扩展它代表了一种将自定义数据直接传递给控制器的有效方法。如图所示,这可以避免代码重复

 类似资料: