Firebase cloud functions catch/handle error - node.js

Update Question: error: TypeError: res.json is not a function
I use Firebase Cloud Functions with Express app. I use middleware for handle error, but it is not working. How to catch/handle error when using throw new Error()?
My code below:
app.get('/test', (req, res) => {
throw new Error('this is error')
})
function errorHandler(err, req, res, next) {
res.json({error: err.message}) // error here
}
app.use(errorHandler)
exports.api = functions.https.onRequest(app)
Please help me. Thanks very much.

I had the same issue. You need a try/catch to capture the error and then use the next function to pass it down the middleware chain.
app.get('/test', (req, res, next) => {
try {
throw new Error('this is error')
} catch (err) {
next(err)
}
})
function errorHandler(err, req, res, next) {
res.json({error: err.message}) // error here
}
app.use(errorHandler)
exports.api = functions.https.onRequest(app)
Wrap the whole handler in the try block and it will always pass it down to the error handler.

You can use try/catch to handle error like this:
app.get('/test', (req, res) => {
try {
// Your Code
} catch (e) {
console.error("error: ", e);
res.status(500).send(e.message);
}
})

Related

Error middleware in express nodejs, next() does not catch error

Why does this approach not work? How can I create an error gaurd middleware for my API?
export function ErrorCatcherMiddleware() {
return (req: Request, res: Response, next: NextFunction) => {
try {
next();
} catch (err) {
console.log("trying request failed")
next(err);
}
} ...
app.use(ErrorCatcherMiddleware());
// ...routes and other middlewares
}
The error handling middleware takes 4 arguments (error as first arg) as opposed to 3 arguments for regular middleware.
const handleErrors = (err, req, res, next) => {
return res.status(500).json({
status: 'error',
message: err.message
})
}
app.use(handleErrors)
I may assume that it doesn't catch errors because the code that produces them is asynchronous.
To catch these errors you need to wait until the async operation is finished and in case of error call the next(err) function.
For example
app.get('/', (req, res, next) => {
fs.readFile('/file-does-not-exist', (err, data) => {
if (err) {
next(err) // Pass errors to Express.
} else {
res.send(data)
}
})
})
app.use((err, req, res, next) => {
// this middleware should be executed in case of the error
});
Or you can use middlewares that return promises like below (starting with Express 5).
app.get('/', async (req, res, next) => { // callback is "async"
const data = await fs.readFile('/file-does-not-exist');
res.send(data);
});
app.use((err, req, res, next) => {
// this middleware should be executed in case of the error
});
In this case, if fs.readFile throws an error or rejects, next will be called with either the thrown error or the rejected value automatically. You can find more details about that in this document

Why is this async handler error catching is different from normal try catch block?

When I try to follow a tutorial. He is using a library called express-async-handler to handle async functions automatically
Link to github npm
The thing is that I try to convert that code into normal code without using library I see some different.
Here is the code with asyncHandler
router.get('/', asyncHandler(async (req, res) => {
const products = await Product.find({});
throw new Error('Error')
res.json(products);
}))
As you can see there is an error thrown to the routes. When using asyncHandler the request status is change to 500 by middleware when error is thrown
const errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode
res.status(statusCode)
res.json({
message: err.message,
})
}
But when I try to use the normal trycatch block, The throw is not detected by the error handler middleware
router.get("/", async (req, res) => {
try {
const products = await Product.find({});
throw new Error('Something went wrong!');
res.json(products);
} catch (err) {
res.json({ message: err });
}
});
So what the difference between the codes , from my understanding the code i converted is correct. Is there something wrong?
The problem is that you are not calling the next function with your error. Right now, you're catching the error, and simply setting the json on the response.
This means that nothing outside of this function knows about the error, since it's already been completely handled!
See if the following gets you more what you're looking for:
router.get("/", async (req, res, next) => {
try {
const products = await Product.find({});
throw new Error('Something went wrong!');
res.json(products);
} catch (err) {
next(err)
}
});

When catching errors in Express route how do I bypass first res.render to avoid error, "Cannot set headers after they are sent to the client"?

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.

Errorhandling in middleware does not pass error

Basicly I got an async function like such:
export default (htmlFilePath, observer, redisClient) => async (req, res, next) => {
try {
...bunch of logic...
} catch (error) {
// if i log error here it displays correctly
next(error)
}
}
So if code comes to the catch above I can correctly use the error with stackTrace etc, but when passing it with next() to go to this express function error is lost somewhere... :
.get('/*', loader(filePath, observer, redisClient))
.use(function (err, req, res, next) {
res.statusCode = 500;
// Logger only logs: TEST
logger.error('TEST', err, err.stack);
res.send("Internal Server Error");
})
Anyone know what I am doing wrong? I want to make use of the error in the function above.
I found a solution at this post, can you try this code?
function loader(req, filePath, observer, redisClient, next) {
try {
...bunch of logic...
} catch (error) {
// if i log error here it displays correctly
req.error = error;
}
next();
}
api.get('/*', loader(req, filePath, observer, redisClient))
.use(function (req, res, next) {
// Logger only logs: TEST
var err = req.error;
if(err){
logger.error('TEST', err, err.stack);
res.send("Internal Server Error");
}else{
//Do something here
}
})
I'm not a big fan of try/catch inside of controllers, error handler middleware should handle them.
From https://expressjs.com/en/advanced/best-practice-performance.html#handle-exceptions-properly
app.get('/', function (req, res, next) {
// do some sync stuff
queryDb()
.then(function (data) {
// handle data
return makeCsv(data)
})
.then(function (csv) {
// handle csv
})
.catch(next)
})
app.use(function (err, req, res, next) {
// handle error
})

A generic catch-version for exceptions?

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

Resources