Koa@2 error handling
Asked Answered
P

2

12

I'm building an API with Koa. I have all my routes in place with koa-router. Each route uses a controller that has all the logic for a given mongoose model. I've read the Koa docs on error-handling and understand the use of awaiting in a try/catch block. There they mention a Default Error Handler should be set at the beginning of the middleware chain. So if I was to have something like the following, I should have resonable error handling for the route at router.get():

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    err.status = err.statusCode || err.status || 500;
    throw err;
  }
});

router
    .get('/', async (ctx, next) => {
    console.log('Got Route');
    //ctx.body = users;
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(3000, () => console.log('Koa app listening on 3000'));

If I was to have something slightly more complex at this route, is there any benefit of adding another try/catch inside the route?

router
    .put('/', async function updateOnServer(ctx, next) {
        try {
            await Model.updateOne({
                _id: ctx.params.id,
            }, {
                field1: ctx.request.body.field1,
                $push: { field2: ctx.request.body.field2 },
            }).exec();
        } catch (e) {
            console.log(e);
        }

        await next();
});

Am I just adding redundant error handling here?

Pashalik answered 12/3, 2018 at 4:56 Comment(0)
P
22

I re-read the docs on Error Handling and also found this little tip on the Koa Wiki. From that, I've concluded the following:

Koa Error Handling states:

However, the default error handler is good enough for most use cases.

The default error handler in this case is the Koa-built-in error handler. You do not need to include any kind of custom error handling in the code you write. Koa will write out a stack trace along with the error message, etc.

If you want to modify how the error is handled, add something like the suggested middleware at the very beginning of the middleware chain:

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    err.status = err.statusCode || err.status || 500;
    ctx.body = err.message;
    ctx.app.emit('error', err, ctx);
  }
});

If you want to change that again for a specific route, or a special bit of logic, then add another try/catch block at that point (as per my above):

router
    .put('/', async function updateOnServer(ctx, next) {
        try {
            await Model.updateOne({
                _id: ctx.params.id,
            }, {
                field1: ctx.request.body.field1,
                $push: { field2: ctx.request.body.field2 },
            }).exec();
        } catch (e) {
            ctx.status = 418;
            ctx.body = "a custom error message, with nothing really helpful";
        }

        await next();
});
Pashalik answered 14/3, 2018 at 0:17 Comment(3)
I am a teapot short and stoutWhoa
The default Koa error handler seems to just always throw an Internal Server Error. I would rather distinguish between that form uncaught exceptions and an error in the request, which is certainly not an internal server error. I also would rather use throw() and not ctx.throw() since many of my libraries know nothing, and should know nothing, about Koa or the context object.Rhetor
@Pashalik didn't you forget something like ctx.status = err.status;?Pate
R
0

I was wrong in a comment above - Koa's default error handler doesn't always throw an Internal Server Error. It depends on the type of error you throw. However, I prefer to simply throw strings, and to use JavaScript's very own throw statement. So, I implemented a simple Koa error handler that allows me to do this:

throw `404: User ${id} not found`;

and that results in sending back a status 404 with a message like 'User 5 not found'. Here is my Koa middleware to do that (if you throw something other than a string, or the string isn't of the right format, it falls back to Koa's built-in error handler):

app.use(async (ctx, next) => {
    try {
        await next();
        }
    catch (err) {
        if (typeof err === 'string') {
            let lMatches = err.match(/^(\d{3}):\s*(.*)$/);
            if (lMatches) {
                ctx.status = Number(lMatches[1]);
                ctx.body = lMatches[2];
                }
            else throw err;  // use default error handler
            }
        else throw err;  // use default error handler
        }
    });
Rhetor answered 19/3, 2020 at 16:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.