Express : Call to next in error handler - javascript

I am implementing a node + express js app, and I am having issues calling to the next function into the error handler.
I have a render middleware which is called by next in each each controler, and I would like it to be the same with my error handler. What I do in controler is putting some viewProperties in the reqand then call the next middleware which retrieve these properties and render the response consequently.
function render(req, res, next) {
// viewName, title, args
var properties = req.viewProperties || {};
// We only handle a res.send(message)
if (properties.body) {
res.send(properties.body);
return;
}
// We only handle a res.redirect(url)
if (properties.redirect) {
res.redirect(properties.redirect);
return;
}
properties.lang = req.app.get('lang');
properties.title = properties.title || 'Message_Me';
properties.connected = req.session ? req.session.connected : false;
properties.firstname = req.session.userFirstname || 'anonymous';
res.render(properties.name, properties);
}
When I try using this middleware with my error handler, using next() the request is just pending on client side, and never recieved.
So I try to create the same middleware as an error handler : The same function but with an arity of 4 and then call next(err) in my error handler. This time the response is revieved client side but it is not rendered properly, it only shows up the stack trace.
The only way I found, it to copy this function in my error handler and paste it instead of calling to next. I don't understand why it can't work properly ?
my error handler :
function redirectError(err, req, res, next) {
// Ajax call running
if (req.xhr) {
req.viewProperties = { body : err.message };
return next(err);
}
req.viewProperties = { name : 'layout/error', title : 'Erreur', message : err.message, err : err };
// Here is the probleme
next()
// next(err);
}
EDIT
I tried another thing : I copied the render method into my error module as a simple function (not declared middleware). And then call to it instead of next in the redirectError error handler. that did the same behaviour. the function is called BUT nothing is recied on client side.
WHEREAS
If I copy the content of the render function INTO the redirectError everything works fine.
There really is something I don't understand here. It may be a deeper problem I have not yet noticed...
Riddles In The Dark
EDIT N2
I figured out my mistake !! I forgot a return statement in a if of another middleware. That made the nextbeing called twice, and a very bad bahaviour...
As a conclusion, a good practice to adopt, is to always use return when calling next !
And thank's to laggingreflex which made me carry on.

If there's an Error present (either thrown or passed through next) then only the next middleware which can handle the error (the one defined with arity of (err,req,res,next)) is called.
Conversely, if there isn't an Error present then the error handler middleware (err,req,res,next) is not called.
So in your case, your redirectError will only be called if there is an Error present, and your render only when there isn't.
To demonstrate:
app.use(function(req, res, next) {
throw(new Error('testing...'));
});
app.use(function(req, res, next) {
// This won't be called
});
app.use(function(err, req, res, next) {
// But This would
next(); // not passing any Error this time
});
app.use(function(err, req, res, next) {
// So now this won’t be called
});
app.use(function(req, res, next) {
// But this would
});

Related

How to add an error handler in NodeJS after all routes defined by user?

I am writing a library that requires the user to handle errors generated by my library. The way I do it now is that I tell users to add an error handler after all their routes like so:
const app = express();
app.use(MyLibrary.init())
// <user's routes here>
app.use(MyLibrary.errorHandler())
app.use((err, req, res, next) => {
// user's generic error handler
})
The init function in my lib is as follows:
function init() {
return async (request, response, next) => {
try {
// some logic here that modifies the request object and can throw an error
return next(); // this can also throw an error, or generate an unhandled rejection / error
} catch (err) {
next(err);
}
};
}
The errorHandler function is as follows:
function errorHandler() {
return async (err, request, response, next) => {
try {
if (/*err from MyLibrary*/) {
// do something and send a response
return;
}
next(err);
} catch (err) {
next(err);
}
};
}
My aim is to make it so that the user doesn't need to add app.use(MyLibrary.errorHandler()).
One solution that comes to mind is that in the init function, instead of calling next(err), I can handle my library's error directly. However, errors from MyLibrary can also be generated in any of the API handlers that the user writes (since they can interact with my library via the modified request object). These API handlers may be async.
Is there any solution to this issue?
Thank you

Node.js express - get route params without going into the route function for quick analytics tracking

I aim to send an analytics event for each route in my API. As opposed to saving an event with the entire full route that was called, I want to save the base url as the event and the parameters of the route as variables of the event. For example, when saving an analytics event...
Not this:
{
event_title: "API User Event"
category: "domain.com/api/user_routes/route_1/value_of_param_one"
}
But this:
{
event_title: "API User Event"
category: "domain.com/api/user_routes/route_1"
params: {
param_one: "value_of_param_one"
}
}
I'd like to have a global function that gets the parameters from the request variable, however, if you do this on a higher level (not route level)
app.use('/api/user_routes/*', myWrapperFunction)
myWrapperFunction will detect anything after /api/user_routes as parameters. From my experiments, I was only able to successfully detect the actual parameters inside a specific route function. However, that approach requires me to either edit each route function or wrap it like so...
router.get('/route_1/:param_one', (req, res) => Utility.analyticsEvent(userController.routeOneFunction, req, res));
router.get('/route_2/:param_one', (req, res) => Utility.analyticsEvent(userController.routeTwoFunction, req, res));
router.get('/route_3/:param_one', (req, res) => Utility.analyticsEvent(userController.routeThreeFunction, req, res));
Is there a way to detect the actual parameters of the route without actually going into the function itself? Or is this a limitation on express because it won't know the specifics of the route until it finds the first matching option traversing down the routes?
Edit If there is no way to know the parameters before express matches the specific route: is there a function that you can run before executing the route function that will tell you which route will be matched and will specify the parameters?
Welcome all comments!
I think one approach is to write a middleware like below.
// This will get executed before every request. As we'll add this with app.use() with top level middlewares
function customMiddleware (req, res, next) {
let url = req.baseUrl;
// some if else logic to re-route
if( url.includes('/api/user_routes')) {
let urlSplit = url.split();
if( url[urlSplit.length() - 1] == 'param_one' ) {
res.redirect('/api/user_routes/route_1')
}
else if(url[urlSplit.length() - 1] == 'param_tow' ) {
res.redirect('/api/user_routes/route_1')
}
// and so on
} else {
// executes other middleware or go to matching route
next()
}
}
app.use(customMiddleware)
Found a way to do it after the call is made by overwriting the response.json function
app.use(function (req, res, next) {
var json = res.json;
res.json = function (body) {
// Send analytics event before returning response
try {
// Routes that don't need to be tracked with analytics
let notIncludeRoutes = [
"some_not_tracked_route"
];
let route = req.baseUrl + req.route.path;
if(notIncludeRoutes.indexOf(route) === -1) {
// Track route and params
let route_params = res.req.params;
Utility.analyticsEvent(route, route_params);
}
} catch(error) {
// Don't block call if there was an error with analytics, but do log it
console.log("ANALYTICS ERROR: ", error);
}
json.call(this, body);
};
next();
});

Using Express module as middleware

I'm new to Express and trying to use middleware to handle a POST request. If I expose the endpoint, and make a request to the API, everything works fine.
Working Correctly
api/index.js
app.post('/api/endpoint', (req, res, next) => {
next();
});
server.js
app.use(function() {
console.log('hello'); // => hello
});
But when I try to replace the middleware function with a module that exports a function, the function never gets invoked.
Not Working
api/index.js
app.post('/api/endpoint', (req, res, next) => {
next();
});
server.js
const makeExternalRequest = require('./server/makeExternalRequest');
...
console.log(makeExternalRequest, typeof makeExternalRequest);
// => [Function] 'function'
app.use(makeExternalRequest);
server/makeExternalRequest.js
module.exports = function(err, req, res, next) {
console.log('hello', err);
}
The function in server/makeExternalRequest.js is never invoked, and nothing logs... Am I using app.use(...) incorrectly?
Express middleware requires three arguments, the third of which is a function you call when you're done to move the request along to the next handler:
module.exports = function (req, res, next) {
console.log('hello');
next();
};
Without calling the third parameter, your request will just remain pending and a response will never be sent. Also, be sure you call app.use before any handler that would return the response. If the response is sent first, then your middleware will never be reached.

How to throw a 404 error in express.js?

In app.js, I have
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
so if I request some not exist url like http://localhost/notfound, above code will execute.
In exist url like http://localhost/posts/:postId, I would like to throw 404 error when access some not exist postId or deleted postId.
Posts.findOne({_id: req.params.id, deleted: false}).exec()
.then(function(post) {
if(!post) {
// How to throw a 404 error, so code can jump to above 404 catch?
}
In Express, a 404 isn't classed as an 'error', so to speak - the reasoning behind this is that a 404 isn't usually a sign that's something's gone wrong, it's just that the server couldn't find anything. Your best bet is to explicitly send a 404 in your route handler:
Posts.findOne({_id: req.params.id, deleted: false}).exec()
.then(function(post) {
if(!post) {
res.status(404).send("Not found.");
}
Or alternatively, if this feels like too much repeated code, you could always pull that code out into a function:
function notFound(res) {
res.status(404).send("Not found.");
}
Posts.findOne({_id: req.params.id, deleted: false}).exec()
.then(function(post) {
if(!post) {
notFound(res);
}
I wouldn't recommend using a middleware in this situation solely because I feel like it makes the code less clear - the 404 is the direct result of the database code not finding anything, so it makes sense to have the response in the route handler.
I have the same app.js structure, and I solved this problem in this way in the route handler:
router.get('/something/:postId', function(req, res, next){
// ...
if (!post){
next();
return;
}
res.send('Post exists!'); // display post somehow
});
The next() function will call the next middleware which is the error404 handler if it is right after your routes in the app.js.
You can use this and the end of your routers.
app.use('/', my_router);
....
app.use('/', my_router);
app.use(function(req, res, next) {
res.status(404).render('error/404.html');
});
you're probably looking for something like https://github.com/expressjs/api-error-handler
or just https://github.com/jshttp/http-errors
Even though 404 pages are not considered an error in Express as written here, its really damn handy if you DO handle them like so. For instance when you are developing an API that wants consistent JSON output. The following code should help you with that:
Define a helper function abort to create status errors that can be easily used in your code to pass to the next function:
// Use the `statuses` package which is also a dependency of Express.
const status = require('statuses');
const abort = (code) => {
const err = new Error(status[code]);
const err.status = code;
return err;
};
Define the catch-all middleware for 404 pages which should be defined at the bottom of your stack (after all routes have been added). This forwards the 404 as an error:
app.use((req, res, next) => {
next(abort(404));
});
Lastly, the final error handler will now consistently send all errors in JSON format:
app.use((err, req, res, next) => {
if(!res.headersSent) {
// You can define production mode here so that the stack trace will not be sent.
const isProd = false;
res.status(err.status || 500).json({
error: err.toString(),
...(!isProd && {stack: err.stack.split('\n').map(i => i.trim())}),
});
}
next(err);
});

How to properly handle errors in Express?

I am beginning to work with Express JS and have run into an issue. I can't seem to figure out the proper way to handle errors.
For example, I have a web services API that serves an object called "event". I'd like to return a simple string of "cannot find event" when a user submits an event id that isn't found. Here is how I'm currently structuring my code:
app.get('/event/:id', function(req, res, next) {
if (req.params.id != 1) {
next(new Error('cannot find event ' + req.params.id));
}
req.send('event found!');
});
When I submit an id other than 1, Node crashes with the following output:
http.js:527
throw new Error("Can't set headers after they are sent.");
^
Error: Can't set headers after they are sent.
at ServerResponse.<anonymous> (http.js:527:11)
at ServerResponse.setHeader (/usr/local/kayak/node_modules/express/node_modules/connect/lib/patch.js:62:20)
at /usr/local/kayak/node_modules/express/node_modules/connect/lib/middleware/errorHandler.js:72:19
at [object Object].<anonymous> (fs.js:107:5)
at [object Object].emit (events.js:61:17)
at afterRead (fs.js:878:12)
at wrapper (fs.js:245:17)
From what I can tell by using the node.js debugger, execution of the block of code continues after next() is called, meaning that req.send('event found!') tries to run. I don't want this to happen.
The only workaround that I've found is to simply throw a new Error() instead of "next-ing" it, but this results in a default Express HTML error page being generated. I'd like a little more control than that.
I have taken the time to read over the error handling section of the Express documentation, but I couldn't make sense of it.
You'll want to check out Express Error Handling. From there:
app.param('userId', function(req, res, next, id) {
User.get(id, function(err, user) {
if (err) return next(err);
if (!user) return next(new Error('failed to find user'));
req.user = user;
next();
});
});
The sweetspot that you are missing is the return next(...)
That's because you're doing it wrong: you already threw an Error (which will be processed by Express and return a 500 - Error page for the user or something like that) but you are also trying to send your own response to the client: res.send('event found!');
You should really check out the Express guide about Error Handling here: http://expressjs.com/guide/error-handling.html
What I would do in your example is:
function NotFound(msg){
this.name = 'NotFound';
Error.call(this, msg);
Error.captureStackTrace(this, arguments.callee);
}
app.get('/event/:id', function(req, res, next){
if (req.params.id != 1) {
throw new NotFound('Cannot find event ' + req.params.id);
} else {
res.send('event found!');
}
});
app.error(function(err, req, res, next){
if (err instanceof NotFound) {
res.render('404.ejs');
} else {
next(err);
}
});
You have a couple of problems in your code:
When responding to the client, you need to use the response object (res rather than req).
When sending an error to next, you should return, so the rest of the function doesn't run.
Here's your code after fixing those errors:
app.get('/event/:id', function(req, res, next) {
if (req.params.id != 1) {
return next(new Error('cannot find event ' + req.params.id));
}
res.send('event found!'); // use res.send (NOT req.send)
});

Categories