使用 Express.js 和 mysql 处理 PATCH 请求

Posted

技术标签:

【中文标题】使用 Express.js 和 mysql 处理 PATCH 请求【英文标题】:Handling PATCH request with Express.js and mysql 【发布时间】:2021-10-29 10:01:18 【问题描述】:

假设我有以下路线:

POST /users 其中所有字段都是必需的,这会将用户添加到我的数据库中,假设这个用户获得了 id1


    "firstName": "FirstName",
    "lastName": "LastName",
    "email": "email@example.com",
    "address": "address"

我还有路由 GET /users,它显示了数据库中的所有用户。

[
    
        "id": 1,
        "firstName": "FirstName",
        "lastName": "LastName",
        "email": "email@example.com",
        "address": "address"
    
]

现在,我正在尝试更新特定用户PATCH /users 的路线。

我想让所有字段都是可选的,并且只发送我通过 PATCH 路由更新的字段,id 将从 JWT 令牌中解码。

请务必注意,数据显示在前端的表单中,我想更新表单,但只发送已从表单更新的值,而不是整个表单。实际的表单有 30 多个字段。

我知道如果我使用PUT 请求,我可以发送表单中的所有值并执行以下操作:

editUser = async ( firstName, lastName, email, address ) => 
    let sql = `UPDATE users
    SET firstName = ?, lastName = ?, email=?, address=? WHERE id = ?`;

    const result = await query(sql, [firstName, lastName, email, address, userId ]);

    return result;

现在,假设我只更新表单中的电子邮件并发送它。例如: PATCH /users


    "email":"email@gmail.com"

使用以下方法也很容易:

editUser = async ( email ) => 
    let sql = `UPDATE users
    SET email = ? WHERE id = ?`;

    const result = await query(sql, [email, userId]);

    return result;

以上方法是PATCH 电子邮件的好方法。

所以现在当我执行GET /users 时,我得到以下信息:

[
    
        "id": 1,
        "firstName": "FirstName",
        "lastName": "LastName",
        "email": "email@gmail.com",
        "address": "address"
    
]

但是我如何处理我不知道哪些密钥被发送到后端的不同情况?例如,一组可以如下: PATCH /users


    "lastName": "updatedLastName"
    "email":"email@gmail.com"

以上可以是实际对象中所有不同键的任意组合。什么是根据键生成自定义查询并仅使用需要更新的列更新表中的行的好方法?

【问题讨论】:

【参考方案1】:

我没有比这些更好的主意了:

使用 forEach:

const buildPatchQuery = (table, id, data) => 
    if (Object.keys(data).length === 0) return null; // Or return what you want
    let sql = `UPDATE $table SET`;
    Object.entries(data).forEach(([key, value]) => 
        const valueToSet = typeof data[key] === 'string' ? `'$value'` : value;
        sql += ` $key=$valueToSet,`;
    );
    sql = sql.slice(0, -1); // Remove last ","
    sql += ` WHERE id=$id;`;
    return sql;

有地图:

const buildPatchQuery = (table, id, data) => 
    if (Object.keys(data).length === 0) return null; // Or return what you want
    let query = `UPDATE $table SET `;
    query += Object.keys(data).map((key) => 
        const valueToSet = typeof data[key] === 'string' ? `'$data[key]'` : data[key];
        return `$key=$valueToSet`;
    ).join(', ');
    return query + ` WHERE id=$id;`;

减少:

const buildPatchQuery = (table, id, data) => 
    if (Object.keys(data).length === 0) return null; // Or return what you want
    let query = `UPDATE $table SET`;
    query += Object.entries(data).reduce((acc, [key, value], index, array) => 
        const valueToSet = typeof data[key] === 'string' ? `'$value'` : value;
        const optionalComma = index < array.length-1 ? ',' : '';
        return `$acc $key=$valueToSet$optionalComma`;
    , '');
    return query + ` WHERE id=$id;`;

通过使用您的 SQL 库:

const buildPatchQuery = (table, id, data) => 
    if (Object.keys(data).length === 0) return null; // Or return what you want
    let sql = `UPDATE $table SET`;
    const newValues = [];
    Object.entries(data).forEach(([key, value]) => 
        sql += ` $key=?,`;
        newValues.push(value);
    );
    sql = sql.slice(0, -1); // Remove last ","
    sql += ' WHERE id=?;';
    newValues.push(id);
    return query(sql, newValues);

测试:

console.log(buildPatchQuery('users', 1, name: 'toto', email: 'truc', age: 18));
console.log(buildPatchQuery('users', 1, name: 'toto'));
console.log(buildPatchQuery('users', 1, ));

(未通过 SQL 库测试)

但是,请务必检查 data 对象并通过检查身份验证和权限来保护您的路由,以避免 SQL 注入或任何黑客攻击!

【讨论】:

这是一个很好的解决方案。我设法制定了另一个解决方案。想听听你的意见。发布在答案中 很高兴!如果是出于测试原因,您可以将代码放在codeshare.io。 您的库的另一个解决方案是“设置”查询中的所有字段(使用“?”),但如果没有提供新值,则通过设置当前对象的值。 我想这就是我所做的。我在这里发布了我的答案。想要一些关于它的输入。 是的,我看过,我同意!【参考方案2】:

自行处理字段,因为最终用户/黑客可以根据密钥覆盖或操纵。根据 key/switch 语句添加条件。进行查找以获取整个对象。因为否则通过 SQL 注入/关键字段可以通过有效负载进行操作。动态占位符可以处理 SQL 注入攻击,但最终用户可以通过示例paidmembership: true 等他们将覆盖。或者在一个对象中有一组键值,只允许动态更新这些字段,或者使用 ORM 形式的 sequelize How to update a record using sequelize for node?

app.patch('/api/products/:id', (req, res) => 
  const product = products.find(product => product.id === parseInt(req.params.id));
  if (!product) return res.status(404).json( message: 'Not Found' );

  if(req.body.name) product.name = req.body.name;
  if(req.body.price)  product.price = req.body.price;

  res.json(product);
);

参考:https://www.tabnine.com/code/javascript/functions/express/Express/patch

【讨论】:

我不能在执行任何数据库操作之前使用验证器中间件来验证输入吗?我目前正在使用 JOI 验证器 是的,您可以这样做,但字段列表会不断变化。新的编写方式是使用 ORM 概念进行数据操作 Ref : ***.com/questions/8158244/… 嗯,验证器(JOI)不允许未知字段通过。所以如果你发送一个随机密钥,它会给你一个错误。但是 SQL 查询中的预处理语句是否有助于防止 SQL 注入? 是的,它确实根据准备好的语句进行处理。 我明白了,谢谢!我找到了一个解决方案。希望得到您的意见。【参考方案3】:

我似乎找到了解决办法。这基本上利用了 mysqlIFNULL 内置函数。我在这里使用的技巧是像往常一样简单地传递我的所有参数。但是,我快速检查一下这些值是否为undefined,如果是,我将它们转为null。这使我现在可以在查询中使用IFNULL 函数。它的作用是检查我是否尝试用空值更新表中的列。如果是这样,那么它不会更新它,而是保留列的当前值。

在这种特殊情况下,我的验证器(JOI 验证器)将所有字段标记为可选,但仍要求它们采用特定格式。此外,SQL 查询中的预处理语句有助于防止 SQL 注入。

editUser = async (
    firstName,
    lastName,
    email,
    address
  ) => 
   
    firstName = firstName ?? null;
    lastName = lastName ?? null;
    email = email ?? null;
    address = address ?? null;

    let sql = `UPDATE $this.tableName
        SET firstName = IFNULL(?, firstName), lastName = IFNULL(?, lastName), email=IFNULL(?, email), address = IFNULL(?, address) WHERE userId = ?`;

    let result = await query(sql, [
      firstName,
      lastName,
      email,
      address
    ]);

    return result;
  ;

【讨论】:

不错的解决方案,我没想到!

以上是关于使用 Express.js 和 mysql 处理 PATCH 请求的主要内容,如果未能解决你的问题,请参考以下文章

使用 Node.js 和 Express.js 在 MySQL 中删除给定值数组的多行

CRUD op Express.js Node.js Mysql 无法使用 MVC 插入

如何解析 node.js、express.js、mysql2 中“rows”对象的数据

如何使用 express js 处理 fetch API 请求

node.js + express.js:使用 mongodb/mongoose 处理会话

Express.js 和 Bluebird - 处理承诺链