I am cleaning my code and moving from callback hell to async/await and try/catch but I still want to make my code DRY as I have too many routes and performing same try catch in every request. What could be the best way to handle this?
this my example code in one of the GET route.
router.get('/customer', async (req, res, next) => {
try {
const customer = await Customer.find({}).populate('buisness').exec();
return res.status(200).json({
result: customer
});
} catch (e) {
return next(e);
}
});
now if I repeat same thing on every route it is not following DRY code. what could be the best?
const errorHandlerMiddleware = async (req, res, next) => {
try {
await next();
} catch (err) {
// handle the error here
}
};
router.use(errorHandlerMiddleware);
router.get('/customer', async (req, res, next) => {
const customer = await Customer.find({}).populate('buisness').exec();
return res.status(200).json({
result: customer
});
});
use errorHandlerMiddleware before all your routes, this middleware will catch every exception that is thrown from your routes.
now every time there is an exception in your route it will be caught in the middleware
Related
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
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);
}
})
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)
}
});
I would like some basic error handling on every route, so if there is ever an exception, the API at least responds with 500.
According to this pattern, you still need to include a try/catch block in every route:
app.post('/post', async (req, res, next) => {
const { title, author } = req.body;
try {
if (!title || !author) {
throw new BadRequest('Missing required fields: title or author');
}
const post = await db.post.insert({ title, author });
res.json(post);
} catch (err) {
next(err) // passed to the error-handling middleware
}
});
That seems a little repetitive. Is there a higher-level way where exceptions are automatically caught everywhere and passed to the middleware?
I mean, it would obviously be possible for me to just define my own appGet():
function appGet(route, cb) {
app.get(route, async (req, res, next) => {
try {
await cb(req, res, next);
} catch (e) {
next(e);
}
});
}
Is there some built in version of this?
You can use express-promise-router package.
A simple wrapper for Express 4's Router that allows middleware to return promises. This package makes it simpler to write route handlers for Express when dealing with promises by reducing duplicate code.
E.g.
app.ts:
import express from 'express';
import Router from 'express-promise-router';
import bodyParser from 'body-parser';
const router = Router();
const app = express();
const port = 3000;
app.use(bodyParser.json());
app.use(router);
router.post('/post', async (req, res) => {
const { title, author } = req.body;
if (!title || !author) {
throw new Error('Missing required fields: title or author');
}
const post = { title, author };
res.json(post);
});
router.use((err, req, res, next) => {
res.status(500).send(err.message);
});
app.listen(port, () => console.log(`Server started at http://localhost:${port}`));
You don't need try/catch statement block anymore.
Test result:
I think the better approach would be to divide the services and the controllers which is demonstrated below.
Add post service:
async function addPostService (title, author) => {
if (!title || !author)
throw new BadRequest('Missing required fields: title or author');
return await db.post.insert({ title, author });
};
Add post controller:
function addPost(req, res, next){
const { title, author }= req.body;
addPostService
.then((post) => {
res.json(post);
})
.catch(next) // will go through global error handler middleware
}
Now, we can make a global error handler middleware which will catch the error thrown by any controller throughout the app.
function globalErrorHandler(err, req, res, next){
switch(true){
case typeof err === 'string':
// works for any errors thrown directly
// eg: throw 'Some error occured!';
return res.status(404).json({ message: 'Error: Not found!'});
// our custom error
case err.name = 'BadRequest':
return res.status(400).json({ message: 'Missing required fields: title or author!'})
default:
return res.status(500).json({ message: err.message });
}
}
And, don't forget to use the error handler middleware right before starting the server.
// ....
app.use(globalErrorHandler);
app.listen(port, () => { console.log('Server started...')});
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