Handling errors in nodejs - node.js

i am making a nodejs web api and i have a function that returns a user object that is associated with a given authentication token:
module.exports.getByToken = function (token_value, callback)
{
mongoose.model('tokens').findOne({ value: token_value }).exec(function (err, token)
{
if (err || token == null)
{
var error = new Error('couldn\'t find user of the given token');
callback(error, null);
}
else
{
mongoose.model('users').find({ _id: token.user }).exec(callback);
}
});
};
As you can see, i am passing the error back to the callback instead of throwing it. Am i doing it right?
This function is called from an authentication middleware:
app.use('/api', function (req, res, next)
{
var token = req.headers.authorization;
users.getByToken(token, function (err, user)
{
if (err || user == null)
{
res.status(401).end('Unauthorized');
}
else
{
app.locals.user = user;
next();
}
});
});
So the idea of passing the error back to the callback works conveniently.
But is this the right way to handle errors?
Can it block the main thread?
Should i throw the error instead and explicitly catch it in the middleware?
Thanks,
Arik

IMO your are doing it the right way. Callbacks should return an error as the first parameter if they are not responsible for handling it. If you want to improve how any possible error is handled you could change your middleware to something like:
app.use('/api', function (req, res, next){
var token = req.headers.authorization;
users.getByToken(token, function (err, user){
if (err){
res.status(500).end('Something went wrong :('); //token could be valid but you have lost your connection to DB or any other error
}else if (user == null){
res.status(401).end('Unauthorized');
}
else {
app.locals.user = user;
next();
}
});
});

It looks to be right. Nothing wrong in it. I would simplify the code or rather make the middleware separate from the routes in the following manner:
app.use('/api',
auth.checkToken,
auth.processUser //The function that would do something with the user returned from the middleware if there are no errors
);
and in another file (where ever you would want all the middleware related to auth to be, say auth/middleware.js) :
module.exports.getByToken = function (req, res, next)
{ var token_value = req.headers.authorization;
mongoose.model('tokens').findOne({ value: token_value}).exec(function (err, token)
{
if (err)
{
var error = new Error('couldn\'t find user of the given token');
//Log the error, if required
return res.status(500).send()
}
else if(token === null || !token) {
var error = new Error('couldn\'t find user of the given token');
//Log the error, if required
return res.status(404).send(error);
}
else
{ //here next refers to a function that accepts error and user as arguments and does some processing.
mongoose.model('users').find({ _id: token.user }).exec(next);
}
});
};

Related

Node js Error Handler Doesnt get exact error message from Controller Express/Mongoose

I a trying to implement a rest API for our project then I go for node js and express. I have built all the models and controllers. I faced an issue while trying to handle an error. Errorhandler function doesn't receive all the properties of error that caught in try/catch block. I can not read its name in a handler but I can use its name in the controller. Could you please help me?
const errorHandler = (err, req, res, next) => {
console.log(`Error in method:${req.method}: ${err.stack}`.bgRed);
let error = { ...err };
console.log(`Error handler: ${err.name}`);
res.status(error.statusCode || 500).json({
success: false,
data: error.message || 'Server Error',
});
};
module.exports = errorHandler;
controller
const mongoose = require('mongoose');
const Product = require('../models/Product');
const ErrorResponse = require('../utils/error');
const routeName = 'PRODUCT';
// #desc getting single product via id
// #route GET api/v1/products
// #acces public
exports.getProdcut = async (req, res, next) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return next(
new ErrorResponse(`Product not found with id:${req.params.id}`, 404)
);
}
res.status(200).json({
success: true,
data: product,
});
} catch (err) {
console.log(err.name);
console.log('ERRO APPEND');
next(new ErrorResponse(`Product not found with id:${req.params.id}`, 404));
}
};
Assuming that errorHandler is part of your middleware that is somewhere after getProdcut, you can try just throwing the error and Express will automatically detect that for you, because error handling middleware such as yours accepts 4 parameters. So the following would work:
const getProdcut = async (req, res, next) => {
try {
// ...
} catch (err) {
throw err;
}
};
const errorHandler = (err, req, res, next) => {
if (err) {
console.log('hello from the error middleware');
console.log(err.name);
}
else {
// next() or some other logic here
}
}
app.use('/yourRoute', getProdcut, errorHandler);
And inside of your errorHandler you should have access to the error object.
Error-handling middleware always takes four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the next object, you must specify it to maintain the signature. Otherwise, the next object will be interpreted as regular middleware and will fail to handle errors.
https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling

Include db access in my middleware.js file

I need to add a new duplicate email control process in my middleware.js file.
This is my middleware.js node file :
module.exports = {
requiresLoggedIn(req, res, next) {
if (!req.session.loggedIn) {
console.log(" FORBIDDEN CAUSE YOU ARE NOT LOGGED IN ")
res.status(403).send({
errorCode: "403"
})
return
} else {
next() // continue the process
}
},
permission_valid(permission) {
return function(req, res, next) {
if (!req.session.user.permissions.includes(permission)) {
console.log(" FORBIDDEN CAUSE THE PERMISSION IS MISSING ")
res.status(403).send({
errorCode: "403"
})
return
} else {
next() // continue the process
}
}
},
duplicate_email(db, email) {
db.collection("users").findOne({
'email': email
}, function(findErr, result) {
if (!result) {
// next() // continue the process
return false;
} else {
return true;
}
});
}
}
duplicate_email() is not working cause I have no db.collection access in my middleware.js file, I have tried async await, I have tried plenty of things.
I have tried out to change my middleware to this format :
module.exports = function(app, db) {
function requiresLoggedIn(req, res, next) {
if (!req.session.loggedIn) {
console.log(" FORBIDDEN CAUSE YOU ARE NOT LOGGED IN ")
res.status(403).send({
errorCode: "403"
})
return
} else {
next() // continue the process
}
}
}
But as long as I do that, requiresLoggedIn(req, res, next) and permission_valid(permission) won't work any more in the web services, don't know why:
This is one of my working web services, how could I add the eMail duplicate control middleware to them , please? :
app.post("/createUser", middleware.requiresLoggedIn, middleware.permission_valid("CREATE_USER"), function(req, res) {
This is what I should have, but it doesn't work :
app.post("/createUser", middleware.requiresLoggedIn, middleware.permission_valid("CREATE_USER"),middleware.duplicate_email(db, req.body.email), function(req, res) {
I would appreciate if you could help me a few, thank you.
I've solved similar issue by attaching the db object on the base request which all the requests are inherits from.
import express from 'express';
express.request.db = db;
// ------^ this is the base request which all requests inherits from.
Then your db will be available in each request by accessing request.db in your middleware / controller

Can't set headers after they are sent : multiple middleware executions concurency

In my main express.js config file, I use two custom error middleware functions:
const error = require('../middlewares/error');
// catch 404 and forward to error handler
app.use(error.notFound);
// if error is not an instanceOf APIError, convert it.
app.use(error.converter);
I use boom to unify error messages. this is my error middleware:
module.exports = {
/**
* Error responder. Send stacktrace only during development
* #public
*/
responder: (err, req, res, next) => {
res.status(err.output.payload.statusCode);
res.json(err.output.payload);
res.end();
},
/**
* If error is not a Boom error, convert it.
* #public
*/
converter: (err, req, res, next) => {
if (env !== 'development') {
delete err.stack;
}
if (err.isBoom) {
return module.exports.responder(err, req, res);
}
if (err.name === 'MongoError' && err.code === 11000) {
const boomedError = boom.conflict('This email already exists');
boomedError.output.payload.stack = err ? err.stack : undefined;
return module.exports.responder(boomedError, req, res);
}
const boomedError = boom.boomify(err, { statusCode: 422 });
return module.exports.responder(boomedError, req, res);
},
/**
* Catch 404 and forward to error responder
* #public
*/
notFound: (req, res) => {
const err = boom.notFound('Not Found');
return module.exports.responder(err, req, res);
},
};
My problem is, when I make a "register" action with an existing email, the responder() is executed three times. One for my boom.conflict error, but then also one for "not found". (even though I've done res.end().
This is the register logic:
exports.register = async (req, res, next) => {
try {
validationResult(req).throw();
const user = new User(req.body);
const token = generateTokenResponse(user, user.token());
const userTransformed = user.transform();
user.tokens.push({ kind: 'jwt', token });
user.activationId = uuidv1();
await user.save();
res.status(httpStatus.CREATED);
sendValidationEmail(user.activationId);
return res.json({ user: userTransformed });
} catch (err) {
return next(converter(err, req, res, next));
}
};
user.save() triggers this by the way:
userSchema.pre('save', async function save(next) {
try {
if (!this.isModified('password')) return next();
const rounds = env === 'test' ? 1 : 10;
const hash = await bcrypt.hash(this.password, rounds);
this.password = hash;
return next();
} catch (err) {
return next(converter(err));
}
});
Calling res.end() just tells the response stream that you're done sending data. You don't need that in this case because calling res.json() will do it for you.
However, that isn't the same as telling Express that you're done with handling the request. Just because you've sent a response doesn't necessarily mean you've no work left to do.
The way you tell Express that you've finished is by not calling next(). Express assumes you've finished by default and will only continue executing the middleware/routing chain if you call next().
So this line:
return next(converter(err, req, res, next));
should just be:
converter(err, req, res, next);
Likewise your other call to converter() shouldn't be calling next() either.

Node/Express function and callback are not breaking with return

I am creating a 'refresh data' function in Node and I cannot figure out where to place the callbacks and returns. The function continues to run. Below is a list of things the function should do. Could someone help out?
Check if a user has an api id in the local MongoDB
Call REST api with POST to receive token
Store token results in a MongoDB
Terminate function
./routes/index.js
router.post('/refresh', function(req, res) {
var refresh = require('../api/refresh');
refresh(req, function() { return console.log('Done'); });
});
../api/refresh.js
var callToken = require('./calltoken');
var User = require('../models/user'); // Mongoose Schema
module.exports = function(req, callback) {
User.findOne( {'username':req.body.username}, function(err, user) {
if(err) { console.log(err) }
if (user.api_id == 0) {
callToken.postToken(req.body.username, callback);
} else { // Do something else }
});
};
./calltoken.js
var request = require('request');
var Token = require('../models/token'); // Mongoose Schema
module.exports = {
postToken: function(user, callback) {
var send = {method:'POST', url:'address', formData:{name:user} };
request(send, function(err, res, body) {
if(err) { console.log(err) }
if (res.statusCode == 201) {
var newToken = new Token();
newToken.token = JSON.parse(body).access_token['token'];
newToken.save(function(err) {
if(err) { console.log(err) }
return callback();
});
}
});
}
};
I'm not an expert in Express but everywhere in you code in lines with if(err) { console.log(err) } you should stop execution (maybe of course not - up to you app) and return 400 or 500 to client. So it can be something like
if(err) {
console.log(err);
return callback(err); // NOTICE return here
}
On successful execution you should call return callback(null, result). Notice null as a first argument - it is according nodejs convention (error always goes as first argument).

Node.js Express Middleware Error Chain

I have to scrape a web page for a key to use later as a cookie. That part works. But because the request is async the error is not handled. I want to make sure the error is passed along the middleware chain. But I can't get my brain around this one. Thanks for helping.
app.use('/', makeLoginCookie, function (req, res, next){
console.log('My app.use() starts here.');
//console.log('makeLoginCookie: %s', err);
next();
});
And here's the function
function makeLoginCookie(req, res, next) {
httpOptions = {
url: site.url,
headers: {
Cookie: null
}
}
// Get HIDDEN key, build login cookie
request(httpOptions, function(error, response, body) {
if (!error && response.statusCode == 200) {
//console.log(body)
var match = body.match( /\<INPUT TYPE=HIDDEN id=\"key\", value=\"([0-9a-f]+)\"\>/i );
var key = match[1]
var encrypted = sha1(key + site.username + site.password);
loginCookie = "username=" + key + ";password=" + encrypted;
next();
} else {
console.log("Connect failed %s" , error);
//err = new Error("Can't connect");
next();
}
});
};
Refer to Express Error handling, you can use next(err); to pass error in Express. Here is one good link.
I would use promises (Q library) in order to resolve this, and for another things too, specially for web scraping. Your "makeLoginCookie" function could return a deferred.promise and, when the request fails, reject it with the error.
Edit 1: I recommend this great video that explains how to work properly with async code https://www.youtube.com/watch?v=obaSQBBWZLk. It could help you with this and another stuff.
Edit 2: Using promises would be like this, see if it helps you:
var Q = require("q");
app.use('/', function (req, res, next) {
// This part, we will call your function "makeLoginCookie"
makeLoginCookie().then(function(){
// This is called when your function RESOLVES the promise
// Here you could log or do some stuff...
// Then, go next...
next();
}, function(error){
// This is called when your function REJECTS the promise
console.log("Connect failed %s" , error);
// And then, handle the error, like stopping the request and sending an error:
res.status(400).json({errorMessage: error});
})
}, function (req, res, next){
console.log('My app.use() starts here.');
next();
});
// Removed parameters from function
function makeLoginCookie() {
// This is the object that will return a promise
var deferred = Q.defer();
httpOptions = {
url: site.url,
headers: {
Cookie: null
}
}
// Get HIDDEN key, build login cookie
request(httpOptions, function(error, response, body) {
if (!error && response.statusCode == 200) {
//console.log(body)
var match = body.match( /\<INPUT TYPE=HIDDEN id=\"key\", value=\"([0-9a-f]+)\"\>/i );
var key = match[1]
var encrypted = sha1(key + site.username + site.password);
loginCookie = "username=" + key + ";password=" + encrypted;
// Instead of using next(), RESOLVE your promise
// next();
deferred.resolve(); // You can pass some data into it..
} else {
// Instead of using next(), REJECT your promise
// next();
deferred.reject(error); // You can pass some data into it, like an error object or string...
}
});
// Promise that something will be fulfilled or reject later
return deferred.promise;
};
There must have been some error elsewhere in my code because this works as expected now.
} else {
console.log("Connect failed %s" , error);
err = new Error("Can't connect");
next(err);
}

Resources