我应该将验证放入节点的模型中吗?

时间:2019-06-09 18:09:35

标签: node.js

在我的应用程序中,我的用户模型如下。当我确认确认密码时,猫鼬模型中存在一些业务逻辑部分。我该如何更好地表达呢?我应该将验证部分与模型分开吗?还是应该将验证保留在猫鼬模型中?

import Joi from 'joi';
import { Schema, model } from 'mongoose';

const userSchema = new Schema(
  {
    firstName: {
      type: String,
      required: true,
      minlength: 2,
      maxlength: 30,
    },
    lastName: {
      type: String,
      required: true,
      minlength: 2,
      maxlength: 30,
    },
    email: {
      type: String,
      required: true,
      minlength: 5,
      maxlength: 100,
      unique: true,
      lowercase: true
    },
    password: {
      type: String,
      required: true,
      minlength: 8,
      maxlength: 1024,
    },
    avatar: {
      type: String
    },
  }, 
);

export const User = model('User', userSchema);

/** Validate user */
export function validateUser(user) {
  const schema = {
    firstName: Joi.string().min(2).max(30).required(),
    lastName: Joi.string().min(2).max(30).required(),
    email: Joi.string().min(5).max(100).required().email(),
    password: Joi.string().min(8).max(1024).required(),
    confirmPassword: Joi.any().valid(Joi.ref('password')).required().options({ language: { any: { allowOnly: 'Passwords do not match' } } }),
    avatar: Joi.string(),
  }; 

  return Joi.validate(user, schema, { abortEarly: false });
}

/** Validate login */
export function validateLogin(login) {
  const schema = {
    email: Joi.string().min(5).max(255).required().email(),
    password: Joi.string().min(5).max(255).required()
  };

  return Joi.validate(login, schema);
}

1 个答案:

答案 0 :(得分:3)

也许您应该尝试询问CodeReview,因为这更多的是设计而非问题。

无论如何,这是我的观点(因为它是设计的,所以主要是观点问题)。

1。使用Mongoose仅声明与DB相关的验证

我不会使用猫鼬来验证与对象相关的约束。

在您的示例中,Mongoose仅声明强制性typeunique,因为它要求数据库对该属性建立索引(扩展名为indexsparse为好)。

2。使用Joi(又称业务逻辑)验证其他所有内容

这仅仅是因为Joi的目的是验证对象,使其比Mongoose的功能明显更好,更易于处理。

在您的示例中,Monoose无需({)双重检查minlengthmaxlength,因为Joi已经做到了-只要您正确控制数据库的入口点即可。 lowercase也可以是handled by Joi。这些对Mongo存储数据的方式没有影响,就像password / confirmPassword相等性检查一样,这是纯粹的事情。

(作为补充,您也可以.strip()这个confirmPassword属性,以避免在请求处理程序中执行此操作)

3。保持每个实体的Joi模式接近猫鼬模式

我不会公开验证实体的函数,而是公开POST和PUT模式。在大多数情况下,您可以从POST自动获取PUT(毕竟只是添加一个ID),但是带有密码确认的用户是需要区分的典型示例。

这样,您几乎可以在此处大致构建经典的REST API:使用文件/模块获取路由的名称,根据所使用的HTTP方法尝试对其进行公开验证,然后使用其公开模型进行持久性工作。然后,您也可以考虑使授权具有通用性,如果您的数据模型在会话中向用户明确表达,则可以使实体具有完全通用的API,但这是另一回事。

将Mongoose和Joi模式保存在同一文件中还可以使开发人员(而且您在6个月内忘记了编码方式时)可以快速了解必须如何使用特定实体。这意味着,当某人开发将数据插入数据库的CLI脚本时,他们将拥有在附近使用的验证模式,如果他们“忘记”使用它,则可以责怪他们:再次,控制数据库的入口点。 ;)

4。不要过度验证,也不要在处理特定业务的地方进行验证

我希望实体文件保持实体文件的状态。

您提供了一种验证登录的方法,但是您知道无法插入不符合Joi和Mongoose模式的内容。因此,如果有人想使用1500个字符长的电子邮件登录,请让他们:由于数据库中没有任何匹配项,因此最终将是通常被拒绝的登录。但这并不意味着您不能实现UI验证,它还意味着在架构更改时都应调整UI代码。

但是,以更通用的方式登录确实不是正常的,可能是通用的端点。这些是您可能需要特殊验证步骤的唯一情况(AFAIK)。由于这些路线完全与业务相关,因此您需要手工制作它们,这就是我要进行这些特殊的(也与业务相关的)验证步骤的地方。无论如何,不是在“最紧密相关”实体中。

关于我在用户实体文件中的内容:

import Joi from 'joi';
import { Schema, model } from 'mongoose';

// purely DB stuff
export default model('User', new Schema(
  {
    firstName: { type: String },
    lastName: { type: String },
    email: { type: String, unique: true },
    password: { type: String },
    avatar: { type: String }
  }
));

// purely business stuff
// common schema first
const schema = {
  firstName: Joi.string().min(2).max(30).required(),
  lastName: Joi.string().min(2).max(30).required(),
  email: Joi.string().min(5).max(100).required().lowercase().email(),
  password: Joi.string().min(8).max(1024),
  avatar: Joi.string(),
};

// POST-specific case
const post = {
  ...schema,
  // note that for this particular case, a UI check is enough
  confirmPassword: Joi.string().valid(Joi.ref('password')).required()
    .options({ language: { any: { allowOnly: 'Passwords do not match' } } })
    .strip()
};
// password is only required on registration
post.password = post.password.required();

// PUT-specific case
const put = {
  ...schema,
  id: Joi.string().required()
};

// you may also want to use a derived Joi schema on GET output to strip some data like password
export const joi = { post, put };