'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" }
Solution 1:[1]
JSON.stringify
can't capture non-enumerable object properties as jfriend00 mentions but there are some pretty direct solutions that seem worth mentioning.
One way is to create the correct object yourself using the err.message
property, which contains the string provided to the Error
constructor:
app.get("/foo", (req, res) => {
try {
throw new Error("hello world");
}
catch (err) {
res.status(400).json({error: err.message});
}
});
Another approach if you really can't touch your res.json
call is to throw
an object set up how you like it. I don't like this because you lose stack trace information (see: 1, 2, 3), but it exists:
app.get("/foo", (req, res) => {
try {
throw {error: "hello world"};
}
catch (err) {
res.status(400).json(err);
}
});
In both cases, the client sees
{
error: "hello world"
}
Solution 2:[2]
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}
Solution 3:[3]
You can throw an error in case the user is null or empty. Another thread answers a similar question.
Solution 4:[4]
You should use the message property of the Error class for making it enumerable. You don't need to use other property for error message like the below answer using error propery.
I searched for implementation of Error class in 'core-js'
So, if you want message should be displayed in console or delivered by JSON you should use the following code
class CustomError extends Error {
constructor (message) {
// super(message)
super() // avoid message's non-enumerable property
this.name = this.constructor.name
this.message = message;
// needed for CustomError instanceof Error => true
Object.setPrototypeOf(this, new.target.prototype);
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
}
}
}
Then, you can send with this code
const err = new CustomError('Custom Error...');
...
res.send(err);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | |
Solution 4 |