如何改进 Node.js 中的数据验证?(寻找最佳实践)

sna*_*nda 1 javascript node.js

所以这是我当前的代码:

/**
 * User Registration
 * Sends a response object with success true if user was registered.
 * Sends a response object with success false if user couldn't be registered.
 */
router.post('/', function(req, res, next) {
    var userInfo = req.body;

    userInfo.email = sanitize(userInfo.email);
    userInfo.password = sanitize(userInfo.password);

    if(!validEmail(userInfo.email) || !validPassword(userInfo.password)) { //If the request info is invalid
        sendJsonResponse(res, false, error, 400);
    }
    else {
        //Check if e-mail already exists
        User.find({email: userInfo.email}, function(err, docs) {
            if(err) {
                console.log(err);
                sendJsonResponse(res, false, "There was an error processing your request.", 400);
            }
            else if(docs.length > 0) { //If e-mail was found in database, generate error
                sendJsonResponse(res, false, "This e-mail already exists, please login or use another e-mail.", 409);
            }
            else {
                //Create user based on data submitted.
                var user = new User({email: sanitize(userInfo.email), password: bcrypt.hashSync(sanitize(userInfo.password), 10), permission_level: 'REGULAR'});

                //Save it in the database.
                user.save(function (err) {
                    if (err) {
                        sendJsonResponse(res, false, "There was an error processing your request.", 400);
                    }
                    else { //If everything is fine

                        //Create token to login the registered user
                        var token = jwt.sign(user, config.secret, {
                            expiresIn: '24h' // expires in 24 hours
                        });

                        sendJsonResponse(res, true, "You have been registered!", 200, token);
                    }
                });
            }
        });
    }
    error = "";
});
Run Code Online (Sandbox Code Playgroud)

此代码用于用户注册。我正在使用我编写的函数 (validEmail/validPassord) 来验证发送到我的 REST API 的电子邮件和密码。

还有 sendJsonResponse,它发送带有特定错误和错误代码的响应。

我觉得我做的还不错,但我仍然觉得这里还有改进的空间。我特别不喜欢所有的 if/else。我可以在这里做些什么来提高最佳实践/代码质量?

boe*_*m_s 8

如果我们只查看您的代码,那就没问题了(逻辑、错误处理、格式化响应的函数......)。但是看看你的代码,我假设你正在使用Express微框架来创建你的后端服务(API?),所以你的 API 设计和你使用 Express 的方式有一些需要改进的地方......


将数据库与路由分开

对您的数据库的一个请求可能会导致不同的错误(重复键、某些约束、错误值......)并提供不同的看法。所以最好创建模块来处理与数据库的交互,这样错误处理会更清晰,代码更可重用。

例子 :

创建一个模块userModel将包含多种方法来处理数据库中的用户。
考虑getBy允许您通过“键” ( getBy({name: 'Doe', age: 25}))过滤用户的方法。
这个方法可以被不同的路由使用并返回不同类型的错误,所以最好将它写在模块内部。
它将减少代码重复,使代码更具可读性并帮助您改进错误处理。


快速路由器

我看到您正在使用 Express 路由器 ( router.post('/', ... ))。
由于您正在设计 REST API,因此您的方法 GET、POST、PUT 和 DELETE 将共享相同的路由名称。
Express 路由器允许您使用以下语法执行此操作:

router.route('/routeName')
      .get(...)
      .post(...)
      .put(...)
      .delete(...);
Run Code Online (Sandbox Code Playgroud)

这不是什么大事,但它使代码更好,并迫使您尊重 REST 架构约束。


快递中间件

我看到您正在使用函数对请求正文进行验证并格式化您的响应。这通常由中间件管理。
中间件是 Express(带路由器)最重要的特性之一。它们是具有以下“签名”的函数:function(req, res, next){}可以链接。
每个中间件可以:

  • 修改收到的请求(通过修改req参数)
  • 发送与由提供的方法的响应res参数(例如:res.status(200).json({created: true})
  • 像这样调用下一个中间件: next()

因此,中间件允许您使用一个简单的函数列表来构建程序的逻辑,这些函数将一个接一个地被调用。

例子 :

[check req.body](1) --> [write user in the DB](2) --> [format response](3)
Run Code Online (Sandbox Code Playgroud)

中间件真是太棒了,会让你写出更好、更简洁、设计良好的 API :)


异步/等待

我们都讨厌回调地狱,这是事实,这就是为什么我们通常使用诸如Promise. 但即使是 Promises 有时也会很痛苦,所以我们async/await让事情变得更简单:
它允许您像编写同步代码一样编写异步代码!


一些代码来说明以上几点

|___models/
|   |___users.js
|
|___controllers/
|   |___users.js
|
|___routes/
|   |___register.js
|
|___middlewares/
    |___validation.js
Run Code Online (Sandbox Code Playgroud)


// models/users.js
import db from 'dbModule';

const save = async (userObj) => {
    const testUserMail = await db.users.find({email: userObj.email});
    if (testUserMail.length > 0)
      return {success: false, err: "The email already exists"};
    await db.users.save(userObj);
    const savedUser = await db.users.findById(userObj.id);
    return {success: true, user: savedUser};
}

exports default {save};
Run Code Online (Sandbox Code Playgroud)


// controllers/users.js
import userModel  from '../models/users';

const register = (req, res, next) => {
  console.log(req.example); // "hey I'm here"
  const dbReq = await userModel.save(req.body);
  if (dbReq.err && !dbReq.success)
    return res.status(400).json({success: false, data: dbReq, message: "failed !"})
  const registeredUser = dbReq.user;
  res.json({success: true, data: {user: registeredUser}, message: "User is registered !");
}

export default {register};
Run Code Online (Sandbox Code Playgroud)


// middlewares/validation.js
const checkUser = (req, res, next) => {
  req.example = "hey I'm here";
  if (isOk(req.body.email) && isOk(req.body.password))
    return next();
  else
    return res.status(400).json({success: false, data: null, message: "failed: bad request body"}))
}

export default {checkUser};
Run Code Online (Sandbox Code Playgroud)


// routes/register.js
import express    from 'express';
import validation from '../middlewares/validation';
import userCtrl   from '../controllers';

const router = express.Router();
router.route('/')
      .post(validation.checkUser, userCtrl.register)
Run Code Online (Sandbox Code Playgroud)


快速和最后(我发誓)的解释:

  • 首先我们检查我们的身体(我们可以根据需要修改它)。如果出现问题,我们发送 400 Bad request Response,否则,我们调用 next 并转到下一个中​​间件 ( userCtrl.register)。
  • 我们检索req与先前的中间件进行修改(这里,我们添加的example关键是我们的req对象)。
  • 我们通过调用 (= awaiting for) 一个async函数将用户保存在数据库中--> 避免回调地狱
  • 就是这样,我们保存用户并发送 200 响应。

如果你喜欢上面的 API 架构/设计,你可以看看


希望它有所帮助,阅读时间不会太长,
最好的问候,