How to make koa ctx.throw() use application/json rather than text/plain
Asked Answered
C

2

7

I have made a custom error handler for my koa app which works beautifully (except for one sticking point) - using ctx.throw() means any stacktrace is emitted to the server logs and also any custom error message is sent in the response.

The one problem is that Content-Type header is text/plain but I really need it to be application/json.

app.js:

import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import logger from 'koa-morgan';

import authentication from './middleware/authentication';
import config from './config';
import errorHandler from './middleware/error-handler';
import notificationsRoutes from './routes/notifications';

const app = new Koa();

app.use(errorHandler);
app.use(bodyParser());
app.use(logger(config.logLevel));
app.use(authentication);
app.use(notificationsRoutes.routes());

export default app;

error-handler.js:

export default async (ctx, next) => {
  return next().catch(({ statusCode, message }) => {
    ctx.throw(statusCode, JSON.stringify({ message }));
  });
};

(I thought (statusCode, JSON.stringify({ message })); might coerce the response into application/json but it doesn't.

I have googled to no avail. Please help!

Clunk answered 3/10, 2018 at 9:15 Comment(0)
C
8

Managed to modify the error-handler to produce the desired result. Works really well - stack traces are emitted to server logs and the first line of that message becomes the message in the response body. The latter might be considered a downside by some but it depends what you're after.

error-handler.js:

export default async (ctx, next) => {
  return next().catch(err => {
    const { statusCode, message } = err;

    ctx.type = 'json';
    ctx.status = statusCode || 500;
    ctx.body = {
      status: 'error',
      message
    };

    ctx.app.emit('error', err, ctx);
  });
};

Found this and used it for reference: https://github.com/koajs/examples/blob/master/errors/app.js

It's worth mentioning that this custom error - ServerError.js - is used in the app; that's why ctx.status = statusCode || 500 - statusCode is provided by ServerError when used, but for non-custom errors that are thrown, statusCode comes through to error-handler.js as undefined so || 500 is needed.

ServerError.js:

export class ServerError extends Error {
  constructor(statusCode, message) {
    super(message);
    this.statusCode = statusCode;
  }
}

(usage: throw new ServerError(400, 'my informative error message');)

Don't have any catch blocks in any of your middlewares and the error will propagate all the way up to your top errorHandler middleware in app.js (which is what you want to happen).

Custom error handling in koa seems to generate many different opinions but this seems to work well for us for now.

Clunk answered 3/10, 2018 at 10:37 Comment(1)
Have tried this method, using node request only get an "Internal Server Error" from the res.body , using Superkoa to run the test, and the res.body is still the entire string output, instead of a JSON object. Is there any way to capture the payload in an object for the client to undersand what went wrong?Gratification
I
1

Add Error handler middleware

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

then throw the error like this:

export async function usersIssuesHandler(context: Context) {
    ....
    context.throw('some error here',500);
}
Implacental answered 24/3, 2022 at 11:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.