How to parse the raw body of a request in a nest js controller
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
- Turn off NestJS automatic body parsing
- Add required middleware classes
- 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.