How to return error message objects to the client in Express? - node.js

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" }

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}

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 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"
}

You can throw an error in case the user is null or empty. Another thread answers a similar question.
Node.js with Express - throw Error vs next(error)

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'
https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/wrap-error-constructor-with-cause.js#:~:text=if%20(message%20!%3D%3D%20undefined)%20createNonEnumerableProperty(result%2C%20%27message%27%2C%20message)%3B
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);

Related

How to send a custom status and error message if no data found using Node.JS/express and axios?

If no user is found (it's just an example to understand error handling), I want to send a custom message such as
res.status(404).send('no user');
my client never receives that and instead I get:
[AxiosError: Request failed with status code 404]
What am I doing wrong? I cannot find any other solution and have been researching for a while now. Also wonder how I could send a custom status (if no data found it's 404 but what if I want to send 200)?
node express
router.get('/getuser', async (req, res) => {
try {
const user = await User.findOne({_id: req.user._id});
if (!user) {
res.status(404).send('no user');
} else {
res.status(200).send(user)
}
} catch(error) {
res.status(500).send(error)
}
});
frontend
const trycatch = async () => {
try {
const response = await axios.get(example.com/trycatch)
return response.data;
} catch (error) {
console.log(error)
}
}
Responses with a status code of 400 or more are treated as errors by axios. As a consumer, you can only react to that in the catch (error) clause, for example:
catch (error) {
switch (error.response.status) {
case 404: return "no user"; break;
default: return error.response.data;
}
}
But you can influence which statuses cause errors with the validateStatus option of the axios request.
Summary: Either you distribute your code between the try and the catch block, where the try block code handles successes (status < 400) and the catch block handles errors (status ≥ 400). Or you use validateStatus to change what counts as a success and what counts as an error.

How to auto send error of try catch block to Sentry in Node JS

I am building a Node JS application. I am using Sentry, https://docs.sentry.io/platforms/node/ in my application to monitor and report errors. But I am having a problem with global reporting for try catch block.
For example, I have a code block as follow.
const getUser = async (id) => {
try {
//do the database operation and return user
} catch (e) {
return {
data: null,
message: e.message
}
}
}
As you can see in the code, I am catching the error in the Try catch block. If I want to report the error to Sentry, I have to put in the following line in the catch block.
Sentry.captureException(e);
Basically, I am explicitly reporting the error. Is there a way to globally and automatically catch the error within the catch block and report it to the sentry. For, example, something like in PHP or Laravel. We will just have to initialize and configure the Sentry in one centralized place of the application and app will report any errors to the Sentry.
Sentry starts monitoring the whole application just by adding, the init function, somewhere in a global scope.
For example :
Sentry.init({
debug: appConfig.env === 'staging',
dsn: appConfig.sentryDSN,
environment: appConfig.env,
integrations: [new Integrations.BrowserTracing()],
release: [pjson.name, pjson.version].join('#'),
tracesSampleRate: 1.0,
ignoreErrors: [],
normalizeDepth: 10, // Or however deep you want your state context to be.
// (breadcrumb: Breadcrumb, hint?: BreadcrumbHint | undefined) => Breadcrumb | null
beforeBreadcrumb(breadcrumb: Sentry.Breadcrumb, hint: Sentry.BreadcrumbHint | undefined) {
return breadcrumb.category === 'xhr' ? breadcrumb : null;
},
});
You can stay just to that 'conf/init' if you like, this captures some errors, which are: `every error that inherits from the 'Error' object, like: TypeError, RangeError, ReferenceError, SyntaxError, etc , for more: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
But its better to handle the errors explicitly, and have the power to control what you want to send to Sentry(add/filter breadcrumbs, add tags, extra data etc).
In my reactjs app i have a middleware that all the errors are sent there, and inside there there is a logic..
Similarly i d suggest an error middleware where all the errors are send there, and at that middleware you exlicitly handle & send them to Sentry.
I assume the the tech stack node + express, so i d suggest, in the routes catch, to call next(error):
router.get('/path', function(req, res, next){
const getUser = async (id) => {
try {
//do the database operation and return user
} catch (error) {
//return {
// data: null,
// message: e.message
// }
next(error)
}
}
})
**OR based on Express 5, route handlers that return Promise will call next(value) automatically when they reject or throw an error **:
app.get('/path', async function (req, res, next) {
var user = await getUser(id)
res.send(user)
})
And into the app.js, you put the error handler middleware where it handles the errors..:
app.use(function(err, req, res, next){
// filter the error
// send it to Sentry
if(condition){
// add custom breadcrumb
Sentry.addBreadcrumb({
type: Sentry.Severity.Error,
category,
message,
level: Sentry.Severity.Error,
});
Sentry.configureScope((scope: any) => {
scope.setTag('section', section);// add tag
Sentry.captureException(error, scope); // capture the error
});
}
})

Nodejs response and console

I am creating a NodeJS API using Express, PostgreSQL in which I created a controller function which will check the user from the database.
const checkUser = async (req, res) => {
try {
const user = await pool.query('select * from users where email = $1', [req.body.email]);
if (user.rows.length === 0) throw new Error('Error Found');
return res.json("User Found");
} catch (e) {
//======= Output at console ========
//Error: Error Found
console.log(e);
//======== Response Object Received From API ==========
// {
// "msg": "Error Found",
// "Error": {}
// }
res.status(400).send({ msg: 'Error Found', Error: e });
}
};
Consider the situation that the user is not found in the Database so from try block the control passes to the catch block.
1- I am not able to get why the Error thrown from the try block to catch block sends back an empty object as a response. Also, at the same time using console.log prints the correct output value in the console.
2- I want a fix so that I can send e as a response to the User.
The problem is that Error objects are not that easy to serialize (which is intentional). This...
try {
throw new Error('Catch me if you can');
} catch(e) {
console.log(JSON.stringify(e)); // {}
}
... just logs {}, the same as for empty object, because Error objects don't have enumerable properties. And in your case, you don't send that object directly - but make it a property of another object sent to client.
However, there are several ways out of this. If you always need your client to get all the details of an Error, cast that to string (as toString() is overridden):
try {
throw new Error('Catch me if you can');
} catch(e) {
console.log(JSON.stringify(e.toString())); // "Error: Catch me if you can"
}
... or use Error object properties - name and message - as separate fields of your response:
res.status(400).send({
name: e.name || 'Anonymous Error',
msg: e.message || 'no message provided'
});
As sub-approach, you might consider choosing your status code based on type of error you have (always sending 404 for 'not founds', for example).

Why express say `Cannot set headers after they are sent to the client` in my code?

I'm creating some kind of SignIn request/response in Express.
When user give valid username & password, then server will return valid authorization code.
This is code.
export const getAuthKey = async (req, res, next) => {
// Query parameter : :username, :hash(SHA256 encoded pasword)
// Return : { authKey: authKey }
if (req.query.username === undefined) { res.status(400).send('`username` query paramter missing.'); }
if (req.query.hash === undefined) { res.status(400).send('`hash` query paramter missing.'); }
getConnection()
.then(conn => new Promise((resolve, reject) => {
const escapeUserName = conn.escape(req.query.username);
const escapeSHA256 = conn.escape(req.query.hash);
return conn.query(`SELECT HEX(AuthorizationKey) AS authKey FROM UserInfo WHERE UserName = ${escapeUserName} AND UserPassword = UNHEX(${escapeSHA256});`)
.then(result => resolve(result))
.catch(err => reject(err))
.finally(() => { conn.end(); });
}))
.then(auth => {
if (auth.length < 1) { res.status(401).send('No such user found'); }
else {
log(`User ${req.query.username} requests valid authorization.`, `AUTH`)
res.json(auth[0]);
}
})
.catch(err => {
log('Unable to get authKey', 'AUTH', 'ERROR', err);
res.status(500).send(`Unable to logging-in`);
});
}
The steps are simple,
1. User send request with parameter username and hash(password)
1-1. If parameter is not fulfilled, it returns status 400.
2. Check Database and find if user is valid, if so, return it's authorization code.
3-2 If there are some unexpected error, returns status 500.
This code works. But I always see
(node:11580) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I think I miss something about http request.
Can you tell me what is wrong?
Also would you provide me a code pattern for this kind of process?
p.s please ignore async on function definition.
The particular error you asked about "Cannot set headers after they are sent to the client" is caused by attempting to send a second response to the same request. So, there is typically a problem with the flow through your code that causes it to execute multiple code paths that can send a response.
On these two statements, you need to add a return statement so code does not continue executing in your function after you've sent a response:
if (req.query.username === undefined) { res.status(400).send('`username` query paramter missing.'); }
if (req.query.hash === undefined) { res.status(400).send('`hash` query paramter missing.'); }
Even though you've called res.send(), normal Javascript flow control still applies and the rest of your function will continue to execute, causing you to attempt to send another response which is the source of the "headers already sent" warning.
So, add a return to each one of these if statements to stop the function from further execution after sending the response.
if (req.query.username === undefined) {
res.status(400).send('`username` query parameter missing.');
return;
}
if (req.query.hash === undefined) {
res.status(400).send('`hash` query parameter missing.');
return;
}
I'd also suggest you modify these if statements slightly to include more conditions like empty strings:
if (!req.query.username) {
res.status(400).send('`username` query parameter missing.');
return;
}
if (!req.query.hash) {
res.status(400).send('`hash` query parameter missing.');
return;
}
FYI, also fix the spelling of "parameter".

NodeJs http status exception handling

I have created nodejs + express application. Now in my application when exception caught errors are send as follows
app.get('/data', (req, res) => {
if(!req.params.token){
return res.status(403).send('Access token not provided');
}
//do something here
});
Instead of sending res.status(403).send('Access token not provided'); can I send something like this
exception.js
class Forbidden {
constructor(message,stack = null){
this.code = 403;
this.message = message
this.stack = stack;
}
}
app.js
var httpForbidden = require('exception.js');
app.get('/data', (req, res) => {
if(!req.params.token){
return new httpForbidden ('Access token not provided');
}
//do something here
});
And also how can I caught all exceptions in once place ?
You could use something like this:
class httpError {}
class httpForbidden extends httpError {
constructor(message, stack = null) {
super();
this.code = 403;
this.message = message
this.stack = stack;
}
}
app.get('/', (req, res) => {
if (!req.params.token) {
throw new httpForbidden('Access token not provided');
}
...
});
app.use((err, req, res, next) => {
if (err instanceof httpError) {
return res.status(err.code).send(err.message);
}
res.sendStatus(500);
});
This uses an Express error handling middleware that will check if the error that got thrown is an instance of httpError (which would be the superclass of all the HTTP error classes that you'd want to create) and, if so, would generate a particular response according to the code and the message (or generate a generic 500 error response otherwise).
I like to create a separate function, along with other utility functions ( say in lib.js), which creates a properly formatted JSON response object and selects the appropriate logger to log response depending upon the HTTP status code.
lib.js
var logger = require("./loggger");
module.exports.sendResponse = function (res,code,message,data) {
if(code<100 || code>599) {
throw new Error("response cannot be sent. Invalid http-code was provided.");
}
var responseLogger = code>=500 ? logger.error : logger.debug;
var responseObject = {
"code" : code,
"message" : message
};
if(data) {
responseObject.data = data;
}
responseLogger(responseObject);
res.status(code).json(responseObject);
};
app.js
var lib = require("./lib");
/*
Relevant Express server code
*/
app.get('/data', function (req,res) {
if(!req.params.token){
return lib.sendResponse(res,403,"Access token not provided");
}
// Rest of business logic
});
Note : You can write your own logging functionality, but I strongly suggest to build it upon some standard logging library like winston)
Below method is deprecated as the boom is changes to #hapi/boom,
https://hapi.dev/family/boom/?v=8.0.1
here you find whole documentation of #hapi/boom library
-----deprecated-------
You can use boom library instead, which provides a set of utilities for returning HTTP errors
HTTP 4xx Errors
Boom.badRequest([message], [data])
Boom.unauthorized([message],[scheme], [attributes])
HTTP 5xx Errors
Boom.badImplementation([message], [data]) - (alias: internal)
Boom.notImplemented([message], [data])
for more api documentation visit here
You can use:
res.code(403).json({message: '...', stack: '...'});
and send whatever you want. But you do it with calling methods on the response object.
And also how can I caught all exceptions in once place ?
Very bad idea. You should handle all errors where they happen so that you can still have some context to handle them in a reasonable way. Otherwise you can just throw exceptions and return 500 errors.

Resources