Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix async express middleware to properly handle error cases #1693

Closed
humphd opened this issue Feb 11, 2021 · 4 comments · Fixed by #2177
Closed

Fix async express middleware to properly handle error cases #1693

humphd opened this issue Feb 11, 2021 · 4 comments · Fixed by #2177
Assignees
Labels
area: back-end type: bug Something isn't working
Milestone

Comments

@humphd
Copy link
Contributor

humphd commented Feb 11, 2021

We have a lot of express end-points that use async functions:

$ rg "async \(req"
src/backend/web/routes/stats.js
8:const statsRoute = (statsPeriod) => async (req, res) => {

src/backend/web/routes/planet.js
63:router.get('/', async (req, res) => {

src/backend/web/routes/query.js
9:router.get('/', validateQuery(), async (req, res) => {

src/backend/web/routes/user.js
19:router.get('/feeds', protect(), async (req, res) => {

src/backend/web/routes/posts.js
10:posts.get('/', validatePostsQuery(), async (req, res) => {
76:posts.get('/:id', validatePostsIdParam(), async (req, res) => {

src/backend/web/routes/feed.js
29:const feedRoute = (type) => async (req, res) => {
110:router.get('/opml', async (req, res) => {
145:router.get('/wiki', async (req, res) => {

src/backend/web/validation.js
5:  return async (req, res, next) => {

src/backend/web/routes/feeds.js
11:feeds.get('/', async (req, res) => {
35:feeds.get('/:id', validateFeedsIdParam(), async (req, res) => {
56:feeds.put('/:id/flag', protectAdmin(), validateFeedsIdParam(), async (req, res) => {
77:feeds.post('/', protect(), validateNewFeed(), async (req, res) => {
100:feeds.delete('/cache', protectAdmin(true), async (req, res) => {
112:feeds.delete('/:id', validateFeedsIdParam(), protect(), async (req, res) => {
133:feeds.delete('/:id/flag', protectAdmin(), validateFeedsIdParam(), async (req, res) => {

Express 4.x doesn't quite do async properly, in that you have to manually deal with the error case and call next(err) manually so that errors propagate down the middleware chain to the eventual default error handler.

The trick is to turn code like this:

router.get('/:id', async (req, res) => {
  const { id } = req.params;
  const data = await getSomething(id);
  res.json(data);
});

into something like this:

router.get('/:id', async (req, res, next) => {
  try {
    const { id } = req.params;
    const data = await getSomething(id);
    res.json(data);
  } catch(err) {
    next(err);
  }
});

Basically, we have to either deal with the error case in the route (i.e., don't let it go further in any code path), or call next(err) ourselves when things blow up. I haven't looked at all of our routes to see if they need changing, so this is a bug to audit things.

@humphd humphd added type: bug Something isn't working area: back-end labels Feb 11, 2021
@HyperTHD
Copy link
Contributor

So if I'm understanding this correctly, we'd fix up the backend routes so that they explicitly call next with the error?

For reference, here's our logout route in auth.js
Example-Route

@humphd
Copy link
Contributor Author

humphd commented Feb 11, 2021

In the code you listed above, we aren't using async, so this doesn't apply. However, in this case we do need to do something different from the usual error handler: we want to redirect.

@HyperTHD
Copy link
Contributor

One thing I noticed in all the asynchronous function callbacks is that many of them do not call next(err). Is this something that needs to be fixed for all of these cases to fix their error handling?

@humphd
Copy link
Contributor Author

humphd commented Feb 22, 2021

Ideally we should have all errors go through a common error handler so that we can log it in production, see https://github.com/Seneca-CDOT/satellite/blob/master/index.js#L99-L119.

Ideally, the pattern would be something like this:

router.get('/whatever', async (req, res, next) => {
  try {
    res.json(await trySomething());
  } catch(err) {
    // you could also create a better external error to use here vs. exposing details about the internal problem...
    next(err);
  }
});

Then you

@rjayroso rjayroso added this to the 1.9 Release milestone Mar 9, 2021
@humphd humphd modified the milestones: 1.9.5 Release, 2.0 Release Apr 9, 2021
HyperTHD added a commit that referenced this issue Apr 23, 2021
Fixes #1693: Audit of all asynchronous routes in backend folder
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: back-end type: bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants