1

I aim to send an analytics event for each route in my API. As opposed to saving an event with the entire full route that was called, I want to save the base url as the event and the parameters of the route as variables of the event. For example, when saving an analytics event...

Not this:

{
  event_title: "API User Event"
  category: "domain.com/api/user_routes/route_1/value_of_param_one"
}

But this:

{
  event_title: "API User Event"
  category: "domain.com/api/user_routes/route_1"
  params: {
     param_one: "value_of_param_one"
  }
}

I'd like to have a global function that gets the parameters from the request variable, however, if you do this on a higher level (not route level)

app.use('/api/user_routes/*', myWrapperFunction)

myWrapperFunction will detect anything after /api/user_routes as parameters. From my experiments, I was only able to successfully detect the actual parameters inside a specific route function. However, that approach requires me to either edit each route function or wrap it like so...

router.get('/route_1/:param_one', (req, res) => Utility.analyticsEvent(userController.routeOneFunction, req, res));

router.get('/route_2/:param_one', (req, res) => Utility.analyticsEvent(userController.routeTwoFunction, req, res));

router.get('/route_3/:param_one', (req, res) => Utility.analyticsEvent(userController.routeThreeFunction, req, res));

Is there a way to detect the actual parameters of the route without actually going into the function itself? Or is this a limitation on express because it won't know the specifics of the route until it finds the first matching option traversing down the routes?

Edit If there is no way to know the parameters before express matches the specific route: is there a function that you can run before executing the route function that will tell you which route will be matched and will specify the parameters?

Welcome all comments!

AMC
  • 39
  • 1
  • 8
  • What do you mean: "detect the actual parameters of the route"? You can see exactly what the URL is in your middleware with `req.url`, `req.path`, req.originalUrl`, `req.query` and get anything you want. If you're not specifying a specific URL pattern with parameters to match for your middleware, then it won't parse the parameters for you, but you can parse them yourself in a generic way. You either specify the pattern you want and let Express do it or you parse the stuff out of the URL in a more generic way. This isn't a "limitation" of Express. It's how any system like this would work. – jfriend00 Mar 23 '20 at 19:01
  • @jfriend00 Thank you for your response. Specifying a pattern is not an option for us. We have about a hundred different paths. – AMC Mar 23 '20 at 19:06
  • Within each route (either in your existing handler or in a middleware function that you add to the route definition itself), you can look at `req.route()` to see what matched, at `req.params` to see the params defined for that route and `req.query`. If you have hundreds of different paths that are all mostly doing the same thing, then I'd suggest you can probably combine them into one route that carries out some common logic, then examines the URL and branches based on what it sees in the URL. – jfriend00 Mar 23 '20 at 21:04

2 Answers2

1

I think one approach is to write a middleware like below.

// This will get executed before every request. As we'll add this with app.use() with top level middlewares

function customMiddleware (req, res, next) {
   let url = req.baseUrl;
   // some if else logic to re-route

   if( url.includes('/api/user_routes')) {
     let urlSplit = url.split();
     if( url[urlSplit.length() - 1] == 'param_one' ) {
       res.redirect('/api/user_routes/route_1') 
     }
     else if(url[urlSplit.length() - 1] == 'param_tow' ) {
       res.redirect('/api/user_routes/route_1') 
     }
     // and so on

   } else {
      // executes other middleware or go to matching route
      next()
   }
}

app.use(customMiddleware)
Prakhar Patidar
  • 124
  • 1
  • 8
0

Found a way to do it after the call is made by overwriting the response.json function

app.use(function (req, res, next) {
  var json = res.json;
  res.json = function (body) {
      // Send analytics event before returning response     
      try { 
        // Routes that don't need to be tracked with analytics
        let notIncludeRoutes = [
          "some_not_tracked_route"
        ];
        let route = req.baseUrl + req.route.path;

        if(notIncludeRoutes.indexOf(route) === -1) {
          // Track route and params
          let route_params = res.req.params;
          Utility.analyticsEvent(route, route_params);
        }
      } catch(error) {
        // Don't block call if there was an error with analytics, but do log it
        console.log("ANALYTICS ERROR: ", error);
      }

      json.call(this, body);
  };
  next();
});
AMC
  • 39
  • 1
  • 8