0
votes

I'm developing a simple app which consumes a third-party API (Shopify REST API), the Shopify API is throttled to allow unlimited requests over time though a leaky bucket algorithm which is detailed in their documentation.

I'm using the Shopify-api-node module to make my API calls, it supports an autoLimit option out of the box which automatically limits the rate in which requests are made so you never have to think about the API call limits.

This works for most Node.js scripts, but in you run into trouble in the context of an Express app where you need to share the API instance across different routes through a session because the autoLimit option is only reliable with one single Shopify instance.

Here's my code (notice that I'm creating a new Shopify instance for each route):

var express = require("express");
var session = require("express-session");
var Shopify = require("shopify-api-node");

var app = express();
var port = 3000;

app.use(session({
  secret: "secret",
  resave: false,
  saveUninitialized: true
}));

app.get("/", (req, res) => {
  var shopName = req.session.shopName = req.session.shopName || req.query.shopName;
  var apiKey = req.session.apiKey = req.session.apiKey || req.query.apiKey;
  var password = req.session.password = req.session.password || req.query.password;

  var shopify = new Shopify({ shopName, apiKey, password, autoLimit: true });

  shopify.on("callLimits", (limits) => console.log(limits));

  var requests = Array.from({ length: 100 }, () => {
    return shopify.shop.get()
  });

  Promise.all(requests)
    .then((requestsRes) => {
      return res.send(JSON.stringify(requestsRes));
    })
    .catch((err) => {
      return res.status(500).send(err);
    });
});

app.get("/other-route", (req, res) => {
  var shopName = req.session.shopName = req.session.shopName || req.query.shopName;
  var apiKey = req.session.apiKey = req.session.apiKey || req.query.apiKey;
  var password = req.session.password = req.session.password || req.query.password;

  var shopify = new Shopify({ shopName, apiKey, password, autoLimit: true });

  shopify.on("callLimits", (limits) => console.log(limits));

  var requests = Array.from({ length: 100 }, () => {
    return shopify.product.list()
  });

  Promise.all(requests)
    .then((requestsRes) => {
      return res.send(JSON.stringify(requestsRes));
    })
    .catch((err) => {
      return res.status(500).send(err);
    });
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}!`)
});

Here's my package.json:

{
  "name": "third-party-api-limit-issue",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.16.4",
    "express-session": "^1.15.6",
    "shopify-api-node": "^2.18.1"
  }
}

When this script is run, go to this URL:

http://localhost:3000?shopName=liquify-app.myshopify.com&apiKey=9d1fae34bf670cc71230fee001486e82&password=dc6f650d5921eb40e7ab1e612e9dae7e

This'll make 100 requests to the Shopify API, store the API credentials in the session and display the shop JSON data.

This breaks if you open the same URL in two tabs at the same time, or open http://localhost:3000/other-route at the same time. This is happening because I can't figure out how to share the same Shopify API instance across my routes.

I need to be able to make as many calls to the API across my routes without running into the "Too many requests" error.

Any help would be greatly appreciated.

1

1 Answers

0
votes

I think you have an impedance mismatch here. Shopify has a limit. It tells you exactly where you are at. So you make a request. The response header you get back tells you how many more requests you can make. That is the number you use.

Your approach of saying, my code will limit itself, so as not to tickle the monster over there, is going to bomb out as that is not the way to really architect the request-response cycle.

Maybe try looking at the response you get before issuing another request. If you have hit the limits, pause while you wait for the bucket to recharge a little. Standard stuff. Works well.