There’s so many data APIs out there (check out Public APIs ↗ for some options) - near-infinite possibilities of great things you could build with them.
Building data-driven applications is fun and challenging at the same time. One of the challenges I have encountered again and again in building these types of applications concern grabbing the data itself.
Recently, I was working on one myself - Bangkok Rent Map ↗. Unfortunately though, I ended up dealing with three issues that come with external APIs - quota/cost per-request, speed of the requests, and marshalling the data I need from several different endpoints.
Cache Horse is the solution to these problems - it’s a remote caching service!
So what does it do?
Quite simply, Cache Horse allows you to cache every single HTTP request you make. Instead of this:
GET https://api.example.com/products
You’d do this:
GET https://api.cache.horse/get?uri=https://api.example.com/products
That’s it! Now the GET /products
request result will be nicely cached for you. Best part is, you can expire it at any time by adding a refresh=true
- or by automatically expiring it by setting an expiry time with ttl=10000000
.
But wait - there’s more!
You can also batch requests together for your convenience. So, instead of making 2 separate requests:
GET https://api.example.com/categories GET https://api.example.com/products
You can just combine it into one by sending it to us with a |
(pipe character) separator:
GET https://api.cache.horse/get?uris=https://api.example.com/categories|https://api.example.com/products
Now these requests will be cached (individually) and returned to you like so:
// The returned data is in an array - with the shape of URI string followed by the data: [ "https://api.example.com/categories", { "categories": [ "shirts", "pants", "underwear" ] }, "https://api.example.com/products", { "products": [ { "id": 1, ... }, { "id": 2, ... }, ... ] } ]
So, how would I use this?
Imagine you have a React component that needs to load both GET /products
and GET /categories
. It might look like so:
import { useState, useEffect } from 'react' const getData = async (endpoint) => { const request = await fetch(`https://api.example.com/${endpoint}`) return request.json() } const MyComponent = () => { const [ products, setProducts ] = useState([]) const [ categories, setCategories ] = useState([]) useEffect(() => { getData('products').then(({ products }) => setProducts(products)) getData('categories').then(({ categories }) => setCategories(categories)) }, []) return ( <div> <aside> <p>Categories:</p> {categories.map((category) => (<p>{category}</p>))} </aside> <ul> {products.map((product) => (<li>{product.name}</li>))} </ul> </div> ) } export default MyComponent
The only thing we really need to do to make this work with Cache Horse is to re-write our fetching code. Instead of grabbing GET /products
and GET /categories
directly, we will simply route those requests through the Cache Horse proxy:
import { useState, useEffect } from 'react' const CACHE_HORSE_API_KEY = '<your-api-key>' const CACHE_HORSE_API = 'https://api.cache.horse/get' const getData = async (endpoints) => { /* We need to build a query parameter like so: uris=https://api.example.com/products|https://api.example.com/categories */ const uris = endpoints.map(path => encodeURIComponent(`https://api.example.com/${path}`)) const request = await fetch(`${CACHE_HORSE_API}?uris=${uris.join('|')}&api_key=${CACHE_HORSE_API_KEY}`) return request.json() } const MyComponent = () => { const [ products, setProducts ] = useState([]) const [ categories, setCategories ] = useState([]) useEffect(() => { getData(['products', 'categories']).then(([ , products, , categories ]) => { setProducts(products) setCategories(categories) }) }, []) return ( <div> <aside> <p>Categories:</p> {categories.map((category) => (<p>{category}</p>))} </aside> <ul> {products.map((product) => (<li>{product.name}</li>))} </ul> </div> ) } export default MyComponent
Done! Now the request comes back in one go, and is now cached.
The advantage of Cache Horse
Obviously, there’s a lot of variables in measuring the impact of using a remote cache like Cache Horse. But to show it off a bit, here’s a quick and easy benchmark we put together to show the difference (source code is available on Github ↗).
Here, we pulled 4 data APIs we found. We chose these because they require no authentication and present unique challenges. For example, world.openfoodfacts.org
sends a massive payload of data back.
+----------------------------+--------------------+---------------------+--------------------+ | Domain | Raw request time | Cached request time | Time savings | +----------------------------+--------------------+---------------------+--------------------+ | api.domainsdb.info | 1.5706900240002142 | 0.3069977329996618 | 1.2636922910005524 | | world.openfoodfacts.org | 27.751143773000877 | 1.1871313930005272 | 26.56401238000035 | | api.openbrewerydb.org | 0.9226720890001161 | 0.3071872029995575 | 0.6154848860005586 | | api.carbonintensity.org.uk | 0.8186340400006884 | 0.4737608600007661 | 0.3448731799999223 | +----------------------------+--------------------+---------------------+--------------------+
These requests were done separately to our API, but of course, if you batch them using our multiple HTTP request cache, you can expect to get all the data back in one big request.
Additionally, since we query things only if the cache has expired (or, you want to force a refresh), the quota problem with external API services is reduced. This can help with data intended to be fresh, but not too fresh.
Your mileage may vary - so Cache Horse has a free plan to try it out for yourself.
Give it a try, and most importantly, let us know how it goes!