loading...
Cover image for Express: Entendendo o tratamento de erros em Express

Express: Entendendo o tratamento de erros em Express

oieduardorabelo profile image Eduardo Rabelo ・6 min read

Foi difícil aprender a lidar com erros no Express. Ninguém parecia ter escrito as respostas que eu precisava, então tive que aprender da maneira mais difícil.

Hoje, quero compartilhar tudo o que sei sobre como lidar com erros em um aplicativo Express.

Vamos começar com erros síncronos.

Manipulando Erros Síncronos

Se você deseja criar um erro síncrono, você pode usar throw em um manipulador de solicitação no Express (nota: manipuladores de solicitações, request handlers, também são chamados de controladores, controllers. Prefiro chamar de manipuladores de solicitações porque fica mais explícitos e fácil de entender).

app.post('/testing', (req, res) => {
  throw new Error('Something broke! 😱')
})

Esses erros podem ser detectados com um manipulador de erros. Se você não escreveu um manipulador de erros customizado (mais sobre isso abaixo), o Express tratará o erro para você com um manipulador de erros padrão.

O manipulador de erros padrão do Express:

  1. Defina o status HTTP como 500
  2. Envia uma resposta de texto ao solicitante
  3. Registra a resposta de texto no console

Manipulando Erros Assíncronos

Se você deseja manipular um erro assíncrono, é necessário enviar o erro para um manipulador de erro do Express por meio do argumento next:

app.post('/testing', async (req, res, next) => {
  return next(new Error('Something broke again! 😱'))
})

Se você estiver usando o async..await em um aplicativo Express, eu recomendo usar o express-async-handler. Isso permite escrever código assíncrono sem blocos try..catch. Falei mais sobre isso em "Usando o async/await no Express".

const asyncHandler = require('express-async-handler')

app.post('/testing', asyncHandler(async (req, res, next) => {
  // Do something
}))

Ao usar o manipulador de solicitações express-async-handler, você pode criar um erro como antes, usando throw, e ele será tratado com um manipulador de erros Express.

app.post('/testing', asyncHandler(async (req, res, next) => {
  throw new Error('Something broke yet again! 😱')
}))

Escrevendo um manipulador de erro personalizado

Os manipuladores de erro expressos recebem quatro argumentos:

  1. error
  2. req
  3. res
  4. next

Eles devem ser colocados após todos os seus middlewares e rotas:

app.use(/*...*/)
app.get(/*...*/)
app.post(/*...*/)
app.put(/*...*/)
app.delete(/*...*/)

// Coloque seu manipulador de erro depois de todos os middlewares
app.use((error, req, res, next) => { /* ... */ })

O Express deixará de usar seu manipulador de erros padrão depois que você criar um manipulador de erros personalizado. Para lidar com um erro, você precisa se comunicar com o frontend que está fazendo a solicitação. Isso significa que você precisa:

  1. Enviar um código de status HTTP válido
  2. Enviar uma resposta válida

Um código de status HTTP válido depende do que aconteceu. Aqui está uma lista de erros comuns para os quais você deve se preparar:

400 Bad Request Error

  • Usado quando o usuário falha ao incluir um campo (como nenhuma informação do cartão de crédito em uma forma de pagamento)
  • Também é usado quando o usuário digita informações incorretas (exemplo: digitando senhas diferentes em um campo de senha e campo de confirmação de senha).

401 Unauthorized Error

  • Usado quando o usuário digita informações de login incorretas (como nome de usuário, email ou senha).

403 Forbidden Error

  • Usado quando o usuário não tem permissão para acessar o endereço.

404 Not Found Error

  • Usado quando o endereço não pode ser encontrado.

500 Internal Server Error

  • A solicitação enviada pelo front-end está correta, mas ocorreu um erro no back-end.

Depois de determinar o código de status HTTP correto, você deseja definir o status com res.status:

app.use((error, req, res, next) => {
  // Bad Request Error
  res.status(400)
  res.json(/* ... */)
})

O código de status HTTP deve corresponder à mensagem de erro. Para que o código de status corresponda à mensagem de erro, você deve enviar o código de status junto com o erro.

A maneira mais fácil é usar o pacote http-errors. Permite enviar três coisas em seus erros:

  1. Um código de status
  2. Uma mensagem para acompanhar o erro
  3. Quaisquer propriedades que você gostaria de enviar (isso é opcional).

Instalando o http-errors:

npm install http-errors --save

Usando o http-errors:

const createError = require('http-errors')

// Criando um erro
throw createError(status, message, properties)

Vamos montar um exemplo para torná-lo mais claro. Digamos que você tentou encontrar um usuário pelo endereço de e-mail. O usuário não pode ser encontrado. Você deseja lançar um erro que diz "Usuário não encontrado".

Ao criar o erro, você deseja:

  1. Enviar um error 400 Bad Request Error (porque o usuário preencheu informações incorretas). Você envia isso como o primeiro parâmetro.
  2. Enviar uma mensagem dizendo "Usuário não encontrado". Você envia isso como o segundo parâmetro.
app.put('/testing', asyncHandler(async (req, res) => {
  const { email } = req.body
  const user = await User.findOne({ email })

  // Cria um erro se o usuário não for encontrado
  if (!user) throw createError(400, `User '${email}' not found`)
}))

Você pode obter o código de status com error.status e a mensagem de error com error.message.

// Fazendo o log do erro
app.use((error, req, res, next) => {
  console.log('Error status: ', error.status)
  console.log('Message: ', error.message)
})

Em seguida, defina o status do erro com res.status. Você envia a mensagem com res.json.

app.use((error, req, res, next) => {
  // Seta o HTTP Status Code
  res.status(error.status)

  // Envia a resposta
  res.json({ message: error.message })
})

Pessoalmente, gosto de enviar o status, a mensagem e o rastreamento do erro para que eu possa depurar facilmente.

app.use((error, req, res, next) => {
  // Seta o HTTP Status Code
  res.status(error.status)

  // Envia a resposta
  res.json({
    status: error.status,
    message: error.message,
    stack: error.stack
  })
})

Código de status de fallback

Se o erro não for criado com createError, ele não terá uma propriedade de status.

Aqui está um exemplo. Digamos que você tentou ler um arquivo com fs.readFile, mas o arquivo não existe.

const fs = require('fs')
const util = require('util')

const readFilePromise = util.promisify(fs.readFile)

app.get('/testing', asyncHandler(async (req, res, next) => {
  const data = await readFilePromise('some-file')
})

Este erro não conteria uma propriedade status.

app.use((error, req, res, next) => {
  console.log('Error status: ', error.status)
  console.log('Message: ', error.message)
})

Nesses casos, você pode usar o padrão 500 Internal Server Error.

app.use((error, req, res, next) => {
  res.status(error.status || 500)
  res.json({
    status: error.status,
    message: error.message,
    stack: error.stack
  })
})

Alterando o código de status de um erro

Digamos que você deseja buscar um arquivo baseado na informação enviada por um usuário. Se o arquivo não existir, você deverá lançar um 400 Bad Request Error, porque não é culpa do seu servidor.

Nesse caso, você deseja usar try..catch para capturar o erro original. Em seguida, você recria um erro com createError.

app.get('/testing', asyncHandler(async (req, res, next) => {
  try {
    const { file } = req.body
    const contents = await readFilePromise(path.join(__dirname, file))
  } catch (error) {
    throw createError(400, `File ${file} does not exist`)
  }
})

Manipulando erros 404

Um terminal não será encontrado se uma solicitação cair em todos os seus middlewares e rotas.

Para lidar com um erro não encontrado, insira um middleware entre suas rotas e seu manipulador de erros. Aqui, crie um erro com createError.

// Middlewares...
// Routes...

app.use((req, res, next) => {
  next(createError(404))
})

// Error handler...

Detalhes sobre "Cannot set headers after they are sent to the client"

Não entre em pânico se você vir um erro que diz "Cannot set headers after they are sent to the client".

Este erro ocorre porque o código executou métodos que definem cabeçalhos de resposta mais de uma vez no mesmo manipulador. Estes são os métodos que definem os cabeçalhos de resposta para você:

  1. res.send
  2. res.json
  3. res.render
  4. res.sendFile
  5. res.sendStatus
  6. res.end
  7. res.redirect

Por exemplo, se você executar res.render e res.json no mesmo manipulador de respostas, voc6e receberá o erro.

app.get('/testing', (req, res) => {
  res.render('new-page')
  res.json({ message: '¯\_(ツ)_/¯' })
})

Portanto, se você receber esse erro, verifique seus manipuladores de resposta para que ele não execute os métodos acima duas vezes.

Cuidados ao fazer streaming

Se ocorrer um erro ao realizar streaming de uma resposta ao front-end, você receberá o mesmo erro "Cannot set headers...".

Nesse caso, o Express declara que você deve delegar o tratamento de erros aos manipuladores padrões do Express. Isso enviará um erro e fechará a conexão para você.

app.use((error, req, res, next) => {
  // Caso você esteja fazendo o stream de uma reposta
  if (res.headersSent) {
    return next(error)
  }

  // Restante dos manipuladores de erros
})

Créditos

Discussion

pic
Editor guide