Here is the background of the question : I'm following the kick-off-koa using Koa 2. But the exercises in the kick-off are designed for Koa 1. I've created an issue for this problem of Koa 2 : Task of error handler with Koa 2 cannot pass.
For short, my problem is how to display a custom error page when a 500 error happens.
Here are the codes :
// error handler middleware
function errorHandler(ctx, next) {
try {
return next();
}
catch(err) {
ctx.status = err.status || 500;
// I would like to display the custom message as follows
ctx.body = 'Oops! internal server error';
// with emitting the error event, don't work
// ctx.app.emit('error', err, ctx);
}
}
// to generate error
app.use(router.get('/error', ctx => {
ctx.throw('oops', 500);
}));
But my page of error is always displaying as "Internal Server Error", which is the default message. It seems that ctx.body = 'Oops! internal server error'; couldn't modify the page.
Thanks for the helps!
If you are using Koa2, you don't have to return inside middleware, instead, use await. And by the way, your middleware function MUST be an async function.
Here is an example of a combined 404 and 500 middleware:
app.use(async (ctx, next) => {
try {
await next()
if (ctx.status === 404) ctx.throw(404)
} catch (err) {
console.error(err)
ctx.status = err.status || 500
ctx.body = errorPage.render({ // Use your render method
error: err,
})
}
})
// Your normal routes here
First, Koa awaits for the next middleware in the chain (which is your normal routes). If nothing is found or an error occurred, the middleware chain goes backwards and the next line is executed, which throws a 404 and its captured inside the catch.
Now in the catch statement, you can get either 404, 500 (by default) or 5xx if other error occurred.
The body of the page is also set with a render of your template and passing the error to the template so you can make use of it.
You don't have to emit the error as this is the last catch in the chain.
Related
I have an Express.js app in which I'm trying to log the validation errors returned by Celebrate to the console so that I can analyze them with the logging service that I use (which is GCP's Cloud Logging).
I'm currently just using the error handling middleware provided by Celebrate as suggested in the documentation:
// app.js
const { errors } = require('celebrate');
...
app.use(errors());
...
How can I extend the middleware (without re-implementing it) so that it also logs the validation errors to the console?
The simplest way to achieve this seems to be by defining another error middleware before the Celebrate error middleware, that checks whether the error is a Celebrate error (using the isCelebrateError method) and if so it logs it to the console:
// app.js
const { errors, isCelebrateError } = require('celebrate');
...
// middleware to log Celebrate validation errors
app.use((err, req, res, next) => {
if (isCelebrateError(err)) {
console.error(err);
}
next(err);
});
// Celebrate middleware to return validation errors
app.use(errors());
...
It is important to include the logging middleware before Celebrate's errors() middleware since errors() returns a JSON response and no other middleware is run after it (you can check out the Celebrate source code for the implementation details of errors()).
Below is the piece of code for my route.patch function that I defined for /:productId
route.patch('/:productId',(req,res,next)=>{
const id = req.params.productId;
const updateOps = {};
console.log(req.body);
for (const ops of req.body) {
updateOps[ops.propName] = ops.value;
console.log(updateOps);
}
res.status(200).json({Message: 'Hi'});
});
console.log(req.body) before the for loop is working but the console.log(updateOps) inside for loop is not working. And the even the res.status(200).json({Message: 'Hi'}); is not working . I am getting a route not found error.
Basically everything before for loop is working and everything after for loop is not working.
Can you please let me know where am I doing wrong. Is it the way I use for loop inside route.patch is wrong? Any help is highly appreciated.
Thanks
Answering your question regarding the error-handling from the comments. You have the following middlewares:
app.use((req, res, next) => {
const error = new Error('Not found');
//console.log(error.status) //console.log(error.message)
error.status = 404; //console.log(error.message)
next(error);
});
app.use((error, req, res, next) => {
res.status(error.status || 500);
res.json({
error: {
message: 'Route not found'
}
})
});
So for every request an error-object will be created which you assign status 404 and pass it on to the next middleware. There you send the error response and since the status is already set to 404 this status is used.
Actually you should just remove the first middleware where you create the error, as this would be done for every request. Express already handles requests to routes that do not exist and will return a 404 not found response. You can keep the second middleware to handle any other unhandled errors though (you should change the error message though :))
Basically, the question is do I need to use next(err) when encountering any errors? The nodejs error documentation says it is fine to use a standard sort of if(err) else... for asynchronous callbacks and EventEmitters, as long as the error isnt handled with a try-catch block for non async-await functions, as it will cause crashing. If I do need to use them, what is to prevent the next() function being called multiple times in the same handler for different asynchronous operations? Wouldnt using the default error handler cause headers to be sent multiple times and cause an error of its own when using event emitters ?
Apologies if the question has been asked, its just I cannot find a specific answer to why usage of express.js error handling is preferred.
If you are asking if you need to use an explicit next(err) in a handler,
e.g.
app.get('/someurl', (req, res, next) => {
//do something - whoops had an error
next(err);
})
No, the above is not required in a handler.
The only time you would need to explicitly wrap or pass on the error is if you have, for example, used a try/catch and are not handling the error condition itself in the handler, i.e. not returning a response in the handler (Not sure why you would want to do that).
What will happen above when an error occurs in the handler, express will continue on through the middlewares until it finds a handler that will deal with your error.
If there are none, it will exit.
So to use a global error handler, you could write your app like the following and not worry about next(err) in each handler function.
app.get('/route/one', async (req, res) => {
// do something that could throw an error
const result = await aFunctionThatCouldThrowAnError();
// No error handling in this function
res.json({ result });
});
app.get('/route/two', (req, res) => {
res.json({ hello: 'world-two' });
});
// A Global Error handler
app.use((err, req, res, next) => {
//handle all errors here
if(err) {
res.status(500).send('some error message')
}
res.status(404).send('not found');
});
Note that the order of middlewares is important, so the global error handler should be applied last.
I'm using promises to wrap asynchronous (Mongo) DB ops at the end of an (expressJS) route.
I want to try and figure out how to test the following code.
userService
userService.findOne = function (id) {
var deferred = q.defer();
User.findOne({"_id" : id})
.exec(function (error, user) {
if (error) {
deferred.reject(error);
} else {
deferred.resolve(user);
}
});
return deferred.promise;
};
userRoute
var user = function (req, res) {
var userId = req.params.id
, userService = req.load("userService");
// custom middleware that enables me to inject mocks
return userService.findOne(id)
.then(function (user) {
console.log("called then");
res.json({
msg: "foo"
});
}).catch(function (error) {
console.log("called catch");
res.json({
error: error
});
}).done();
};
Here's an attempt to test the above with mocha
userTest
it("when resolved", function (done) {
var jsonSpy = sinon.spy(httpMock.res, "json")
, httpMock = require("/path/to/mock/http/object")
, serviceMock = require("/path/to/mock/service"),
, deferred = q.defer()
, findStub = sinon.stub(serviceMock, "findOne")
.returns(deferred.promise)
, loadStub = sinon.stub(httpMock.req, "load")
.returns(serviceMock),
retPromise;
// trigger route
routes.user(httpMock.req, httpMock.res);
// force promise to resolve?
deferred.resolve();
expect(jsonSpy.called).to.be.true; // fails
// chai as promised
retPromise = findStub.returnValues[0];
expect(retPromise).to.be.fulfilled; // passes
});
the http mock is just an empty object with no-ops where expressJS would normally start rendering stuff. I've added some logging inside those no-ops to get an idea on how this is hanging together.
This isn't really working out. I want to verify how the whole is integrated, to establish some sort of regression suite - but I've effectively mocked it to smithereens and I'm just testing my mocks (not entirely successfully at that).
I'm also noticing that the console logs inside my http mocks triggered by then and catch are firing twice - but the jsonSpy that is invoked inside the actual code (verified by logging out the sinon spy within the userRoute code) is not called in test.
Has anyone got some advice on integration testing strategies for express apps backed by Mongo?
It looks to me like you're not giving your promise an opportunity to fire before you check if the result has been called. You need to wait asynchronously for userService.findOne()'s promise chain to complete before jsonSpy.called will be set. Try this instead:
// start of code as normal
q.when(
routes.user(httpMock.req, httpMock.res),
function() { expect(jsonSpy.called).to.be.true; }
);
deferred.resolve();
// rest of code as normal
That should chain off the routes.user() promise and pass as expected.
One word of caution: I'm not familiar with your framework, so I don't know if it will wait patiently for all async events to go off. If it's giving you problems calling back into your defer chain, you may want to try nodeunit instead, which handles async tests very well (IMO).
How can I implement a custom error handler in Express using CSRF middleware after users click the back button in browser and resubmit the form? By default Express return a 403 page with lots of stack traces. I want to replace it by for example redirecting user to a custom error page. How can I do that?
Here are some examples of writing custom error handlers in Express: https://github.com/visionmedia/express/blob/master/examples/error-pages/index.js
Here are the custom error handlers I use: Error handling in an Express route
You might also want to consider modifying connect to return a different code than 403 when CSRF fails. You can change it here: https://github.com/senchalabs/connect/blob/master/lib/middleware/csrf.js#L82
You might choose 428 Precondition Required. The full list is here: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
That way, you could have a special message shown only for CSRF failures.
Like any other well designed middleware csurf passes the error to next. So it's possible to react on the raised error in the following way:
var csurf = require('csurf')();
app.use(function (req, res, next) {
csurf(req, res, function (err) {
if (err) {
// do what ever with err
} else {
next();
}
});
});