Artigo original: How to make input validation simple and clean in your Express.js app

Este tutorial exige conhecimento prévio do uso do framework Express.js

Por que precisamos de validação do lado do servidor?

  • A validação no lado do client não é suficiente e pode ser burlada
  • Ela é mais propensa a ataques do tipo "man-in-the-middle" e o servidor jamais deve confiar no lado do client
  • Um usuário pode desligar a validação do JavaScript do lado do client e manipular os dados

Se você tem criado aplicações da web usando o framework Express.js ou algum framework do Node.js, a validação tem uma função fundamental em qualquer aplicação da web que exija que você valide solicitações com body, params e query.

Escrever seu próprio middleware pode ser trabalhoso se

  • você quiser ser rápido e, ainda assim, manter a qualidade do código, ou
  • quiser evitar o uso de if (req.body.head) ou de if (req.params.isCool) em sua função controladora, onde você define a lógica de negócios

Neste tutorial, você aprenderá a validar entradas em uma aplicação do Express.js usando um módulo de código aberto e popular chamado express-validator.

Introdução ao express-validator

A definição no Github (tradução nossa) diz que:

o express-validator é um conjunto de middlewares do express.js que envolve as funções de validação e sanitização do validator.js.

O módulo implementa cinco API importantes:

  • a API Check
  • a API Filter
  • a API Sanitization Chain
  • a API Validation Chain
  • a API Validation Result

Vamos dar uma olhada em um usuário básico route sem módulos de validação para a criação de um usuário:  /route/user.js

/**
* @api {post} /api/user Create user
* @apiName Create new user
* @apiPermission admin
* @apiGroup User
*
* @apiParam  {String} [userName] username
* @apiParam  {String} [email] Email
* @apiParam  {String} [phone] Phone number
* @apiParam  {String} [status] Status
*
* @apiSuccess (200) {Object} mixed `User` object
*/

router.post('/', userController.createUser)

Agora, no controlador de usuário /controllers/user.js

const User = require('./models/user')

exports.createUser = (req, res, next) => {
  /** Aqui, você precisa validar a entrada do usuário. 
   Digamos que apenas os campos Name e email sejam obrigatórios
 */
  
  const { userName, email, phone, status } = req.body
  if (userName && email &&  isValidEmail(email)) { 
    
    // isValidEmail é uma função de e-mail personalizada para validar o e-mail, da qual você pode precisar escrever em seu próprio módulo ou ao usar um módulo do npm
    User.create({
      userName,
      email,
      phone,
      status,   
    })
    .then(user => res.json(user))
    .catch(next)
  }
}

O código acima é apenas um exemplo básico de campos de validação por conta própria.

Você pode lidar com as validações em seu modelo de usuário com o Mongoose. Em termos de práticas recomendadas, queremos garantir que a validação ocorra antes da lógica de negócios.

O express-validator cuidará de todas essas validações e também fará a sanitização das entradas (texto em inglês).

Instalação

npm install --save express-validator

Inclua o módulo em seu arquivo server.js:

const express = require('express')
const bodyParser = require('body-parser')
const expressValidator = require('express-validator')
const app = express()
const router = express.Router()

app.use(bodyParser.json())

app.use(expressValidator())

app.use('/api', router)

Agora, usando o express-validator, /routes/user.js terá essa aparência:

router.post(
  '/', 
  userController.validate('createUser'), 
  userController.createUser,
)

Aqui, userController.validate é uma função de middleware, explicada logo abaixo. Ela aceita o nome method, para o qual a validação será usada.

Vamos criar uma função de middleware validate() em nosso /controllers/user.js:

const { body } = require('express-validator/check')

exports.validate = (method) => {
  switch (method) {
    case 'createUser': {
     return [ 
        body('userName', 'userName doesn't exists').exists(),
        body('email', 'Invalid email').exists().isEmail(),
        body('phone').optional().isInt(),
        body('status').optional().isIn(['enabled', 'disabled'])
       ]   
    }
  }
}

Consulte este artigo (em inglês) para saber mais sobre a definição de funções e seu uso.

A função body validará apenas req.body e receberá dois argumentos. O primeiro será property name. O segundo será sua mensagem (message) personalizada, que será exibida caso haja problemas com a validação. Se você não fornecer uma mensagem personalizada, a mensagem padrão será usada.

Como se pode ver, para um campo obrigatório (required), usaremos o método .exists(). Usaremos .optional() para um campo opcional (optional). Da mesma maneira, isEmail() e isInt() serão usados para validar campos do tipo email e integer.

Se desejar que um campo de entrada inclua apenas determinados valores, é possível usar .isIn([]). Essa função recebe um array de valores. Se você receber valores que não estejam no array, será lançado um erro.

Por exemplo, o campo status no trecho de código acima somente pode ter os valores enabled ou disabled. Se você fornecer um valor diferente, será lançado um erro.

Em /controllers/user.js, vamos escrever uma função createUser, onde é possível escrever a lógica de negócios. Ela será chamada após validate() com o resultado das validações.

const { validationResult } = require('express-validator/check');

exports.createUser = async (req, res, next) => {
   try {
      const errors = validationResult(req); // Encontra os erros de validação nesta solicitação e os envolve em um objeto com funções úteis

      if (!errors.isEmpty()) {
        res.status(422).json({ errors: errors.array() });
        return;
      }

      const { userName, email, phone, status } = req.body
      
      const user = await User.create({

        userName,

        email,

        phone,

        status,   
      })

      res.json(user)
   } catch(err) {
     return next(err)
   }
}

Está se perguntando sobre o que é validationResult(req)?

Essa função encontra os erros de validação nessa solicitação e os envolve em um objeto com funções úteis

Agora, sempre que uma solicitação incluir parâmetros de body ou quando o campo userName estiver ausente em req.body, seu servidor responderá assim:

{
  "errors": [{
    "location": "body",
    "msg": "userName é obrigatório",
    "param": "userName"
  }]
}

Desse modo, se userName ou se email não satisfizerem a validação, cada erro retornado pelo método .array() terá o seguinte formato por padrão:

{   
  "msg": "A mensagem de erro",
   
  "param": "nome do parâmetro", 
  
  "value": "valor do parâmetro",   
  // A localização do parâmetro que gerou esse erro.   
  // Será normalmente body, query, params, cookies ou headers.   
  "location": "body",    
  
  // nestedErrors existem apenas quando usados na função oneOf
  "nestedErrors": [{ ... }] 
}

Como você pode ver, esse módulo realmente é útil para tratarmos da maioria das validações por conta própria. Ele também mantém a qualidade do código e tem como foco principal a lógica de negócios.

Essa foi a introdução à validação de entradas usando o módulo express-validator. Saiba como validar um array de itens e criar sua própria validação personalizada na parte 2 dessa série (em inglês, a ser traduzida em breve).

Busquei trazer a melhor explicação possível e espero ter tratado do suficiente para explicar a validação em detalhes para que você possa começar seus trabalhos.

Caso encontre problemas, fique à vontade para entrar em contato com o autor, que ajudará você com prazer :)

Acompanhe Shailesh Shekhawat para receber notificações quando ele publicar novos artigos.

Não se esqueça de agradecer o autor caso ache útil a leitura!

Publicado inicialmente em 101node.io (site não encontrado no momento da tradução) em 2 de setembro de 2018.