使用 Typescript 自动验证请求的最佳方法是啥?

Posted

技术标签:

【中文标题】使用 Typescript 自动验证请求的最佳方法是啥?【英文标题】:What is the best way to automatically validate requests using Typescript?使用 Typescript 自动验证请求的最佳方法是什么? 【发布时间】:2020-03-10 16:01:34 【问题描述】:

我想使用 Typescript 接口轻松验证后端请求。有没有办法让它发生?我想做的事的例子:

import  Request, Response  from 'express'

interface AuthenticateParams 
    email: string
    password: string


type AuthenticationRequest = Request & body: AuthenticateParams

const authenticate = async (req: AuthenticationRequest, res: Response) => 
    if (typeof req.body !== AuthenticateParams) res.status(400).send('Invalid body')

    // DO STUFF
    let stuff

    res.status(200).send(stuff)


export default authenticate

【问题讨论】:

接口在运行时不存在。 Typescript 不提供此功能。 我能想到的最接近的事情是利用 JSON Schema,但这不在 TypeScript 范围内。 Are TypeScript "Interfaces" also input validators? (In other Modules or User Requests)的可能重复 【参考方案1】:

typescript 中的接口纯粹是编译时构造。您正在尝试做的是动态检查正文的类型。

您可以使用架构验证器实现您想要的。

【讨论】:

拥有集成 json 模式验证和打字稿定义的东西会非常有用。这样您就不必两次定义“类型”:防止更新时出现错误。【参考方案2】:

没有办法做到这一点。接口仅在编译和检查代码时存在,但 TypeScript 没有运行时。

一旦你的 TypeScript 代码被编译,它就会变成普通的 javascript。而且 JavaScript 没有接口的概念,所以无法检查。

您基本上需要编写自己的函数来验证接口,例如,检查对象上是否存在特定字段,或者它是特定类的实例。

【讨论】:

【参考方案3】:

是的,我知道接口在运行时不存在。我创建了这个解决方案,它验证我的输入并在我需要时给我输入错误。我认为我的标题有点误导,所以我还将标题更改为“使用 Typescript 自动验证请求的最佳方法是什么”。

这个想法是强迫我为我希望用来验证后端端点的每个接口编写一个简单的模式。此外,为了尽量减少人为错误的可能性,我的 linter 类型检查 validatorObject 很重要。

validateParams.ts:

const validateParams: (targetObject: any, validatorObject: any) => boolean = (targetObject, validatorObject) => 
    if (typeof targetObject !== typeof validatorObject) return false
    if (typeof targetObject === 'object') 
        let validObject = true
        if (Array.isArray(targetObject)) 
            for (let subObject of targetObject) 
                validObject = validObject && validateParams(subObject, validatorObject[0])
            
         else 
            for (let key of Object.keys(validatorObject)) 
                if (typeof targetObject[key] === 'object') validObject = validObject && validateParams(targetObject[key], validatorObject[key])
                if (typeof targetObject[key] !== typeof validatorObject[key]) validObject = false
            
        
        return validObject
    
    return true


export default validateParams

认证.ts

import  Request, Response  from 'express'
import validateParams from "./validateParams"

interface AuthenticateParams 
    email: string
    password: string


const validatorObject: AuthenticateParams = 
    email: 'string',
    password: 'string'


type AuthenticationRequest = Request & body: AuthenticateParams

const authenticate = async (req: AuthenticationRequest, res: Response) => 
    if (!validateParams(req.body, validatorObject)) res.status(400).send('Invalid body')

    // DO STUFF
    let stuff

    res.status(200).send(stuff)


export default authenticate

updateUser.ts

import  Request, Response  from 'express'
import validateParams from "./validateParams"

interface UpdateUserParams 
    name: string
    products: 
        name: string
        price: number
    []


const validatorObject: UpdateUserParams = 
    name: 'string',
    products: [name: 'string', number: 0]


type AuthenticationRequest = Request & body: UpdateUserParams

const updateUser = async (req: UpdateUserParams, res: Response) => 
    if (!validateParams(req.body, validatorObject)) res.status(400).send('Invalid body')

    // DO STUFF
    let stuff

    res.status(200).send(stuff)


export default updateUser

这不是最漂亮的解决方案,但我喜欢它自动验证我的请求并且如果我忘记更新我的验证器对象也会给我错误。但是,是的,在运行时获得 Typescript 接口会很棒。

【讨论】:

【参考方案4】:

这是我检查属性键的最佳帮手,同时与打字稿集成。有一个变化是可以递归扩展。

function hasKeys<T, K extends string | number | symbol>(
    obj: T, 
    keys: K | K[]
): obj is T &  [P in K]: unknown  
    return Array.isArray(keys) ? keys.every(k => k in obj) : keys in obj;

declare const obj: unknown;

if(typeof obj === "object" && obj !== null) 
    if("prop" in obj) 
        // Property 'prop' does not exist on type 'object'.ts(2339)
        let x = obj.prop;
    

    if(hasKeys(obj, "prop")) 
        let x = obj.prop;
    

    if(hasKeys(obj, ["prop1", "prop2"])) 
        let x = obj.prop1;
        let y = obj.prop2;
    

【讨论】:

【参考方案5】:

如果您还维护 openapi 文档,则可以使用 express-openapi-validator 验证请求。它也适用于 fastify 和 Koa

【讨论】:

以上是关于使用 Typescript 自动验证请求的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

验证 Laravel 请求的最佳方法

从 node.js 和 Typescript 中的动态文件名加载 JSON 的最佳方法是啥?

NPM 模块 + TypeScript 最佳实践

在rest api app中生成laravel请求验证的最佳实践

电子书丨《Learning TypeScript中文版》

typescript 插入前在数组中查找重复的最佳方法