Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return error message objects to the client in Express?

I have this block of code:

router.post('/users/login', async (req, res) => {
  try {
    const { email, password } = req.body
    const user = await User.findByCredentials(email, password)
    console.log(user) //false
    if (!user) {
      throw new Error('Login failed! Check authentication credentials')
    }
    const token = await user.generateAuthToken()
    res.status(200).json({ user, token })
  } catch (err) {
    console.log(err) //Error: Login failed! Check authentication credentials at C:\Users...
    res.status(400).json(err)
  }
})

It works all fine until there is no Error. When error occur (user is false) in Postman I have only empty object returned {}. Also with res.status(400).json({error: err}) it gives me { "err": {} }.

I want to receive object like this { "error": "Login failed! Check authentication credentials" }

like image 653
kapsztat12 Avatar asked Dec 04 '25 03:12

kapsztat12


1 Answers

Error objects don't serialize well in JSON because some of their properties are non-enumerable and thus JSON.stringify() does not include them when res.json(err) uses it.
That's why res.json(err) doesn't show what you want.

Possible reasons why non-enumerable properties are not included is that they may contain stack traces and other information that is not meant to be sent back to the client. That's just a by-product of how the Error object works and how JSON.stringify() is implemented and res.json() just inherits those issues and the interaction between the two. I've never understood why the main .message property is non-enumerable as that has never made sense to me, but it is.

There are a number of possible work-arounds:

  1. You could add your own .json2() method that includes all properties of an Error object (even non-enumerable ones).
  2. You could use the Express error handling scheme where you call next(err) and you supply a centralized error handler that gets called and you can do your own serialization of the error response there.
  3. You could make a subclass of Error that populates enumerable properties that will show up in JSON and use that.

Option #3 could look like this:

class RouteError extends Error {
    constructor(msg, statusCode = 500) {
        super(msg);
        // define my own enumerable properties so they
        // will show up in JSON automatically
        this.error = msg;
        this.statusCode = statusCode;
    }
}

router.post('/users/login', async (req, res) => {
  try {
    const { email, password } = req.body
    const user = await User.findByCredentials(email, password)
    console.log(user) //false
    if (!user) {
      throw new RouteError('Login failed! Check authentication credentials', 401)
    }
    const token = await user.generateAuthToken()
    res.status(200).json({ user, token })
  } catch (err) {
    console.log(err) //Error: Login failed! Check authentication credentials at C:\Users...
    res.status(err.statusCode).json(err);
  }
});

This example would generate this response:

{"error":"Login failed! Check authentication credentials","statusCode":401}
like image 168
jfriend00 Avatar answered Dec 05 '25 21:12

jfriend00