0
votes

I am writing the middleware for API endpoints in my app that respond to webhooks from other applications, and am relatively new to Koa, so am not completely familiar with its patterns.

I would like to structure my middleware as follows:

exports.updateReceived = async (ctx, next) => {
  // Respond to server issuing the webhook
  ctx.res.body = "ok";
  ctx.res.statusCode = 200;

  // Grab what we need from the request
  const { headers, state, request } = ctx;
  const { body } = request;

  // Do some async work
  const { example } = await doSomeAsyncWork(ctx);

  // Prepare a database query
  const query = { aValue: anId };

  // Run the DB query
  const result = await Thing.findOne(query);

  // Add data to the request
  state.thing = result;

  // Move on...
  return next();
};

However, this does not appear to be working, as an error in any of my async methods can cause the route to error out.

My goal is for this endpoint to always respond "yep, ok" (immediately), meaning it is simply up to the application to handle any error states.

I have researched this fairly well, and have come across this pattern:

app.use(async ctx => {
  db.fetch() // Assuming a Promise is returned
    .then(() => { ... })
    .catch(err => {
      log(err)
    })

  // status 200 will be returned regardless of if db.fetch() resolves or rejects.
  ctx.status = 200
})

However, this does not meet my needs as the middleware makes no use of next, so it is not really a useful pattern, so far as I can tell.

Could someone tell me what I am overlooking?

3
The key is that your middleware needs to return before calling next. Next is an encapsulation of the middleware chain. - Evert

3 Answers

2
votes

next() invokes the downstream middleware chain and returns a promise that resolves after all downstream middleware/handlers have finished.

That means you can simply implement your own upstream error handler that catches any errors and always ensures a 200 OK response.

const Koa = require('koa')
const app = new Koa()

app.use(async (ctx, next) => {
  next().catch((err) => {
    // Print error for our own records / debugging
    console.error(err)

    // But ensure that outgoing response is always a smile
    ctx.status = 200
    ctx.body = ':)'
  })
})

app.use(async (ctx) => {
  // Do your webhook / biz logic here, but for demonstration
  // let's make it always throw an error. Thus upstream next()
  // will be a rejected promise.
  throw new Error('this middleware will always bubble up a rejected promise')
})

app.listen(3000, () => {
  console.log('listening on 3000')
})

0
votes

Just to divert the solution in a different side, You could consider adding your work to some kind of MessageQueue and then let another process do that task for you. Basically asynchrously but you will still be important. This kind of pattern suits for your requirement.

There are many messaging system availble like AWS SQS which you could consider. This way your api will be very light weight and it will do thing which it needs to and send a command to your messaging system to do extra stuff. You are basically separting your core logic and the doing things in background which scales very nicely as well.

0
votes

Note: We are not awaiting next(), so we can end the request immediately. However the next handler in the chain will still have the opportunity to process the logic

app.use((ctx, next) => {
  next()
  ctx.status = 200
})

app.use( async ctx =>{
    db.fetch()
        .then(() => { ... })
        .catch(err => log(err))
    }
}