I am trying to implement express error handling middleware. It is working but I don't understand why I have to set error.message equal to the err.message after I copy err to error. Is the spread operator not copying all the attributes from err? When I inspect the err object I don't even see a message or a stack attribute. Also, why is it able to log the error message before the error message is created in the if block.
This is how I am extending the Error
// src/utils/errorResponse.js
class ErrorResponse extends Error {
constructor(message, statusCode) {
super(message)
this.statusCode = statusCode
}
}
module.exports = ErrorResponse
Custom error handler middleware
// src/middleware/error.js
const ErrorResponse = require('../utils/errorResponse')
const errorHandler = (err, req, res, next) => {
let error = { ...err }
console.log({ error.message }) // undefined
error.message = err.message
console.log({ error.message }) // `Resource not found with id of ${err.value}`
// Log to console for dev
// console.log(error.stack)
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = `Resource not found with id of ${err.value}`
error = new ErrorResponse(message, 404)
} else {
console.log(error.name)
}
res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Server Error',
})
}
module.exports = errorHandler
I trigger the error by making a get request with a bad ObjectID
// src/controllers/bootcamp.js
const Bootcamp = require('../models/Bootcamp')
...
// #desc Get single bootcamp
// #route GET /api/v1/bootcamps/:id
// #access Public
exports.getBootcamp = async (req, res, next) => {
try {
const bootcamp = await Bootcamp.findById(req.params.id)
if (!bootcamp) {
return next(err)
}
res.status(200).json({ success: true, data: bootcamp })
} catch (err) {
next(err)
}
}
This is happening because the err parameter you're getting is of Error type object and message key is present in prototype of Error object and spread operator shallow copies keys of object ( excluding prototype keys )
Statement for spread operator from mdn -
The Rest/Spread Properties for ECMAScript proposal (ES2018) added spread properties to object literals. It copies own enumerable properties from a provided object onto a new object.
Shallow-cloning (excluding prototype) or merging of objects is now possible using a shorter syntax than Object.assign().
For reference -
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
Related
I tried to insert data as array using node js but then displayed me this error. I added model class .I implemented model class as class components
controller code
const addAnswer = async (req, res, next) => {
const questionName = req.body.questionName;
const answerName = req.body.answerName;
const newDetails = new Details({
//initializing properties
questionName,
answerName
})
newDetails.save().then(() => {
//saving the object to the db
firestore.collection('answersdas').doc().set();
res.status(200).json({ success: true, message: "New answer Added" })//success message
}).catch((error) => {
//error message
res.status(500).json({ success: false, message: "Adding answer failed", error: error.message })
})
}
model
class Answer {
constructor(ans) {
this.ans = ans;
}
module.exports = Answer;
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
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.
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.
I have more of a code architecture question about error handling NodeJs Express apps. I am not sure on what is the best pattern for error handling. On that note, what situations should be considered as an error. For instance, is a 401 Unauthorized code considered an error even though this response is expected when sending bad credentials?
When using:
//app.js file
app.use(err, req, res, next){}
I generally tend to only put 5xx errors here which would represent situations in which a database cannot be found or no network connection issue or function failures. As for the rest, I would manually send back a status code, such as a 401, from the controller by explicitly coding res.status(xxx).send(); or something of the sort. But the issue behind what I'm doing is I tend to repeat myself and have to place logging scattered across the app. Is my approach fine? Should I instead be creating multiple error handling middlewares for different ranges of status codes? I need an opnion
I prefer using middleware with your custom error class to deal with this problem.
Let's see a error class, which contains a custom error message, http status code and logLevel if incase you employ logger.
module.exports = class ApiCalError extends Error {
constructor (message, status, logLevel) {
// Calling parent constructor of base Error class.
super(message);
// Capturing stack trace, excluding constructor call from it.
Error.captureStackTrace(this, this.constructor);
// Saving class name in the property of our custom error as a shortcut.
this.name = this.constructor.name;
// You can use any additional properties you want.
// I'm going to use preferred HTTP status for this error types.
// `500` is the default value if not specified.
this.status = status || 400;
this.logLevel = logLevel || 'warn';
}
toResponseJSON () {
return {
success: false,
message: this.message
}
}
};
Now, let's took a look at a controller. We have only sent successful response from this controller, and passed custom errors to middleware.
exports.Login = function(req, res, next) {
const validationResult = validateLoginForm(req.body)
if (!validationResult.success) {
var err = new customError(validationResult.message, 400, 'warn')
return next(err)
} else {
return passport.authenticate('local-login', (err, token, userData) => {
if (err) {
if (err.name == 'IncorrectCredentialsError' || err.name == 'EmailNotVerified') {
var error = new customError(err.message, 400, 'warn')
return next(error)
}
return next(err)
}
return res.json({
success: true,
message: 'You have successfully logged in!',
token,
user: userData
})
})(req, res, next)
}
}
Now, let's take a look at logger and error handlers middlewares. Here, logger will log the errors in api and pass the error to error handlers. These functions would then be used in app.use().
// Import library
var Logger = function(logger) {
return function(err, req, res, next) {
var meta = {
path: req.originalUrl,
method: req.method,
'user-agent': req.headers['user-agent'],
origin: req.headers.origin
}
if (err instanceof customError) {
logger.log(err.logLevel, err.message, meta)
return next(err)
} else {
logger.log('error', err.message, meta)
return next(err)
}
}
}
var ErrorHandler = function() {
return function(err, req, res, next) {
if (err instanceof customError) {
return res.status(err.status).json(err.toResponseJSON())
}else{
return res.status(500).json({
success: false,
message: err.message
})
}
}
}
module.exports = {
Logger,
ErrorHandler
}