How to parse the raw body of a request in a nest js controller

taylor-vick-M5tzZtFCOfs-unsplash

I recently needed to parse the raw body in a nestJS application to validate a webhook from a third party service. The key to this was using the json validate method to read the raw body in to a variable before passing the request on to the next middleware.

Why would you need raw body?

If you accept any webhooks from third party services you might need to parse the request and verify it with a hash. You can't modify the request in any way or the hashes will not match the expected value. You must use the request stream as it is sent.

Nest js automatically parses your application requests into JSON. This is perfect for 99% of use cases with NestJS web applications. We have to bypass this functionality.

! Update for NestJS 8+ in 2023 !

If you are using NestJS 8 or above you can use the @RawBody() decorator to access the raw body of the request. This is a much simpler solution than the one I originally wrote in this article.

You have to turn on the rawBody option in your nestJS application.

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    bodyParser: { rawBody: true },
  });
  await app.listen(3000);
}

Then you can access the raw body in your controller

@Post()
async handleAWebhook(@Req() req: RawBodyRequest<Request>) {
    const rawBody = req.rawBody
  // use the raw body (It is a Buffer so .toString() or use the buffer directly)
}

You can see this in a real app at my Nest Backend Libs on GitHub

I use those libraries in the app Use Miller (also GitHub)

The steps for the old way of doing it

  1. Turn off NestJS automatic body parsing
  2. Add required middleware classes
  3. Wire up the middleware in NestJS bootstrapping

Turn off NestJS automatic body parsing

Somewhere in your application you will bootstrap your application

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}

bootstrap();

You need to pass the option to create method that disables body parsing

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { bodyParser: false });
  await app.listen(3000);
}

bootstrap();

Add the required middleware classes

Add a RawBodyMiddleware.ts

import { Injectable, NestMiddleware } from "@nestjs/common";
import { json } from "body-parser";

/**
 * Copied this middleware to parse the raw response into a param to use later
 * from https://github.com/golevelup/nestjs/blob/master/packages/webhooks/src/webhooks.middleware.ts
 */
@Injectable()
export class RawBodyMiddleware implements NestMiddleware {
  public constructor() {}

  public use(req: Request, res: Response<any>, next: () => any): any {
    json({
      verify: (req: any, res, buffer) => {
        if (Buffer.isBuffer(buffer)) {
          const rawBody = Buffer.from(buffer);
          req["parsedRawBody"] = rawBody;
        }
        return true;
      },
    })(req, res as any, next);
  }
}

Add a JsonBodyMiddleware

import { Injectable, NestMiddleware } from "@nestjs/common";
import { json } from "body-parser";

@Injectable()
export class JsonBodyMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => any) {
    json()(req as any, res as any, next);
  }
}

Wire up the middleware into the NestJs Bootstrapping

We need to tell NestJS to parse the specified routes with the raw body parser. Then parse every other route with the regular JSON parser.

Then you can add any middleware you like after that.

// This is an array of routes we want raw body parsing to be available on
const rawBodyParsingRoutes: Array<RouteInfo> = [
  {
    path: "*my/rawbody/required/route*",
    method: RequestMethod.POST,
  },
];

export class AppModule implements NestModule {
  public configure(consumer: MiddlewareConsumer): MiddlewareConsumer | void {
    consumer
      .apply(RawBodyMiddleware)
      .forRoutes(...rawBodyParsingRoutes)
      .apply(JsonBodyMiddleware)
      .exclude(...rawBodyParsingRoutes)
      .forRoutes("*")
      .apply(MyOtherMiddleware)
      .forRoutes({ path: "*", method: RequestMethod.ALL });
  }
}

Access the raw body in a NestJS controller

Now in the controller for handling the specified route we can access the parsedRawBody parameter.

const payload = request.parsedRawBody?.toString();

Tip: If you don't see a body here make sure that the path you specified is actually triggering the raw body parser.