Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Parse sendBeacon data in Express

I’m trying to start logging my own WebVitals. The simple use-case in their example docs looks like this:

function sendToAnalytics(metric) {
  // Replace with whatever serialization method you prefer.
  // Note: JSON.stringify will likely include more data than you need.
  const body = JSON.stringify(metric);

  // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
  (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
      fetch('/analytics', {body, method: 'POST', keepalive: true});
}

That all seems simple enough. Here’s my actual implementation:

function sendToLog(metric) {
    // Replace with whatever serialization method you prefer.
    // Note: JSON.stringify will likely include more data than you need.
    const body = JSON.stringify(metric);
    console.log(`Sending body ${body}`);
  
    // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
    // (navigator.sendBeacon && navigator.sendBeacon('https://localho  st:9292/api/log', body)) ||
        fetch('https://localhost:9292/api/log', {body: body,   method: 'POST', headers: { 'Content-Type': 'application/json' }, keepalive: true});
}

I had to modify the fetch to include the "body" property name and add headers to get it to work, but that is now working. However, if I uncomment the navigator.sendBeacon line, I just get {} for the body.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

I’m using NodeJS and Express on the backend. My starting point was this:

const app = express();
app.use(
    express.json({
        // We need the raw body to verify webhook signatures.
        // Let's compute it only when hitting the Stripe webhook endpoint.
        verify: function (req, res, buf) {
            if (req.originalUrl.startsWith(API_PREFIX + '/webhook')) {
            req.rawBody = buf.toString();
            }
        },
    })
);
app.use(cors());

// Expose an endpoint for client logging.
app.post(API_PREFIX + '/log', async (req, res) => {
  const message = req.body;

  console.log('Got message');
  console.dir(message, { depth: null });
  logger.info(message);

  res.sendStatus(200);
});

There’s this similar question, where the accepted answer suggests that adding body-parser and app.use(bodyParser.raw()) should do the trick, and then in the comments there’s discussion of using bodyParser.json()) instead.

I’ve tried both of those:

import bodyParser from 'body-parser';
...
//app.use(bodyParser.raw());
//app.use(bodyParser.json());
app.use(
    express.json({
        // We need the raw body to verify webhook signatures.
        // Let's compute it only when hitting the Stripe webhook endpoint.
        verify: function (req, res, buf) {
            if (req.originalUrl.startsWith(API_PREFIX + '/webhook')) {
            req.rawBody = buf.toString();
            }
        },
    })
);
app.use(cors());

(i.e., uncommenting either of the two lines at the beginning), and in all 3 cases I still get an empty body when sendBeacon is used.

The Express documentation says,

[express.json] is a built-in middleware function in Express. It
parses incoming requests with JSON payloads and is based on
body-parser….A new body object containing the parsed data is
populated on the request object after the middleware (i.e. req.body),
or an empty object ({}) if there was no body to parse, the
Content-Type was not matched, or an error occurred.

So, a) I guess I shouldn’t need the body-parser, since express.json is just doing that anyhow; and b) I’m hitting one of those three conditions (there’s no body to parse, the Content-Type was not matched, or an error occurred). Assuming that’s the case, how do I diagnose which one it is, and then fix it (in such a way that the fetch fallback continues to work). Or, if there’s some other problem, back to the original question, how do I get this to work? 🙂

>Solution :

navigator.sendBeacon sends Content-Type: text/plain whereas express.json() only parses the body if Content-Type: application/json.

Use express.text() in combination with JSON.parse(req.body) instead, or additionally:

app.post(API_PREFIX + '/log',
  express.json(), express.text(), function(req, res) {
  if (typeof req.body === "string") req.body = JSON.parse(req.body);
  ...
});
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading