<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: fiiv</title>
    <description>The latest articles on Forem by fiiv (@mtimofiiv).</description>
    <link>https://forem.com/mtimofiiv</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F134085%2F9bd40599-f373-46a5-a7b4-97279941dd5a.jpeg</url>
      <title>Forem: fiiv</title>
      <link>https://forem.com/mtimofiiv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mtimofiiv"/>
    <language>en</language>
    <item>
      <title>So I made a remote cache service</title>
      <dc:creator>fiiv</dc:creator>
      <pubDate>Thu, 29 May 2025 04:53:36 +0000</pubDate>
      <link>https://forem.com/mtimofiiv/so-i-made-a-remote-cache-service-452o</link>
      <guid>https://forem.com/mtimofiiv/so-i-made-a-remote-cache-service-452o</guid>
      <description>&lt;p&gt;There's so many data APIs out there (check out &lt;a href="https://publicapis.io/" rel="noopener noreferrer"&gt;Public APIs&lt;/a&gt; for some options) - near-infinite possibilities of great things you could build with them.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Recently, I was working on one myself - &lt;a href="https://bkkrentmap.com" rel="noopener noreferrer"&gt;Bangkok Rent Map&lt;/a&gt;. Unfortunately though, I ended up dealing with three issues that come with external APIs - &lt;strong&gt;&lt;em&gt;quota/cost per-request&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;speed of the requests&lt;/em&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;em&gt;marshalling the data I need from several different endpoints&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Horse&lt;/strong&gt; is the solution to these problems - it's a remote caching service!&lt;/p&gt;

&lt;h2&gt;
  
  
  So what does it do?
&lt;/h2&gt;

&lt;p&gt;Quite simply, Cache Horse allows you to cache every single HTTP request you make. Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET https://api.example.com/products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'd do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET https://api.cache.horse/get?uri=https://api.example.com/products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now the &lt;code&gt;GET /products&lt;/code&gt; request result will be nicely cached for you. Best part is, you can expire it at any time by adding a &lt;code&gt;refresh=true&lt;/code&gt; - or by automatically expiring it by setting an expiry time with &lt;code&gt;ttl=10000000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But wait - &lt;strong&gt;there's more&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;You can also batch requests together for your convenience. So, instead of making 2 separate requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET https://api.example.com/categories
GET https://api.example.com/products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can just combine it into one by sending it to us with a &lt;code&gt;|&lt;/code&gt; (pipe character) separator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET https://api.cache.horse/get?uris=https://api.example.com/categories|https://api.example.com/products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now these requests will be cached (individually) and returned to you like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The returned data is in an array - with the shape of URI string followed by the data:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.example.com/categories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;categories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shirts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pants&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;underwear&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.example.com/products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  So, how would I use this?
&lt;/h2&gt;

&lt;p&gt;Imagine you have a React component that needs to load both &lt;code&gt;GET /products&lt;/code&gt; and &lt;code&gt;GET /categories&lt;/code&gt;. It might look like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setProducts&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCategories&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;categories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;categories&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCategories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;aside&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Categories:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;aside&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;GET /products&lt;/code&gt; and &lt;code&gt;GET /categories&lt;/code&gt; directly, we will simply route those requests through the Cache Horse proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CACHE_HORSE_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;your-api-key&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CACHE_HORSE_API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.cache.horse/get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    We need to build a query parameter like so:
    uris=https://api.example.com/products|https://api.example.com/categories
  */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uris&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CACHE_HORSE_API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?uris=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;uris&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;api_key=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CACHE_HORSE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setProducts&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCategories&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;categories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;categories&lt;/span&gt; &lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setProducts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setCategories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;aside&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Categories:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;aside&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done! Now the request comes back in one go, and is now cached.&lt;/p&gt;

&lt;h2&gt;
  
  
  The advantage of Cache Horse
&lt;/h2&gt;

&lt;p&gt;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 (&lt;a href="https://github.com/cachehorse/benchmark" rel="noopener noreferrer"&gt;source code is available on Github&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Here, we pulled 4 data APIs we found. We chose these because they require no authentication and present unique challenges. For example, &lt;code&gt;world.openfoodfacts.org&lt;/code&gt; sends a massive payload of data back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------------+--------------------+---------------------+--------------------+
| 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 |
+----------------------------+--------------------+---------------------+--------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Your mileage may vary - so Cache Horse &lt;a href="https://cache.horse/signup" rel="noopener noreferrer"&gt;has a free plan to try it out for yourself&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Give it a try, and most importantly, let us know how it goes!&lt;/p&gt;

</description>
      <category>cache</category>
      <category>http</category>
      <category>api</category>
      <category>performance</category>
    </item>
    <item>
      <title>One simple trick to increase Stimulus.js legacy browser compatibility</title>
      <dc:creator>fiiv</dc:creator>
      <pubDate>Fri, 07 Feb 2025 05:04:25 +0000</pubDate>
      <link>https://forem.com/mtimofiiv/one-simple-trick-to-increase-stimulusjs-legacy-browser-compatibility-2oih</link>
      <guid>https://forem.com/mtimofiiv/one-simple-trick-to-increase-stimulusjs-legacy-browser-compatibility-2oih</guid>
      <description>&lt;p&gt;&lt;a href="https://stimulus.hotwired.dev" rel="noopener noreferrer"&gt;Stimulus.js&lt;/a&gt; states in its installation documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stimulus supports all evergreen, self-updating desktop and mobile browsers out of the box. Stimulus 3+ does not support Internet Explorer 11.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my latest project, I found that older iOS Safari browsers also had trouble with one particular part of the code. To understand it, here's the basic sample Stimulus &lt;code&gt;hello_controller.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stimulus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nameTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will lead to the following error message on older Safari browsers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SyntaxError: Unexpected token '='
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue is the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static" rel="noopener noreferrer"&gt;&lt;code&gt;static&lt;/code&gt; keyword&lt;/a&gt;. However, there's a simple workaround for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stimulus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nameTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, what did we do?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, the &lt;code&gt;static&lt;/code&gt; keyword to create static &lt;em&gt;fields&lt;/em&gt; &lt;a href="https://caniuse.com/mdn-javascript_classes_static_class_fields" rel="noopener noreferrer"&gt;is only available in Safari after 14.4 (2020)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6h3nfy02g23ymuxg1q4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6h3nfy02g23ymuxg1q4.png" alt="Compatibility table for static fields" width="628" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;BUT - &lt;code&gt;static&lt;/code&gt; has been around for much longer to define methods (including getter methods with the &lt;code&gt;get&lt;/code&gt; keyword), &lt;a href="https://caniuse.com/mdn-javascript_classes_static" rel="noopener noreferrer"&gt;in Safari 8.4+ (2014)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9u11gbcs6pdponvi5cue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9u11gbcs6pdponvi5cue.png" alt="Compatibility table for static keyword" width="624" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this simple trick (and honestly, it's not that much of a pain to refactor), you can extend support of your applications for an admittedly small segment of legacy audiences.&lt;/p&gt;

</description>
      <category>stimulus</category>
      <category>rails</category>
      <category>hotwired</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Everything you've (n)ever wanted to know about favicons</title>
      <dc:creator>fiiv</dc:creator>
      <pubDate>Tue, 26 Oct 2021 19:43:43 +0000</pubDate>
      <link>https://forem.com/mtimofiiv/everything-youve-never-wanted-to-know-about-favicons-4k5k</link>
      <guid>https://forem.com/mtimofiiv/everything-youve-never-wanted-to-know-about-favicons-4k5k</guid>
      <description>&lt;p&gt;A favicon is quite simply an &lt;strong&gt;icon&lt;/strong&gt;. The idea behind its inception was for browsers to use them to make a quick, easy to identify visual representation of a website in the browser's UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydfpdypv6lqmuvirmonk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydfpdypv6lqmuvirmonk.png" alt="Favicon as it appears in Firefox" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A little bit of history
&lt;/h2&gt;

&lt;p&gt;The first favicons were implemented in 1999 by Microsoft in their Internet Explorer browser. Since then, they were widely adopted by almost all browsers, and today it is commonplace to find favicons being displayed all over the browser, such as in bookmarks menus, window and tab headings, address bars, history, etc.&lt;/p&gt;

&lt;p&gt;Microsoft's original idea was to load that icon from a specific place relative to a website. If your website's address was &lt;code&gt;pokemon.com&lt;/code&gt;, Internet Explorer looked for it at &lt;code&gt;pokemon.com/favicon.ico&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This original implementation worked alright at first. But at the time, lots of websites were hosted with companies like Geocities, Angelfire and Tripod. They would give your website an address like &lt;code&gt;tripod.com/my-pokemon-website&lt;/code&gt;. The problem with this was that the browser would always use Tripod's favicon located at &lt;code&gt;tripod.com/favicon.ico&lt;/code&gt; and not one that the website's author might want to show!&lt;/p&gt;

&lt;p&gt;Eventually, the people responsible for standardising HTML on the web, the World Wide Web Consortium (W3C), created a specification for how both browsers and website authors should use these icons. In the HTML of the page, an HTML tag would be included, telling the browser where to find that icon.&lt;/p&gt;

&lt;p&gt;But, since the original Internet Explorer needed to work because lots of websites had been using it, browsers began supporting them both.&lt;/p&gt;

&lt;h2&gt;
  
  
  The advent of the smartphone (and other use-cases)
&lt;/h2&gt;

&lt;p&gt;A lot of things changed when the smartphone became mainstream. One notable thing that changed for favicons was that people could save websites to their home screen. But the favicon.ico icons up to this point were all too small – sometimes they were as tiny as 16 pixels! These icons were supposed to be shown next to app icons, which are much more high fidelity images – up to 10 times bigger, in fact!&lt;/p&gt;

&lt;p&gt;Android and iOS use different icon sizes. Also, Windows began using yet another icon type for displaying websites in its Metro tile display.&lt;/p&gt;

&lt;h2&gt;
  
  
  The chaos is real
&lt;/h2&gt;

&lt;p&gt;As a result of all these different specifications, there are now a lot of places to look for favicons. And the different icons you can get will vary depending on which one you request. Some sites will have some but not others!&lt;/p&gt;

&lt;p&gt;The list includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;yoursite.com/favicon.ico&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;In the site's HTML code&lt;/li&gt;
&lt;li&gt;In the site's web manifest file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some sites end up with dozens of different icons. These vary, from the usually tiny favicon.ico to the higher resolution icon used by iOS's home screen feature. Additionally, a lot can go wrong with fetching these.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There might not be any icons at all.&lt;/li&gt;
&lt;li&gt;The icons are specified but don't exist (return a 404).&lt;/li&gt;
&lt;li&gt;The icons are the wrong size.&lt;/li&gt;
&lt;li&gt;The icons are the wrong format for where you might want to use them.&lt;/li&gt;
&lt;li&gt;The icons are corrupted or broken.&lt;/li&gt;
&lt;li&gt;The icons specified might not have the one you might want to use.&lt;/li&gt;
&lt;li&gt;The web server could be misconfigured (for example, resulting in redirect loops).&lt;/li&gt;
&lt;li&gt;The web server could be slow to respond with the icons or the files specifying the icons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, requesting something as simple as a tiny little website icon can be a very complicated task! Clearly there is no one-size-fits-all solution to grab the icons.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to make sure your website has all its icons
&lt;/h2&gt;

&lt;p&gt;So all that being said, how do you make sure your website has all the different icons it should have, and they're properly referenced in your HTML markup?&lt;/p&gt;

&lt;p&gt;Quite simply, the best way to do this is to use a service that generates the markup and icons for you. The best ones I've found so far are &lt;a href="https://realfavicongenerator.net/" rel="noopener noreferrer"&gt;realfavicongenerator.net&lt;/a&gt; and &lt;a href="https://www.websiteplanet.com/webtools/favicon-generator" rel="noopener noreferrer"&gt;Website Planet's Favicon Generator&lt;/a&gt;. You can upload an icon image, specify some preferences, and it will generate a ZIP file with your icons properly sized and set up, as well as the HTML to insert into your site's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;If you use Webpack, you could also try out the plugin &lt;a href="https://github.com/jantimon/favicons-webpack-plugin" rel="noopener noreferrer"&gt;jantimon/favicons-webpack-plugin&lt;/a&gt; that will automatically work its magic to give you favicons.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use other sites' favicons on your site
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;You could build your own API or serverless function to fetch a site's favicons. But, I gotta tell you from experience, there's a lot of edge cases and weirdness here.&lt;/li&gt;
&lt;li&gt;You could always load &lt;code&gt;domain.com/favicon.ico&lt;/code&gt;, but be prepared for 404s.&lt;/li&gt;
&lt;li&gt;You could use an existing service. &lt;strong&gt;Shameless plug&lt;/strong&gt; – I built &lt;a href="https://icon.horse" rel="noopener noreferrer"&gt;Icon Horse&lt;/a&gt; for this purpose, you can use it free (or upgrade for additional features). Other services also exist, like &lt;a href="https://favicongrabber.com/" rel="noopener noreferrer"&gt;Favicon Grabber&lt;/a&gt;. Some search engines like Google, Bing and DuckDuckGo also have APIs for this, but since they're intended for internal use and are undocumented and unsupported, be prepared that they might not work like you want or they might stop working at any moment.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Other cool stuff
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://iconmap.io/" rel="noopener noreferrer"&gt;Iconmap.io&lt;/a&gt;&lt;/strong&gt;: a massive map of favicons from all over the web&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://realfavicongenerator.net/" rel="noopener noreferrer"&gt;realfavicongenerator.net&lt;/a&gt;&lt;/strong&gt;: the best tool I've found for generating favicons for your website&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.websiteplanet.com/webtools/favicon-generator" rel="noopener noreferrer"&gt;Website Planet's Favicon Generator&lt;/a&gt;&lt;/strong&gt;: another great tool for generating favicons for your site&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://icon.horse" rel="noopener noreferrer"&gt;Icon Horse&lt;/a&gt;&lt;/strong&gt;: my super cool favicon grabber API&lt;/li&gt;
&lt;li&gt;Let me know if you can think of a link for here and I'll add it&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>technology</category>
      <category>web</category>
    </item>
    <item>
      <title>How &amp; why I built a favicon fetching service</title>
      <dc:creator>fiiv</dc:creator>
      <pubDate>Fri, 03 Sep 2021 20:27:28 +0000</pubDate>
      <link>https://forem.com/mtimofiiv/how-why-i-built-a-favicon-fetching-service-32nl</link>
      <guid>https://forem.com/mtimofiiv/how-why-i-built-a-favicon-fetching-service-32nl</guid>
      <description>&lt;p&gt;A few days ago, I launched &lt;a href="https://icon.horse" rel="noopener noreferrer"&gt;Icon Horse&lt;/a&gt;, which is a simple, free API to quickly get the favicon of any website. The launch itself went really well, and I had a lot of fun building it.&lt;/p&gt;

&lt;p&gt;But inside the seemingly simple but glamorous life of favicons, there is a lot of complexity. I thought I'd share some of how it works with you all, and some of the hoops I had to hop to build Icon Horse.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a favicon, really?
&lt;/h2&gt;

&lt;p&gt;The year was 1999. Britney Spears and Eminem were at the top of the charts, the world was introduced to Napster for the first time, and the "browser war" was heating up with Microsoft releasing Internet Explorer 5.&lt;/p&gt;

&lt;p&gt;One of IE5's new features was the favicon, or a small icon which was displayed in the "Favourites" menus/bars next to the title of the site someone bookmarked. Things were simple back then, and if the favicon existed, it would be loaded from the site's root like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://mysite.com/favicon.ico
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since then, a number of new circumstances have come up requiring new types of icons – one example is the advent of the smart phone, which allowed people to save a website shortcut on their home screen. But since favicons tended to be low resolution, a number of diverging standards came about and added these on both Android and iOS.&lt;/p&gt;

&lt;p&gt;Now while technically a &lt;em&gt;shortcut icon&lt;/em&gt;, they are usually lumped in with favicons in general internet terminology and also during the process to create them.&lt;/p&gt;

&lt;p&gt;Today, there are three main places to look for icons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;https://mysite.com/favicon.ico&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The HTML of the site in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A separate Web App Manifest file which is specified in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  So what's the problem?
&lt;/h2&gt;

&lt;p&gt;I'm currently working on a tool called &lt;a href="https://meetingcanary.app" rel="noopener noreferrer"&gt;Meeting Canary&lt;/a&gt;, from which a note-taking interface is displayed for a given calendar meeting. Many meetings have links to relevant places, such as video conferencing apps (especially since the COVID-19 situation has moved many people to remote work).&lt;/p&gt;

&lt;p&gt;I decided it would be nice to render a small icon next to the links, as a way to tease the content to my users:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FE8pFHL8VIAAsUvH%3Fformat%3Djpg%26name%3Dlarge" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FE8pFHL8VIAAsUvH%3Fformat%3Djpg%26name%3Dlarge" alt="A meeting invite often has a link" width="1518" height="784"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After hunting for a good way to fetch icons for a given link, I found one that was a JSON API endpoint, but I wasn't very satisfied with this solution – I did &lt;strong&gt;not&lt;/strong&gt; want to complicate my life by using it, since I still needed to write code to figure out the &lt;em&gt;best&lt;/em&gt; icon to display from the list I got back.&lt;/p&gt;

&lt;p&gt;Also, I did not find a single service that provided &lt;strong&gt;fallbacks&lt;/strong&gt;, or an icon that would be shown if the site is unreachable or if it had no icons at all.&lt;/p&gt;

&lt;p&gt;After all, &lt;a href="https://gravatar.com" rel="noopener noreferrer"&gt;Gravatar&lt;/a&gt; does this very well for email addresses, and it was strange that no one had done this with favicons yet! So I got to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  It was really simple to make, right?
&lt;/h2&gt;

&lt;p&gt;When it comes to standards, the web is pure chaos. When building a product to fetch favicons, you should expect no mercy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febmsntdrjux2tyyoo462.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febmsntdrjux2tyyoo462.jpg" alt="The web is a hive of scum and villainy" width="640" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some sites have no icons at all. Some sites have only a few of the icons from the spec. Some sites use strange sizes. Some sites don't even bother to tell you the size (in pixels) of the icons.&lt;/p&gt;

&lt;p&gt;Some sites have completely broken DNS or server issues (like infinite redirect loops). Some send confusing or broken headers. Some sites serve invalid HTML or JSON. Some sites have &lt;code&gt;404 Not Found&lt;/code&gt; errors on their icons. Some sites used weird caching schemes. Some don't specify a MIME type or a file extension so you have to parse the actual image to know what you're dealing with.&lt;/p&gt;

&lt;p&gt;The list of difficulties goes on – for example one prominent clothing retailer's icon is simply not loadable because they have a nasty bug in their site's redirects and headers meaning you cannot simply &lt;code&gt;redirect: 'follow'&lt;/code&gt; your way to the HTML page, but must chain one request after the other manually.&lt;/p&gt;

&lt;p&gt;But I persevered through all those.&lt;/p&gt;

&lt;p&gt;I knew there was going to be even more edge cases I hadn't considered in the future, so it was very important to build in functionality for fallback images. I never wanted to serve a broken image or a timeout.&lt;/p&gt;

&lt;p&gt;I also had to make some decisions. Since my service was making icons available to all who wanted them, there was no telling the different use cases they would be serving. So I made the assumption that the best icon to serve would be the most high fidelity image. Also, for some use cases (such as React Native), SVG format icons would not work out of the box, and needed something like &lt;a href="https://github.com/react-native-svg/react-native-svg" rel="noopener noreferrer"&gt;react-native-svg&lt;/a&gt; to get them to load, so I stuck to raster formats only (for the first version at least, I plan to open up SVGs as well in the future via a query parameter).&lt;/p&gt;

&lt;p&gt;And finally, pulling all this content is time intensive. Consider that to serve an icon, one needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load &lt;code&gt;https://asite.com&lt;/code&gt;'s HTML site&lt;/li&gt;
&lt;li&gt;Parse the HTML and do a query lookup to get relevant icons and the manifest file&lt;/li&gt;
&lt;li&gt;Load the manifest file and parse it&lt;/li&gt;
&lt;li&gt;Check for the existence of the &lt;code&gt;https://asite.com/favicon.ico&lt;/code&gt; icon&lt;/li&gt;
&lt;li&gt;Merge all these icons together in a list and sort them based on a &lt;em&gt;best&lt;/em&gt; to &lt;em&gt;worst&lt;/em&gt; criteria, while also making sure the icons themselves are reachable (and don't &lt;code&gt;404 Not Found&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If all this fails, generate a JPEG file on the fly as a fallback&lt;/li&gt;
&lt;li&gt;Serve the icon to the user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So when dealing with a slow server, it's possible that loading that icon could take quite a long time. To help with that, Icon Horse must cache the resulting list of icons and the resulting chosen icon.&lt;/p&gt;

&lt;p&gt;I wanted to keep the functionality super simple, so when someone queried my API to get an icon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://icon.horse/icon/dev.to
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ended up with just the icon:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ficon.horse%2Ficon%2Fdev.to" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ficon.horse%2Ficon%2Fdev.to" alt="dev.to icon" width="32" height="31"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;From the very beginning, I knew I wanted to use a serverless approach to this service. The landing page is built out of Next.js and the service itself sits in a lambda function hosted on Amazon.&lt;/p&gt;

&lt;p&gt;After struggling a little bit with getting the environment set up (it had to have image processing capability as well as a few other things), I managed to get it working and running properly.&lt;/p&gt;

&lt;p&gt;And there you have it. I launched on &lt;a href="https://www.producthunt.com/posts/icon-horse" rel="noopener noreferrer"&gt;ProductHunt&lt;/a&gt; (among other places) and was surprised by the overwhelming positive reception – I got almost 200 upvotes and almost 1000 unique visitors. What surprised me the most is how a favicon fetcher service I thought would be niche and developer only was actually really well understood by all kinds of people.&lt;/p&gt;

&lt;p&gt;I learned a lot about the weird world of favicons and solved my own need, but above all had a lot of fun doing it.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>learning</category>
      <category>writing</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
