routing-controllers
routing-controllers copied to clipboard
question: how to get raw request body
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?
+1
@kkorus Did you end up finding a way around?
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!
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!
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
}
}
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
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.
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.