Search code examples
typescriptdebuggingerror-handlingkoa2

How do I make a custom middleware function get logged to console while in Koa debug mode in TypeScript?


Using Koa v2.7.0 and TypeScript v3.3.1

My "errorHandler" middleware function being exported like this

export const clientErrorHandler = () => {
    return async (ctx: Context, next: NextFunction) => {
        try{
            await next();
        }
        catch (err){
            if(err instanceof HTTPClientError){
                console.warn(err);
                ctx.status = err.statusCode;
                ctx.body = err.message;
                ctx.app.emit('error', err, ctx);
           }
       }
   }
}

My logic which handles the attachment of the middleware on the given Koa app looks like this:

export const handleErrors = (app: Koa) => {
    const serverErrHandler = serverErrorHandler();
    serverErrHandler._name = 'serverErrorHandler';
    app.use(serverErrHandler)
}

The reason why I'm trying to create this property is due to this Koa documentation (see link below) which states that we can give middleware functions a _name property so that when we run the program with DEBUG=koa* set, this middleware function will be able to have a name that shows up in the console.

Since JavaScript does not allow defining function names at runtime, you can also set a middleware's name as ._name. This is useful when you don't have control of a middleware's name. For example:

const path = require('path'); const serve = require('koa-static');
const publicFiles = serve(path.join(__dirname, 'public'));
publicFiles._name = 'static /public';
app.use(publicFiles);

Source of the above snippet: https://github.com/koajs/koa/blob/master/docs/guide.md#debugging-koa

However, when trying this, since I'm using TypeScript, it does not like it that I'm trying to set a property on this anonymous function.

[ts] Property '_name' does not exist on type '(ctx: Context, next: NextFunction) => Promise'.

I was hoping to determine the best way to go about enabling myself to be able to add this little _name property to this anonymous function so that I could have solid debugging logs.


Solution

  • As I was writing this out I ended up finding two possible solutions.

    The one that I'm going with, is to simply not use an anonymous function, so there is no need to modify a _name property on my middleware function. After re-reading the koa documentation I think a key phrase I looked over was

    "for middleware that you don't have control over"

    My middleware function definition now looks like this:

    export const clientErrorHandler = () => {
        let clientErrHandler = async (ctx: Context, next: NextFunction) => {
            try{
              await next();
            }
            catch (err){
                if(err instanceof HTTPClientError){
                  console.warn(err);
                  ctx.status = err.statusCode;
                  ctx.body = err.message;
                  ctx.app.emit('error', err, ctx);
                }
            }
          }
        return clientErrHandler;
    }
    

    Now, if this hypothetically were the case where you didn't have control over a 3rd party middleware function name AND you were using TypeScript, I found that this stack overflow solution helps in that scenario: https://stackoverflow.com/a/18640025

    Using that that stackoverflow's suggestion, one could define an interface and do a type assertion with the anonymous function to produce a result that would let the caller typescript code set the _name property on the returned function without complaining that no such property existed.

    Just to demonstrate what this looks like:

    Even though I'm demonstrating this on the code that you technically wouldn't have access to modify I assume you could do a type assertion on the caller side as well and then modify the _name property after that.

    interface MiddlewareFunction { (ctx: Context, next: NextFunction ): Promise<void>; _name: string; }
    
    export const serverErrorHandler = () => {
      return <MiddlewareFunction> async function (ctx: Context, next: NextFunction){
        try {
          await next();
        }
        catch (err) {
          if(process.env.NODE_ENV === 'production'){
            ctx.status = 500;
            ctx.body = 'Internal Server Error';
          }
          else {
            ctx.status = err.status;
            ctx.body = err.stack;
            ctx.app.emit('error', err, ctx);
          }
        }
      }
    }
    

    Same code as in the question:

    export const handleErrors = (app: Koa) => {
        const serverErrHandler = serverErrorHandler();
        serverErrHandler._name = 'serverErrorHandler';
        app.use(serverErrHandler)
    }
    

    EDIT: You can also just change from arrow function back to a normal named javascript function in the return.

    export const serverErrorHandler = () => {
      return async function serverErrorHandler (ctx: Context, next: NextFunction){
        try {
          await next();
        }
        catch (err) {
          if(process.env.NODE_ENV === 'production'){
            ctx.status = 500;
            ctx.body = 'Internal Server Error';
          }
          else {
            ctx.status = err.status;
            ctx.body = err.stack;
            ctx.app.emit('error', err, ctx);
          }
        }
      }
    }