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:
- You could add your own
.json2()
method that includes all properties of an Error object (even non-enumerable ones).
- 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.
- 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}