0

I am working on a Node Express/Angular Civic project that is integrated with a 3rd party API. Each call costs me $1.00. I am funding a certain portion of it but then I would like the POST request to the 3rd party to stop. The reason is I don't want to end up with thousands of dollars of cost. 3rd party API doesn't support rate limit.

I can build a database, or create auth but I prefer not to go that route or integrate payment gateway. But I don't prefer any of these solutions for personal reasons. I came across a partial solution/node-module (express-rate-limit) which is limiting per IP Address. But this still doesn't solve the problem of stop making requests after it reaches x number of calls.

var RateLimit = require('express-rate-limit');

app.enable('trust proxy'); // only if you're behind a reverse proxy (Heroku, Bluemix, AWS if you use an ELB, custom Nginx setup, etc)

var apiLimiter = new RateLimit({
  windowMs: 15*60*1000, // 15 minutes
  max: 1, // 1 call every 15 minutes
  delayMs: 0 // disabled
});

// only apply to requests that begin with /api/
app.use('/api/', apiLimiter);

Are there any better solutions/best practices? It's just a side project that can help several people but I don't want to go broke by bearing the cost if many people start using it. Thanks!

Mihir Patel
  • 2,202
  • 3
  • 23
  • 36
  • The [express-rate-limit code](https://github.com/nfriedly/express-rate-limit/blob/master/lib/express-rate-limit.js) is *super* simple. Surely you could adapt it to your needs. Just modifying the `keyGenerator` function to return some constant (instead of the IP address) seems like it ought to work. – Jordan Running Feb 10 '17 at 16:51
  • @jordan: thank you for your answer. Do you mind showing a working example? – Mihir Patel Feb 10 '17 at 17:14
  • 1
    I don't happen to have an express app in front of me that I can just drop some code into, so no, sorry. (That's why I posted a comment, not an answer.) – Jordan Running Feb 10 '17 at 17:16
  • Okay, thanks! req.ip is what you are asking me to modify, right? So just set a counter and get return that counter? – Mihir Patel Feb 10 '17 at 17:18
  • Out of curiosity, what API costs $1 per call? – rsp Feb 10 '17 at 17:27
  • Ya i don't follow how to switch req.ip to a counter. – Mihir Patel Feb 10 '17 at 17:28
  • I never said counter. I said some constant, like `"this-key-will-never-change"`, or `"127.0.0.1"`. Basically you want to act as though every request is from the same IP. – Jordan Running Feb 10 '17 at 17:48

2 Answers2

0

Like Jordan said, set a const and return the constant. It will maintain state for rate limit across different IP address.

const keyIp = "127.0.0.1";

var limiter = new RateLimit({
  windowMs: 2147483647, // store key for node max
  max: 3, // limit each IP to 1 requests per windowMs
  // delayMs: 24*60*60*60, // delaying - 24 hours
  message: "Sorry, the maximum limit of 50 letters sent has been reached. Thank you for participating!",
  keyGenerator: function (req, res) {
        return keyIp;
  }
});

app.use('/api/letter', limiter);
Mihir Patel
  • 2,202
  • 3
  • 23
  • 36
0

It's a bit late, but I would suggest you to use more flexible library rate-limiter-flexible for rate limiting.

const rateLimiter = new RateLimiterMemory(
  {
      points: 1,
      duration: 15 * 60,
  }
);

router.get('/your-endpoint', (req, res, next) => {
  rateLimiter.consume(req.connection.remoteAddress)
    .then(() => {
      // Request to 3-rd party API here
    })
    .catch((rejRes) => {
      const secs = Math.round(rejRes.msBeforeNext / 1000) || 1;
      res.set('Retry-After', String(secs));
      res.status(429).send('Too Many Requests');
      // Or you can suggest to pay here and then use 
      // rateLimiter.reward() , see README
  });
});

You can also use Cluster, Redis or Mongo limiters from the same library, if you need it in cluster or distributed app

Animir
  • 1,121
  • 10
  • 23