Search code examples
javascriptnode.jsexpressnestjsmiddleware

Nest js get response body in middleware


I am developing logging middleware, and want to log response body in 500 responses. In middleware im setting a callback for "finish" event on request, that emits after the response was sent, but no response body in Response object is present.

I assume there is no body because nest js with express under the hood sends response by chanks, and does not saves it in Request or Response objects.

But I hope there is a workaround to get response body from Response object in middleware that someone found. The example of middleware class im working on:

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(request: Request, response: Response, next: NextFunction): void {
    const { ip, method, originalUrl } = request;
    const userAgent = request.get("user-agent") || "";

    response.on("finish", () => {
      const { statusCode } = response;
      const contentLength = response.get("content-length");

      const basicRequestMetaInfo = {
        method,
        originalUrl,
        statusCode,
        ... // other data
      };

      if (this.isErroneousStatusCode(statusCode)) {
        const additionalRequestMetaInfo = {
          body: response.body, // The place i want to reach Response body
        };

        this.winstonLoggerErrorLevel.error({
          ...basicRequestMetaInfo,
          ...additionalRequestMetaInfo,
        });
        return;
      }
      this.winstonLoggerInfoLevel.info(basicRequestMetaInfo);
    });
    next();
  }
}


Solution

  • This article explains how to transform the buffer chunks in a readable response.

    This is the code from the article that you would need, with some typescript typing you can change as needed:

    const getResponseLog = (res: Response) => {
      const rawResponse = res.write;
      const rawResponseEnd = res.end;
      const chunkBuffers = [];
      res.write = (...chunks) => {
        const resArgs = [];
        for (let i = 0; i < chunks.length; i++) {
          resArgs[i] = chunks[i];
          if (!resArgs[i]) {
            res.once('drain', res.write);
            i--;
          }
        }
        if (resArgs[0]) {
          chunkBuffers.push(Buffer.from(resArgs[0]));
        }
        return rawResponse.apply(res, resArgs);
      };
      console.log(`Done writing, beginning res.end`);
      res.end = (...chunk) => {
        const resArgs = [];
        for (let i = 0; i < chunk.length; i++) {
          resArgs[i] = chunk[i];
        }
        if (resArgs[0]) {
          chunkBuffers.push(Buffer.from(resArgs[0]));
        }
        const body = Buffer.concat(chunkBuffers).toString('utf8');
        res.setHeader('origin', 'restjs-req-res-logging-repo');
         const responseLog = {
          response: {
            statusCode: res.statusCode,
            body: JSON.parse(body) || body || {},
            // Returns a shallow copy of the current outgoing headers
            headers: res.getHeaders(),
          },
        };
        console.log('res: ', responseLog);
        rawResponseEnd.apply(res, resArgs);
        return responseLog as unknown as Response;
      };
    };