Pular para o conteúdo

d

Validação e ESLint

Existem restrições que normalmente queremos aplicar aos dados armazenados no banco de dados da nossa aplicação. Nossa aplicação não deve aceitar notas que tenham uma propriedade content em falta ou vazia. A validade da nota é verificada no gerenciador de rota:

app.post('/api/notes', (request, response) => {
  const body = request.body
  if (body.content === undefined) {    return response.status(400).json({ error: 'content missing' })  }
  // ...
})

Se a nota não tiver a propriedade content, respondemos à requisição com o código de status 400 bad request (400 requisição inválida).

Uma maneira mais inteligente de validar o formato dos dados antes de serem armazenados no banco de dados é usar a funcionalidade de validação (validation) disponível no Mongoose.

Podemos definir regras de validação específicas para cada campo no esquema:

const noteSchema = new mongoose.Schema({
  content: {    type: String,    minLength: 5,    required: true  },  important: Boolean
})

O campo content agora deve ter pelo menos cinco caracteres de comprimento e é definido como obrigatório, o que significa que não pode estar faltando. Não adicionamos nenhuma restrição ao campo important, portanto, sua definição no esquema não mudou.

Os validadores minLength (grosso modo, "comprimentoMínimo") e required são integrados (built-in) e fornecidos pelo Mongoose. A funcionalidade de validação personalizada do Mongoose nos permite criar novos validadores se nenhum dos integrados atender às nossas necessidades.

Se tentarmos armazenar no banco de dados um objeto que viola uma das restrições, a operação lançará uma exceção. Vamos alterar nosso gerenciador para criar uma nova nota para que ele passe quaisquer exceções potenciais para o middleware gerenciador de erros:

app.post('/api/notes', (request, response, next) => {  const body = request.body

  const note = new Note({
    content: body.content,
    important: body.important || false,
  })

  note.save()
    .then(savedNote => {
      response.json(savedNote)
    })
    .catch(error => next(error))})

Vamos expandir o gerenciador de erros para lidar com os erros de validação:

const errorHandler = (error, request, response, next) => {
  console.error(error.message)

  if (error.name === 'CastError') {
    return response.status(400).send({ error: 'malformatted id' })
  } else if (error.name === 'ValidationError') {    return response.status(400).json({ error: error.message })  }

  next(error)
}

Quando a validação de um objeto falha, retornamos a seguinte mensagem de erro padrão do Mongoose:

mensagem de erro sendo exibida no postman

Percebemos que o backend tem agora um problema: as validações não são executadas quando uma nota é editada. A documentação explica qual é o problema: as validações não são executadas por padrão quando findOneAndUpdate é executado.

É fácil a correção. Também vamos reformular um pouco o código da rota:

app.put('/api/notes/:id', (request, response, next) => {
  const { content, important } = request.body
  Note.findByIdAndUpdate(
    request.params.id, 
    { content, important },    { new: true, runValidators: true, context: 'query' }  ) 
    .then(updatedNote => {
      response.json(updatedNote)
    })
    .catch(error => next(error))
})

Implantando o backend do banco de dados para produção

A aplicação deve funcionar quase como está no Fly.io/Render. Não precisamos gerar um novo build de produção do frontend, uma vez que as alterações até agora ocorreram apenas no backend.

As variáveis de ambiente definidas em dotenv só serão usadas quando o backend não estiver em modo de produção (production mode), ou seja, no Fly.io ou Render.

Para colocar em produção, temos que definir a URL do banco de dados no serviço que está hospedando nossa aplicação.

Isso é feito no Fly.io com fly secrets set:

fly secrets set MONGODB_URI='mongodb+srv://fullstack:thepasswordishere@cluster0.o1opl.mongodb.net/noteApp?retryWrites=true&w=majority'

Quando a aplicação está sendo desenvolvida, é mais do que provável que algo falhe. Por exemplo, quando implantei minha aplicação pela primeira vez com o banco de dados, não foi exibida uma única nota sequer:

nenhuma nota sendo exibida na aplicação online

O console do navegador na guia Rede revelou que a busca pelas notas não teve sucesso — a requisição simplesmente permaneceu por muito tempo no estado pendente (pending) até falhar e exibir o código de status 502.

O console do navegador tem que estar aberto o tempo todo!

Também é vital acompanhar continuamente os logs do servidor. O problema ficou óbvio quando os logs foram abertos com fly logs:

logs da aplicação online no Fly.io

A URL do banco de dados estava undefined (indefinida), então o comando fly secrets set MONGODB_URI foi esquecido.

Ao usar o Render, a URL do banco de dados é fornecida definindo a variável de ambiente adequada no painel:

opção da variáveis de ambiente no painel do Render

O Painel do Render mostra os logs do servidor:

logs da aplicação online no Render

É possível encontrar o código da nossa aplicação atual na íntegra na branch part3-5 neste repositório do GitHub.

Lint

Antes de prosseguirmos para a próxima parte, vamos dar uma olhada em uma ferramenta importante chamada lint. A Wikipedia diz o seguinte sobre o lint:

De forma genérica, lint ou um linter é qualquer ferramenta que detecta e sinaliza erros em linguagens de programação, incluindo erros de estilo. O termo "lint-like behavior" ("de um jeito lint") é às vezes aplicado ao processo de sinalização do uso suspeito da linguagem. Ferramentas semelhantes ao lint geralmente realizam análise estática do código fonte.

Em linguagens compiladas de tipagem estática, como Java, IDEs (Integrated Development Environment [Ambiente de Desenvolvimento Integrado]) como o NetBeans podem apontar erros no código, inclusive aqueles que são mais do que apenas erros de compilação. Ferramentas adicionais que fazem análise estática (static analysis), como checkstyle, podem ser usadas para expandir as capacidades da IDE e apontar também problemas relacionados ao estilo, como a indentação (indentation).

No universo JavaScript, a ferramenta líder atual para análise estática, também conhecida como "linting", é o ESlint.

Vamos instalar o ESlint como uma dependência de desenvolvimento no projeto backend com o comando:

npm install eslint --save-dev

Após isso, podemos inicializar uma configuração padrão do ESlint com o comando:

npx eslint --init

Responderemos à todas as perguntas:

saída do terminal proveniente do comando 'eslint --init'

A configuração será salva no arquivo .eslintrc.js:

module.exports = {
    'env': {
        'commonjs': true,
        'es2021': true,
        'node': true    },
    'extends': 'eslint:recommended',
    'parserOptions': {
        'ecmaVersion': 'latest'
    },
    'rules': {
        'indent': [
            'error',
            4
        ],
        'linebreak-style': [
            'error',
            'unix'
        ],
        'quotes': [
            'error',
            'single'
        ],
        'semi': [
            'error',
            'never'
        ]
    }
}

Vamos mudar imediatamente a regra relacionada à indentação, para que o nível de indentação (ou nível de recuo) seja de dois espaços.

"indent": [
    "error",
    2
],

Inspeciona-se e valida-se um arquivo como index.js da seguinte maneira:

npx eslint index.js

Recomenda-se criar um npm script separado para a análise estática (linting):

{
  // ...
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js",
    // ...
    "lint": "eslint ."  },
  // ...
}

Agora o comando npm run lint verificará todos os arquivos do projeto.

Também são verificados os arquivos do diretório build quando o comando é executado. Não queremos que isso aconteça; podemos impedir essa análise criando um arquivo .eslintignore na raiz do projeto adicionando o seguinte:

build

Isso faz com que o diretório inteiro build não seja verificado pelo ESlint.

O lint tem bastante a dizer sobre o nosso código:

saída de erros ESlint no terminal

Mas não vamos corrigir esses problemas ainda.

Uma melhor forma de executar o linter a partir da linha de comando é configurar um eslint-plugin no editor, que executará o linter continuamente. Assim que usar o plugin, você verá erros no seu código imediatamente. É possível encontrar mais informações sobre o plugin Visual Studio ESLint aqui.

O plugin ESlint do VS Code sublinhará as violações de estilo com uma linha vermelha:

captura de tela do plugin do vscode ESlint exibindo erros

Isso torna os erros fáceis de detectar e corrigir imediatamente.

O ESlint tem uma vasta variedade de regras, das quais são fáceis de usar por meio do arquivo .eslintrc.js.

Vamos adicionar a regra eqeqeq que nos alerta se a igualdade é verificada com algo que não seja o operador de igualdade estrita. A regra é adicionada sob o campo rules no arquivo de configuração.

{
  // ...
  'rules': {
    // ...
   'eqeqeq': 'error',
  },
}

Já que estamos configurando essa parte, vamos fazer algumas outras mudanças nas regras.

Vamos evitar espaços à direita (ou no final da cadeia) (trailing spaces) ao final das linhas, exigir que sempre haja um espaço antes e depois das chaves e também exigir um uso consistente de espaços em branco nos parâmetros de funções de seta.

{
  // ...
  'rules': {
    // ...
    'eqeqeq': 'error',
    'no-trailing-spaces': 'error',
    'object-curly-spacing': [
        'error', 'always'
    ],
    'arrow-spacing': [
        'error', { 'before': true, 'after': true }
    ]
  },
}

Nossa configuração padrão usa uma série de regras pré-determinadas da regra eslint:recommended:

'extends': 'eslint:recommended',

Isso inclui uma regra que avisa sobre comandos console.log. Para desabilitar uma regra, é necessário que seu "valor" seja definido como 0 no arquivo de configuração. Vamos fazer isso para a regra no-console por enquanto.

{
  // ...
  'rules': {
    // ...
    'eqeqeq': 'error',
    'no-trailing-spaces': 'error',
    'object-curly-spacing': [
        'error', 'always'
    ],
    'arrow-spacing': [
        'error', { 'before': true, 'after': true }
    ],
    'no-console': 0  },
}

Obs.: quando se faz alterações no arquivo .eslintrc.js, é recomendado executar o linter a partir da linha de comando. Isso irá verificar se o arquivo de configuração está formatado corretamente:

saída do terminal como resultado do comando 'npm run lint'

O plugin do lint irá se comportar incorretamente se houver algo errado em seu arquivo de configuração.

Muitas empresas definem padrões de código que são aplicados em toda a organização por meio do arquivo de configuração do ESlint. Não é recomendado reinventar a roda toda vez, e pode ser até uma boa ideia adotar uma configuração pré-pronta do projeto de outra pessoa no seu. Muitos projetos adotaram recentemente o guia de estilo Javascript da Airbnb, adotando a configuração do ESlint da empresa.

É possível encontrar o código da nossa aplicação atual na íntegra na branch part3-6 neste repositório do GitHub.