emonigma
emonigma

Reputation: 4406

Express server for both serving pages and endpoint

I want to have a Node web server serving pages and also set as an endpoint listening to webhooks. The example for the first comes from Rocket Rides, with the relevant code being:

const express = require('express');
// ...
const app = express();
// ...
// CRUD routes for the pilot signup and dashboard
app.use('/pilots', require('./routes/pilots/pilots'));
app.use('/pilots/stripe', require('./routes/pilots/stripe'));
// ...
// Index page for Rocket Rides
app.get('/', (req, res) => {
  res.render('index');
});
// ...
// Start the server on the correct port
const server = app.listen(process.env.PORT || config.port, () => {
  console.log('🚀 Rocket Rides server started:', config.publicDomain);
});

For the second, I use this tutorial with the following relevant code:

// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
  console.log("called!");
  let event;

  try {
    event = JSON.parse(request.body);
  } catch (err) {
    response.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'payment_intent.succeeded':
      const paymentIntentSucceeded = event.data.object;
      break;
    case 'payment_method.attached':
      const paymentMethod = event.data.object;
      break;
    // ... handle other event types
    default:
      // Unexpected event type
      return response.status(400).end();
  }

  // Return a response to acknowledge receipt of the event
  response.json({received: true});
});

app.listen(8000, () => console.log('Webhooks running on port 8000'));

With both parts, the server does not handle the webhook request:

Webhooks running on port 8000
POST /webhook 404 590.525 ms - 1415

and the sender receives a 404.

When I comment out most of the code in the first part, the webhook request is handled properly:

Webhooks running on port 8000
called!

and the sender receives a 200.

I believe one of the routes from the web server is masking the route for the endpoint. I tried looking for one with this thread:

app._router.stack.forEach(function(middleware){
    if(middleware.route){ // routes registered directly on the app
        routes.push(middleware.route);
    } else if(middleware.name === 'router'){ // router middleware 
        middleware.handle.stack.forEach(function(handler){
            route = handler.route;
            route && routes.push(route);
        });
    }
});

console.log(routes);

and the only relevant one was GET /.

If I include the code for the endpoint before the code for the router, the webhook is handled properly.

How can I find which route is masking the webhook endpoint?

Upvotes: 0

Views: 155

Answers (1)

jfriend00
jfriend00

Reputation: 707386

Put the more specific route definitions first like this:

app.use('/pilots/stripe', require('./routes/pilots/stripe'));
app.use('/pilots', require('./routes/pilots/pilots'));

And, the more general route definitions later. That makes sure the more specific routes aren't gobbled up by the more general handlers.

Keep in mind that with app.use(), something like app.use('/pilots') will match any route that starts with /pilots which would include all your /pilots/stripe routes. So, you want to make sure and put the app.use('/pilots/stripe', ...) before the app.use('/pilots', ...).


Another observation. In your /webhook handler, you need to return after you send an error status so the rest of your request handler doesn't continue to run.

// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
  console.log("called!");
  let event;

  try {
    event = JSON.parse(request.body);
  } catch (err) {
    response.status(400).send(`Webhook Error: ${err.message}`);
    return;                         // <=====  Add this
  }
  ....
}

This appears to be a bug in the actual stripe documentation.


If I include the code for the endpoint before the code for the router, the webhook is handled properly.

I would guess that you have bodyparser middleware elsewhere in your server. If that middleware is BEFORE this route, then this route won't get to use its bodyParser.raw() and get the data the way it wants and it will not work properly. This is because whichever bodyParser middleware runs first reads the body and parses it and puts it wherever that middleware is configured to put it. Once the body is read, it's gone from the stream so any other middleware that comes along and also tries to read the body data from the stream will find the stream empty.

So, this route just has to be before any other body parsing middleware that might handle JSON.

If you provided a link to your full code, we could take a look and see where this is happening.

Upvotes: 1

Related Questions