How to perform data validation using Yup with custom locale?

Asked

Viewed 4,302 times

2

I’m trying to add input data validation to a small script I built using Express, the validation part is using Yup.

I’m having a hard time organizing things because of the need to work with a custom locale on Yup.

In this file I have the definition of custom messages, as well as a function that generates an array containing rule violation data:

const yup = require('yup')

yup.setLocale({
    mixed: {
        default: 'é inválido',
        required: 'é um campo obrigatório',
        oneOf: 'deve ser um dos seguintes valores: ${values}',
        notOneOf: 'não pode ser um dos seguintes valores: ${values}',
    },
    string: {
        length: 'deve ter exatamente ${length} caracteres',
        min: 'deve ter pelo menos ${min} caracteres',
        max: 'deve ter no máximo ${max} caracteres',
        email: 'tem o formato de e-mail inválido',
        url: 'deve ter um formato de URL válida',
        trim: 'não deve conter espaços no início ou no fim.',
        lowercase: 'deve estar em maiúsculo',
        uppercase: 'deve estar em minúsculo',
    },
    number: {
        min: 'deve ser no mínimo ${min}',
        max: 'deve ser no máximo ${max}',
        lessThan: 'deve ser menor que ${less}',
        moreThan: 'deve ser maior que ${more}',
        notEqual: 'não pode ser igual à ${notEqual}',
        positive: 'deve ser um número posítivo',
        negative: 'deve ser um número negativo',
        integer: 'deve ser um número inteiro',
    },
        date: {
        min: 'deve ser maior que a data ${min}',
        max: 'deve ser menor que a data ${max}',
    },
        array: {
        min: 'deve ter no mínimo ${min} itens',
        max: 'deve ter no máximo ${max} itens',
    },
});

const validate = (schema, data) => {
    return schema.validate(data, { abortEarly: false })
      .then(_ => {
          return null
      }).catch(err => {
        return err.inner.map(item => {
            return {
                path: item.path,
                message: item.message,
                label: item.params.label
            }
        })
    })
}

module.exports = { yup, validate }

This is where the above file call is made:

const express = require('express')
const {yup, validate} = require('./validator')

const server = express()

server.use(express.json())
server.use(express.urlencoded({ extended: false}))

server.get('/', async (req, res) => {

      schema = yup.object().shape({
        name: yup.string().email().required(),
        password: yup.string().required()
      });

      const errors = await validate(schema, req.body)

      if (errors) {
          return res.json(errors)
      }

      return res.json({ sucess: true})


})

server.listen(3000)

In my view I’m doing it wrong, but I can’t determine if I have to separate responsibilities, use constants to store the custom mailing list and the validate function or even a Singleton.

whereas not all application routes will input data, as I could address this issue of separation of responsibilities?

  • Man, I really think Yup can’t stand this kind of stuff... You can format the error by sending keys to the client (as if they were codes) and it is responsible for translating each key to the given language. You can also search for another validation library...

1 answer

1

I would recommend you to put in separate files the custom locale, the validation function and each schema.

In your route file, you can add a middleware by calling its validation function before executing the logic of that route. This way, as it is optional, it will not be necessary on all routes.

I made a small example with two routes, one without validation and the other with validation.

src/index.js file

const server = require("./server");
server.listen(3000 || process.env.PORT);

src/server.js file.

const express = require("express");
const Youch = require("youch");

class App {
  constructor() {
    this.express = express();
    this.isDev = process.env.NODE_ENV === "production";
    this.middlewares();
    this.routes();
    this.exception();
  }

  middlewares() {
    this.express.use(express.json());
  }
  routes() {
    this.express.use(require("./routes"));
  }
  // Quando o middleware tem quatro parametros ele serve para tratativa de erros
  exception() {
    if (process.env.NODE_ENV === "production") {
      //
    }
    this.express.use(async (err, req, res, next) => {

      // Apenas para debug no ambiente interno
      if (process.env.NODE_ENV === "production") {
        // Não se aplica
        console.log("producao");
      } else {
        console.log("desenvolvimento");
        const youch = new Youch(err, req);
        return res.json(await youch.toJSON());
      }

      // Para um erro que nao recebeu um tratamento especifico
      return res
        .status(err.status || 500)
        .json({ error: "Internal Server Error" });
    });
  }
}

module.exports = new App().express;

src/Routes.js file

const express = require("express");
const handle = require("express-async-handler");
const routes = express.Router();
const controllers = require("./app/controllers");
const validators = require("./app/validators");
routes.get("/", (req, res) => res.json({ ok: true }));

routes.post(
  "/login",
  async function(req, res, next) {
    await validators.validate(validators.Login, req.body, res, next);
  },
  handle(controllers.SessionController.index)
);

module.exports = routes;

Note that on the route /login I have 3 parameters, the first is the route address, the second is an asynchronous function that will call your validation routine, and the third is another asynchronous function that will run the route logic (The express-async-Handler serves to facilitate the calling of a function asynchronously)

Inside the folders /src/app/controllers and /src/app/validators I have an index.js file with only one line.

module.exports = require("require-dir")();

Within /src/app/controllers I have a Sessioncontroller.js file with route logic /login.

class SessionController {
  async index(req, res) {
    console.log("rota de login!");
    const { name, password } = req.body;
    //Logica para validar o login
    const user = { nome: "User1", email: "[email protected]" };
    return res.status(200).json({ user, token: "123456" });
  }
}

module.exports = new SessionController();

All validation logic was inside the /src/app/validators folder. Your Validator and schema had no changes, they were only placed separately.

File /src/app/validators/Validator.js

const yup = require("yup");

yup.setLocale({
  mixed: {
    default: "é inválido",
    required: "é um campo obrigatório",
    oneOf: "deve ser um dos seguintes valores: ${values}",
    notOneOf: "não pode ser um dos seguintes valores: ${values}"
  },
  string: {
    length: "deve ter exatamente ${length} caracteres",
    min: "deve ter pelo menos ${min} caracteres",
    max: "deve ter no máximo ${max} caracteres",
    email: "tem o formato de e-mail inválido",
    url: "deve ter um formato de URL válida",
    trim: "não deve conter espaços no início ou no fim.",
    lowercase: "deve estar em maiúsculo",
    uppercase: "deve estar em minúsculo"
  },
  number: {
    min: "deve ser no mínimo ${min}",
    max: "deve ser no máximo ${max}",
    lessThan: "deve ser menor que ${less}",
    moreThan: "deve ser maior que ${more}",
    notEqual: "não pode ser igual à ${notEqual}",
    positive: "deve ser um número posítivo",
    negative: "deve ser um número negativo",
    integer: "deve ser um número inteiro"
  },
  date: {
    min: "deve ser maior que a data ${min}",
    max: "deve ser menor que a data ${max}"
  },
  array: {
    min: "deve ter no mínimo ${min} itens",
    max: "deve ter no máximo ${max} itens"
  }
});

module.exports = yup;

File /src/app/validators/Login.js

const yup = require("./validator");

const schema = yup.object().shape({
  name: yup
    .string()
    .email()
    .required(),
  password: yup.string().required()
});
module.exports = schema;

Its validation function had changes to be inserted as a route middleware

File /src/app/validators/validate.js

const validate = (schema, data, res, next) => {
  return schema
    .validate(data, { abortEarly: false })
    .then(_ => {
      next();
    })
    .catch(err => {
      var erro = err.inner.map(item => {
        return {
          path: item.path,
          message: item.message,
          label: item.params.label
        };
      });
      res.send(erro);
    });
};

module.exports = validate;

The package.json was as follows:

{
  "name": "validacao_yup",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node src/index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "express-async-handler": "^1.1.4",
    "require-dir": "^1.2.0",
    "youch": "^2.0.10",
    "yup": "^0.28.0"
  }
}
  • I found your solution elegant, I did not understand why they denied your answer.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.