routing-controllers icon indicating copy to clipboard operation
routing-controllers copied to clipboard

question: how to get raw request body

Open kkorus opened this issue 4 years ago • 6 comments

I'm adding a Stripe webhook and I need to get raw request body, like:

app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {}

https://stripe.com/docs/webhooks/signatures#verify-official-libraries

How I can get it using routing-controller?

kkorus avatar Feb 21 '20 14:02 kkorus

+1

manan avatar Jun 27 '20 08:06 manan

@kkorus Did you end up finding a way around?

manan avatar Jun 27 '20 08:06 manan

Spent all afternoon trying to figure this out too, but I have a solution:

Add the following middleware:

// RawBodyMiddleware.ts
import { Request, Response } from 'express';

const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
  const body = []
  req.on('data', chunk => {
    body.push(chunk)
  })
  req.on('end', () => {
    const rawBody = Buffer.concat(body)
    req['rawBody'] = rawBody
    switch (req.header('content-type')) {
      case 'application/json':
        req.body = JSON.parse(rawBody.toString())
        break
      // add more body parsing if needs be
      default:
    }
    next()
  })
  req.on('error', () => {
    res.sendStatus(400)
  })
}

export default RawBodyMiddleware

In your controller, add the middleware to the endpoint that requires a raw body and access the raw body property from the request using the @Req() decorator:

import { Request, Response } from 'express';
import { Req, UseBefore } from 'routing-controllers';
import RawBodyMiddleware from '../middlewares/RawBodyMiddleware'

@Post('/myendpoint')
@UseBefore(RawBodyMiddleware)
public myEndpoint(@Req() request: Request) {
   const rawBody = request.rawBody
   const asString = rawBody.toString()

   const jsonBody = request.body
}

Hope that helps!

Aitchy13 avatar Aug 14 '20 19:08 Aitchy13

Spent all afternoon trying to figure this out too, but I have a solution:

Add the following middleware:

// RawBodyMiddleware.ts
import { Request, Response } from 'express';

const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
  const body = []
  req.on('data', chunk => {
    body.push(chunk)
  })
  req.on('end', () => {
    const rawBody = Buffer.concat(body)
    req['rawBody'] = rawBody
    switch (req.header('content-type')) {
      case 'application/json':
        req.body = JSON.parse(rawBody.toString())
        break
      // add more body parsing if needs be
      default:
    }
    next()
  })
  req.on('error', () => {
    res.sendStatus(400)
  })
}

export default RawBodyMiddleware

In your controller, add the middleware to the endpoint that requires a raw body and access the raw body property from the request using the @Req() decorator:

import { Request, Response } from 'express';
import { Req, UseBefore } from 'routing-controllers';
import RawBodyMiddleware from '../middlewares/RawBodyMiddleware'

@Post('/myendpoint')
@UseBefore(RawBodyMiddleware)
public myEndpoint(@Req() request: Request) {
   const rawBody = request.rawBody
   const asString = rawBody.toString()

   const jsonBody = request.body
}

Hope that helps!

This was super helpful, thanks!

eliobricenov avatar Sep 15 '20 21:09 eliobricenov

I tried the solution above, but stripe wasn't liking the rawBody buffer, just passing the bodyparser.raw function into the UseBefore decorator seems to work for me!

@Service()
@JsonController("/stripe")
export class StripeController {
  @HttpCode(200)
  @Post("/webhook")
  @UseBefore(raw({ type: "application/json" }))
  async stripe(@Req() request: Request, @HeaderParam("stripe-signature") signature: string) {
    let event: Stripe.Event
    if (!request.body) return false
    try {
      event = stripe.webhooks.constructEvent(request.body, signature, STRIPE_WEBHOOK_SECRET)
    } catch (err) {
      console.log("oops", err)
      return false
    }
   // do stuff with event
    }
}

JClackett avatar Aug 24 '21 12:08 JClackett

The issue mainly roots back to how express handles body parsing middlewares. When we use the default express.json() middleware, it will by definition, apply it globally across all controllers and actions. I got around this with a 2-step process:

  • Inject the raw body buffer into the webhook route

// src/middlewares/rawBodyInjector.ts
export const RawBodyInjector = (req: any, res: any, buf: Buffer, encoding: any) => {
  if (buf && buf.length) {
    req.rawBody = buf.toString(encoding || "utf8");
  }
};

This will inject the buffer bytes (with the proper encoding) into the attribute rawBody of the express.Request object received by your server. Generally, utf8 encoding works.

  • Create a custom JSON body parser middleware

For general use cases, the default body middleware serves its purpose, but this one is an exceptional case, therefore if your code contains something like this in your index.ts or app.ts, you'll have to remove it:

// app.ts or index.ts

// this applies it globally
// as a result req.body is parsed into an object
app.use(express.json()); //  ====> REMOVE THIS

Replacing the factory body middleware, we instead use this custom defintion:

// app.ts
...
const app = express();
...
...
...
const CustomJsonParserMiddleware = (
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
): void => {
  if (req.originalUrl === "/webhook") {
    // on the webhook route, process the request with the injector
    express.json({ verify: RawBodyInjector })(req, res, next);
  } else {
    // bodyParser.json() will work as well
    express.json()(req, res, next);
  }
};

app.use(CustomJsonParserMiddleware);

This ensures that the body parser middleware doesn't parse the raw body bytes with the default configuration for the route /webhook, but by using the middleware we created (technically, RawBodyInjector is not a middleware as it is being used inside a middleware, but it provides functionality similar to a middleware).

Once that is done, you can access the req.rawBody on the controller action for route /webhook

@Post("/webhook")
  async postReceiveWebhook(@Req() req: any) {
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
    
    const rawBody = req.rawBody;
    const signature = req.headers["stripe-signature"];
    const secret = process.env.STRIPE_WEBHOOK_SECRET!;

    let event = undefined;
    try {
      event = stripe.webhooks.constructEvent(rawBody, signature, secret);
    } catch (e: any) { 
      console.log("Webhook verification failed:", e.message);
      return false;
    }

    // process the event object

sowmenappd avatar May 01 '22 22:05 sowmenappd

Closing this as stale.

If the issue still persists, you may open a new Q&A in the discussions tab and someone from the community may be able to help.

attilaorosz avatar Dec 21 '22 16:12 attilaorosz

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

github-actions[bot] avatar Jan 21 '23 00:01 github-actions[bot]