Artigo original escrito por: Shailesh Shekhawat
Artigo original: How to make input validation simple and clean in your Express.js app
Traduzido e adaptado por: Daniel Rosa

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.