Express custom error handler is not getting called - node.js

I'm trying to use a custom error handler for my express application, but it is not getting called.
I have added an error handler at the bottom of index.js file
app.use("/api/auth", authRoutes);
app.use(errorHandler);
app.listen(process.env.PORT, () => {
console.log("Server stared sucessfully at port " + process.env.PORT);
});
errorHandler.js
const errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode ? res.statusCode : 500;
res.status(statusCode).json({
message: err.message,
});
next();
};
export default errorHandler;
In the controllers directory, postController.js
export const getPost = (req, res) => {
const q =
"SELECT posts.id, `username`, `title`, `desc`, posts.img, users.img AS userImg, `category`, `date` FROM users JOIN posts on users.id = posts.uid WHERE posts.id = ?";
db.query(q, [req.params.id], (err, data) => {
if (err) {
res.status(500);
throw new Error("Something went wrong, please try again.");
}
// Error handler is not catching this error and my application crashes every time.
if (data.length == 0) throw new Error("Invalid post id");
return res.status(200).json(data[0]);
});
};
Console
Server stared sucessfully at port 8080
file:///E:/Programming/Web/Blog/backend/controllers/postController.js:39
if (data.length == 0) throw new Error("Invalid post id");
^
Error: Invalid post id
at Query.onResult (file:///E:/Programming/Web/Blog/backend/controllers/postController.js:39:37)
at E:\Programming\Web\Blog\backend\node_modules\mysql2\lib\commands\query.js:86:16
at processTicksAndRejections (internal/process/task_queues.js:77:11)
[nodemon] app crashed - waiting for file changes before starting...
Edit:
If I throw an error outside the callback function, it is getting handled by the handler. I think it is happening because before the error is handled by handler, it is handled by the MySQL library. Please correct me Im wrong.

Your custom error handler must be established after all the application stack (after the app.listen call).
Source: Express 4 middleware error handler not being called

You can try this one:
export const getPost = (req, res, next) => {
const q =
"SELECT posts.id, `username`, `title`, `desc`, posts.img, users.img AS userImg, `category`, `date` FROM users JOIN posts on users.id = posts.uid WHERE posts.id = ?";
db.query(q, [req.params.id], (err, data) => {
if (err) {
// Pass the error down to the error handler middleware
return next(new Error("Something went wrong, please try again."));
}
if (data.length == 0) {
// Pass the error down to the error handler middleware
return next(new Error("Invalid post id"));
}
return res.status(200).json(data[0]);
});
};

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

ValidationError not caught by express error handler

In my mongoose schema, I have a field type that is required. I am using a custom error handler in express defined as
const notFound = (req, res, next) => {
const error = new Error(`Not found-${req.originalUrl}`);
res.status(404);
next(error);
};
const errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
res.status(statusCode);
res.json({
message: err.message,
stack: process.env.NODE_ENV === 'production' ? null : err.stack,
});
};
and use the error handlers at the bottom of my server.js file as
app.use(notFound);
app.use(errorHandler);
However, when I try to test the route that posts an entry using Postman, the request will be stuck and no response is sent back, and in the terminal there is an error saying that UnhandledPromiseRejectionWarning: ValidationError: ...
My question is: shouldn't my custom error handler catch the error?
Make sure that you're forwarding the error wherever you're performing your mongoose action. There you should be able to forward that to your middleware.
For example you can try something like this:
example.findById(req.id, async function(err, foundRecord){
if(err) {
next(err);
} else {
....
}
});
Let me know if that works.
You can try with this simple HttpError:
Create HttpError.js:
class HttpError extends Error {
constructor(statusCode, message = 'Internal Server Error') {
super(message);
this.statusCode = statusCode;
this.isSuccess = false;
this.isError = true;
this.errorMessage = message;
this.data = null;
}
}
module.exports.HttpError = HttpError;
You can create for an example: NotFoundError extends HttpError :
const { HttpError } = require('../HttpError');
class NotFoundError extends HttpError {
constructor(message = 'Not Found') {
super(404, message);
}
}
module.exports.NotFoundError = NotFoundError;
Now, in your app.js, you can handle error like this code below:
// handle notFoundError
app.use((req, res, next) => { throw new NotFoundError() });
// handle unexpectedly error or another error
app.use((error, req, res, next) => {
res.status(error.statusCode || 500).send(error);
})
If you want to look at the tutorial, for mys, but use the indonesian language ("But you don't have to listen, just look at the concept"), you can check it out here:
Handle Express Error: https://www.youtube.com/watch?v=GwS6KJmO9w8&list=PLREvIK3N7Ga6F669gbDCDMwrn37Uq32-O&index=13
Handle HttpError: https://www.youtube.com/watch?v=VoorNGvDypE&list=PLREvIK3N7Ga6F669gbDCDMwrn37Uq32-O&index=14
Or for example code, you can check it out on this github repo: https://github.com/12bedeveloper/basic-express
Keep learn.

How to properly log erros in my application using ExpressJS

I am part of a project which uses nodeJS + ExpressJS for the backend application, and We have a middleware function to log accesses on routes in the database.
When an User tries to access the /user route with a post method, a middleware receives the Request, get information like the URL, ip address, origin, a description of the event and record it in the database.
Everything works just fine, but some of my teammates were discussing about how to log the erros also in the database.
I will put bellow a code example
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
response.status(500).json({ error });
}
};
When we were discussing about how to implement it, we realized we'd have to call a log error function in a lot of places since we have many flows which leads to an error response.
So the code would be just like:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
function() // here we would log the error
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // here we would log the error
response.status(500).json({ error });
}
};
is it a properly way of dealing with error logging or is there any better way of doing it? Thank you for reading!
You can use the built-in error handler provided by Express.JS for this kind of logic, of course it requires a bit of setup. Like most things in Express.JS, the error handler it's just a middleware function with four parameters err, req, res and next, which MUST be placed after all your other middlewares. It comes to play when, inside a router handle (for example), your call next(err) (where err it's an Error) or by simply throwing err. Check out the documentation for more.
app.use(...)
app.use(...)
app.use((req, res, next) => {
if (req.params.id === undefined) {
let error = new Error("ID required.")
error.statusCode = 400
error.statusMessage = "Request not valid, ID not found."
throw error;
} else {
// Do some stuff...
}
})
// NOTE: After ALL your other middlewares
app.use((err, req, res, next) => {
console.error(err)
res
.status(err.statusCode)
.json(err.statusMessage)
})
Ideally you should log the errors only inside the catch block. Whenever you encounter an error just throw a new error by calling throw new Error("Type your error message here"). Then your function inside catch block will log and handle the error appropriately.
I would change your code to this:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
throw new Error("E-mail already in use")
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // log your error
response.status(500).json({ error.message });
}
};
Read more about Errors here.

Express central error handling with app.use

Hey guys I want to achieve central error handling in express, I've done this.
app.use(function(err,req,res,next){
logger.error(err);
utils.jsonOutHandler(err,null,res);
next(err);
});
app.get('/',(req,res)=>{
throw new Error('Testing');
});
Also I made a special jsonOutHandler method which sends proper response to the user.
function jsonOutHandler(err, result, out) {
if (err) {
let status = 500;
const message = 'Something broke';
if (err instanceof DbError) {
status = 500;
}
if (err instanceof ValidationError) {
status = 400;
}
if (err instanceof SystemError) {
status = 500;
}
out.status(status).send({message});
return;
}
out.status(result.status).send({data: result.data});
}
But whenever I throw error on '/' route my error handler is never triggered. Why?
Express is based on middlewares, so, if you wanna catch the errors inside the middleware, you should call the error middleware:
app.get('/',(req,res)=>{
next(new Error('Testing'));
});
/**
* middleware to catch errors happened before this middleware
* express knows this middleware is for error handling because it has
* four parameters (err, req, res, next)
**/
app.use((err, req, res, next) => {
res.status(500).send({
message: err.message,
});
});
I hope you can adapt this example to your requirements. The thing to keep in mind is an error middleware can be used from previous middlewares. In your example you couldn't catch the error because your middleware was defined before your main router app.get('/')

Is there a way to test error handling in ExpressJS with Mocha when using a custom error handler?

Test
it('should fail trying to GET bookmarks with false user id',async () => {
try {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
} catch (e) {
console.log(e); //it doesn't reach this point
expect(e.httpStatus).to.equal(HttpStatus.UNAUTHORIZED);
}
});
The relevant part of the method under test:
/* GET bookmark of user */
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
userIdTokenValidator.validateUserIdInToken(request);
...
}));
where wrapAsync makes sure the error is passed to the custom error handler:
let wrapAsync = function (fn) {
return function(req, res, next) {
// Make sure to `.catch()` any errors and pass them along to the `next()`
// middleware in the chain, in this case the error handler.
fn(req, res, next).catch(next);
};
}
The validateUserIdInToken method which causes the method under test to throw an exception:
const AppError = require('../models/error');
const HttpStatus = require('http-status-codes');
let validateUserIdInToken = function (request) {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
throw new AppError(HttpStatus.UNAUTHORIZED, 'Unauthorized', ['the userId does not match the subject in the access token']);
}
}
module.exports.validateUserIdInToken = validateUserIdInToken;
and the custom error handler in the root middleware:
app.use(function(err, req, res, next) {
if (res.headersSent) {
return next(err)
}
if(err instanceof AppError) { //execution lands here as expected and the test stops...
res.status(err.httpStatus);
return res.send(err);
} else {
res.status(err.status || HttpStatus.INTERNAL_SERVER_ERROR);
res.send({
message: err.message,
error: {}
});
}
});
I think you may be approaching this incorrectly. Invalid auth should not raise errors in the app - it's not an error really, is a validation issue.
If the auth fails, simply send the relevant http error code - 401 back to the client.
res.send(HttpStatus.UNAUTHORIZED, 'a message if you want'); // 401
In your route handler:
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
return response.send(HttpStatus.UNAUTHORIZED);
}
...
}));
In your test, check the for status 401:
chai.request(server)
.get('/false_user_id/bookmarks')
.end((err, result) => {
if (err) {
return callback(err);
}
result.should.have.status(401);
});
Thanks to #laggingreflex's comment I missed debugging that the response actually returned with the expected status and error message
The adjusted test case now looks like this:
it('should fail trying to GET bookmarks with false user id',async () => {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
expect(response.status).to.equal(HttpStatus.UNAUTHORIZED);
});

Resources