I am new to express (or any JS backend) so sorry if question was already answered, or kind of stupid.
I have registered endpoint.
app.get('/hello-world'), async (req, res) => {
try {
// do something
sendResponse({"Message": "Hello World"}, res);
} catch (e) {
handleError(e, res);
}
});
Where sendResponse and handleError are doing just setting status and body / additional exception metadata using res.status().json()
Is there any way to make response handling more simple by registering some response handler and write the logic of response / exception handling at one place?
What I have in mind is this:
Change example endpoint logic to:
app.get('/hello-world'), async (req, res) => {
return {"Message": "Hello World"}
// or throw new error
});
and some repsonse handler which will handle result of function
resposeHandler(payload, err, res) {
if (err) {
res.status(500).json(err) // just as an example
} else {
res.status(200).json(payload)
}
}
You can create a function wrapper to catch all the errors and send them to the error middleware:
const errorHandler = (routeHandler) =>
(req, res, next) => {
const routeHandlerReturn = routeHandler(req, res, next)
return Promise.resolve(routeHandlerReturn).catch(next)
}
Then you can reutilize it in all your controllers, making the app much cleaner:
app.get('/hello-world', errorHandler(async function(req, res, next) {
sendResponse({"Message": "Hello World"}, res);
});
If some error is thrown, it will be handled in your error handler middleware:
// index.js
app.use((err, req, res, next) => {
res.status(err.status || 500)
res.json({
message: err.message || 'Internal Error',
error: err.error
})
})
create two middlewares, one for error handle and the other one for success response
// error handling when error occured
function errorHandler(req,res,next) => {
return res.status(err.status || 500).json({
success: false,
message: err.message
});
};
// success response and return data
function successHandler(successMsg, successData) => {
return (req,res,next) => {
return res.status(200).json({
success: true,
message: successMsg,
data: successData
});
};
};
register them in express
const app = express();
app.get('/someroute', successHandler('it is endpoint of someroute!', 'your data here'))
app.use(errorHandler)
use errorHandler after you call and define the route
Related
Background: The simplified test code below uses Express and Mongoose.
Question: I set up the .then statement to throw an error for testing. When an exception is thrown my error handling middleware is triggered with next() but not before res.render('index', { doesUserExist }); is hit. This line results in the error, "Cannot set headers after they are sent to the client" because in my error handling middleware res.render('error_page', { err }); is also called. What part of my code should I change to eliminate the error?
Followup: Do I need more than a slight shift in my approach? Am I using the completely wrong pattern to perform this action efficiently/effectively?
app.get('/', function(req, res, next) {
(async function() {
let doesUserExist = await User.exists( { name: 'steve' })
.then( function(result) {
throw 'simulated error';
})
.catch( function(error) {
next(new Error(error));
});
res.render('index', { doesUserExist });
})();
});
app.use(function(err, req, res, next) {
res.render('error_page', { err });
});
This is because of an async function without a catch block
app.get('/', function (req, res, next) {
(async function () {
try {
let doesUserExist = await User.exists( { name: 'steve' });
if (doesUserExist) {
throw 'simulated error';
} else {
next(new Error(error));
}
res.render('index', { doesUserExist });
} catch (err) {
return next(err)
}
})();
});
app.use(function (err, req, res, next) {
res.render('error_page', { err });
});
Instead of next write return next(new Error(error)). In this way it wont execute any further code and go to the error middleware
You can create a function wrapper to catch all the errors and send them to the error middleware:
const asyncWrap = fn =>
function asyncUtilWrap (req, res, next, ...args) {
const fnReturn = fn(req, res, next, ...args)
return Promise.resolve(fnReturn).catch(next)
}
Then you can reutilize it in all your controllers, making the app much cleaner:
app.get('/', asyncWrap(async function(req, res, next) {
let doesUserExist = await User.exists( { name: 'steve' }) //*
.then( function(result) {
throw 'simulated error'; // This error is automatically sent to next()
})
.catch( function(error) {
next(new Error(error)); // This error works normally
});
res.render('index', { doesUserExist });
});
*You shouldnt combine await and then/catch syntax by the way.
Following is the code I am referring to,
axios.get('http://localhost:1337/')
.then( response => {
var dataSet = response.data;
// URL Handle for Data page
app.get('/data', (req, res) => {
res.render('data', {
data = dataSet
});
});
})
.catch( error => {
console.log('An error occurred: ' + error);
});
// URL Handle for Contact page
app.get('/contact', (req, res) => {
res.render('contact');
});
// URL Handle for Error page
app.all('*', (req, res) => {
res.render('error');
});
Now, as I am using Axios for fetching the response from the database, I am also rendering the data in that desired page where I want the response of the API.
But there's a problem when I am using "app.all" handler for the wrong URL handle, it redirects to the error page.
I need a solution in which there can be an exception which can be added to the "app.all" handler and whenever the handle is '/data' it does not redirect to '/error'.
Looks like your route for /data is registered after the async call of axios is completed.
Try, putting axios call inside route handler
app.get('/data', (req, res) => {
axios.get('http://localhost:1337/').then( response => {
var dataSet = response.data;
res.render('data', {
data: dataSet
});
})
.catch( error => {
console.log('An error occurred: ' + error);
});
});
// URL Handle for Contact page
app.get('/contact', (req, res) => {
res.render('contact');
});
// URL Handle for Error page
app.all('*', (req, res) => {
res.render('error');
});
You should use axios in controller, so code should look like this:
app.get('/data', async (req, res, next) => {
try{
const { body } = await axios.get('http://localhost:1337/')
res.render('data', {
data : body
});
}catch(error){
next(error)
}
});
As You see, if there is error You should call next function with error instance and then create middleware as errorHandler to catch it.
I am trying to understand how to forward errors that occur in routes in a nodejs express application back to the error handler without throwing the error.
Basically I have my middleware setup like this:
const allowedMethods = ['GET', 'POST'];
app.use(function (req, res, next) {
if (!allowedMethods.includes(req.method))
return res.sendStatus(405);
});
let paymentRoutes = require('./routes/payment');
app.use('/payment', paymentRoutes);
let notificationRoute = require('./routes/notifications');
app.use('/notifications', notificationRoute);
let customerRoutes = require('./routes/customer');
app.use('/customer', customerRoutes);
let subscriptionRoutes = require('./routes/subscription');
app.use('/subscription', subscriptionRoutes);
app.use(function (err, req, res, next) {
console.log("error occured!");
console.log(err);
if (err) {
console.log("Throwing an error!");
next(err);
} else {
console.log("no error...")
}
});
app.use(function logErrors (err, req, res, next) {
winston.error(err);
next(err);
});
app.use(function sendMailToAdmin(err, req, res, next) {
//sendmailtoadmin
});
And it is working whenever I throw an error in one of my routes. Currently I am handling errors like this in my routes though.
router.get('/:id/has-premium', (req, res) => {
//throwing a test error
throw new Error;
const userId = req.params.id;
customer.hasActivePremium(userId)
.then(activePremium => {
res.status(200).send(JSON.stringify({"success": true, "premium": activePremium}));
})
.catch(err => {
res.status(err.httpCode).send(JSON.stringify({"success": false, "error": err.description}));
})
});
My question is how would I "call" my middleware error handler with the error I caught when I caught the promise?
You can use next in the route signature like
router.get('/:id/has-premium', (req, res,next) => {
//throwing a test error
throw new Error; // you can call next() here
const userId = req.params.id;
customer.hasActivePremium(userId)
.then(activePremium => {
res.status(200).send(JSON.stringify({"success": true, "premium": activePremium}));
})
.catch(err => {
res.status(err.httpCode).send(JSON.stringify({"success": false, "error": err.description}));
})
});
and then call next(err) instead of res.status(err.httpCode).send(JSON.stringify({"success": false, "error": err.description}));
I have a basic express setup that includes the following:
app.all('/api/:controller/:id?', [ api, response.success, response.error ])
app.get('*', (req, res) => {
console.log('This should only fire when no route matched')
res.sendFile(path.resolve(pubRoot, 'index.html'))
})
In the code for the middleware on the first route the api module simply resolves a promise at this point (or rejects on 404) and then calls next, this makes it into the response middleware which looks like this:
const response = {
success: (req, res, next) => {
res.status(200).send(res.data)
},
error: (error, req, res, next) => {
const statusCode = error.statusCode || 500
res.status(statusCode).send(error.message || 'Internal Server Error')
}
}
This works fine on a successful request and I get the data (and status code 200) back. It also works on error (returning 404 and Not Found as expected), however, it continues on to the wildcard route. I get the console.log along with Error: Can't set headers after they are sent.
UPDATE: Below is the code for the api module:
const api = (req, res, next) => {
const controller = req.params.controller
const event = { body: req.body, id: req.params.id, query: req.query }
// Handle non-existent controller
if (!controllers[controller]) {
next(new HTTPError(404))
return
}
// Controller found, process
controllers[controller][req.method.toLowerCase()](event)
.then((result) => {
res.data = result
next()
})
.catch((err) => {
next(err)
})
}
I have a sequelize database that validates data and throws errors.
I know I can do something like this to catch and output my errors:
User.build()
.catch(Sequelize.ValidationError, function (err) {
// respond with validation errors
return res.status(422).send(err.errors);
})
.catch(function (err) {
// every other error
return res.status(400).send({
message: err.message
});
But I don't want to add it to every single request, is there some generic way to catch theese errors?
You can add a custom method to req (or res) that will resolve the promise and handle any errors:
app.use((req, res, next) => {
req.resolve = (promise) => {
return promise.catch(Sequelize.ValidationError, err => {
// respond with validation errors
return res.status(422).send(err.errors);
}).catch(err => {
// every other error
return res.status(400).send({ message: err.message });
});
});
next();
});
Usage (provided that the middleware above is added before your routes):
router.post('/user', (req, res) => {
req.resolve(User.build()).then(user => res.json(user));
});
ES.next version (2016):
you can use async functions that throw using this wrapper function copied from the official strongloop website:
let wrap = fn => (...args) => fn(...args).catch(args[2]);
then make the function in your router/controller like that:
router.post('/fn/email', wrap(async function(req, res) { ...throw new Error(); }
and finally have a normal catch all errors middleware:
app.use(function(err, req, res, next) { console.log(err); }
Obviously for this to work you need the babel transpiler currently