<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Cache Invalidation - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Cache Invalidation - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 25 May 2026 05:06:27 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/cache-invalidation/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Scalable URL Shortener with Distributed Caching Using Redis ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, we'll build a scalable URL shortening service using Node.js and Redis. This service will leverage distributed caching to handle high traffic efficiently, reduce latency, and scale seamlessly. We'll explore key concepts such as consi... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-scalable-url-shortener-with-distributed-caching-using-redis/</link>
                <guid isPermaLink="false">673cab723502f2a00d61ece1</guid>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ caching ]]>
                    </category>
                
                    <category>
                        <![CDATA[ consistent hashing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cache Invalidation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sharding ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Url Shortener ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Birkaran Sachdev ]]>
                </dc:creator>
                <pubDate>Tue, 19 Nov 2024 15:14:58 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/b9-odQi5oDo/upload/934c8b697ce5ce612a217d47ddf5430d.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, we'll build a scalable URL shortening service using Node.js and Redis. This service will leverage distributed caching to handle high traffic efficiently, reduce latency, and scale seamlessly. We'll explore key concepts such as consistent hashing, cache invalidation strategies, and sharding to ensure the system remains fast and reliable.</p>
<p>By the end of this guide, you'll have a fully functional URL shortener service that uses distributed caching to optimize performance. We'll also create an interactive demo where users can input URLs and see real-time metrics like cache hits and misses.</p>
<h3 id="heading-what-you-will-learn">What You Will Learn</h3>
<ul>
<li><p>How to build a URL shortener service using <strong>Node.js</strong> and <strong>Redis</strong>.</p>
</li>
<li><p>How to implement <strong>distributed caching</strong> to optimize performance.</p>
</li>
<li><p>Understanding <strong>consistent hashing</strong> and <strong>cache invalidation strategies</strong>.</p>
</li>
<li><p>Using <strong>Docker</strong> to simulate multiple Redis instances for sharding and scaling.</p>
</li>
</ul>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before starting, make sure you have the following installed:</p>
<ul>
<li><p>Node.js (v14 or higher)</p>
</li>
<li><p>Redis</p>
</li>
<li><p>Docker</p>
</li>
<li><p>Basic knowledge of JavaScript, Node.js, and Redis.</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-project-overview">Project Overview</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-setting-up-the-project">Step 1: Setting Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-setting-up-redis-instances">Step 2: Setting Up Redis Instances</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-implementing-the-url-shortener-service">Step 3: Implementing the URL Shortener Service</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-implementing-cache-invalidation">Step 4: Implementing Cache Invalidation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-monitoring-cache-metrics">Step 5: Monitoring Cache Metrics</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-testing-the-application">Step 6: Testing the Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion-what-youve-learned">Conclusion: What You’ve Learned</a></p>
</li>
</ul>
<h2 id="heading-project-overview">Project Overview</h2>
<p>We'll build a URL shortener service where:</p>
<ol>
<li><p>Users can shorten long URLs and retrieve the original URLs.</p>
</li>
<li><p>The service uses <strong>Redis caching</strong> to store mappings between shortened URLs and original URLs.</p>
</li>
<li><p>The cache is distributed across multiple Redis instances to handle high traffic.</p>
</li>
<li><p>The system will demonstrate <strong>cache hits</strong> and <strong>misses</strong> in real-time.</p>
</li>
</ol>
<h3 id="heading-system-architecture">System Architecture</h3>
<p>To ensure scalability and performance, we'll divide our service into the following components:</p>
<ol>
<li><p><strong>API Server</strong>: Handles requests for shortening and retrieving URLs.</p>
</li>
<li><p><strong>Redis Caching Layer</strong>: Uses multiple Redis instances for distributed caching.</p>
</li>
<li><p><strong>Docker</strong>: Simulates a distributed environment with multiple Redis containers.</p>
</li>
</ol>
<h2 id="heading-step-1-setting-up-the-project">Step 1: Setting Up the Project</h2>
<p>Let's set up our project by initializing a Node.js application:</p>
<pre><code class="lang-javascript">mkdir scalable-url-shortener
cd scalable-url-shortener
npm init -y
</code></pre>
<p>Now, install the necessary dependencies:</p>
<pre><code class="lang-javascript">npm install express redis shortid dotenv
</code></pre>
<ul>
<li><p><code>express</code>: A lightweight web server framework.</p>
</li>
<li><p><code>redis</code>: To handle caching.</p>
</li>
<li><p><code>shortid</code>: For generating short, unique IDs.</p>
</li>
<li><p><code>dotenv</code>: For managing environment variables.</p>
</li>
</ul>
<p>Create a <strong>.env</strong> file in the root of your project:</p>
<pre><code class="lang-javascript">PORT=<span class="hljs-number">3000</span>
REDIS_HOST_1=localhost
REDIS_PORT_1=<span class="hljs-number">6379</span>
REDIS_HOST_2=localhost
REDIS_PORT_2=<span class="hljs-number">6380</span>
REDIS_HOST_3=localhost
REDIS_PORT_3=<span class="hljs-number">6381</span>
</code></pre>
<p>These variables define the Redis hosts and ports we'll be using.</p>
<h2 id="heading-step-2-setting-up-redis-instances">Step 2: Setting Up Redis Instances</h2>
<p>We'll use Docker to simulate a distributed environment with multiple Redis instances.</p>
<p>Run the following commands to start three Redis containers:</p>
<pre><code class="lang-javascript">docker run -p <span class="hljs-number">6379</span>:<span class="hljs-number">6379</span> --name redis1 -d redis
docker run -p <span class="hljs-number">6380</span>:<span class="hljs-number">6379</span> --name redis2 -d redis
docker run -p <span class="hljs-number">6381</span>:<span class="hljs-number">6379</span> --name redis3 -d redis
</code></pre>
<p>This will set up three Redis instances running on different ports. We'll use these instances to implement <strong>consistent hashing</strong> and sharding.</p>
<h2 id="heading-step-3-implementing-the-url-shortener-service">Step 3: Implementing the URL Shortener Service</h2>
<p>Let's create our main application file, <strong>index.js</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> redis = <span class="hljs-built_in">require</span>(<span class="hljs-string">'redis'</span>);
<span class="hljs-keyword">const</span> shortid = <span class="hljs-built_in">require</span>(<span class="hljs-string">'shortid'</span>);

<span class="hljs-keyword">const</span> app = express();
app.use(express.json());

<span class="hljs-keyword">const</span> redisClients = [
  redis.createClient({ <span class="hljs-attr">host</span>: process.env.REDIS_HOST_1, <span class="hljs-attr">port</span>: process.env.REDIS_PORT_1 }),
  redis.createClient({ <span class="hljs-attr">host</span>: process.env.REDIS_HOST_2, <span class="hljs-attr">port</span>: process.env.REDIS_PORT_2 }),
  redis.createClient({ <span class="hljs-attr">host</span>: process.env.REDIS_HOST_3, <span class="hljs-attr">port</span>: process.env.REDIS_PORT_3 })
];

<span class="hljs-comment">// Hash function to distribute keys among Redis clients</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRedisClient</span>(<span class="hljs-params">key</span>) </span>{
  <span class="hljs-keyword">const</span> hash = key.split(<span class="hljs-string">''</span>).reduce(<span class="hljs-function">(<span class="hljs-params">acc, char</span>) =&gt;</span> acc + char.charCodeAt(<span class="hljs-number">0</span>), <span class="hljs-number">0</span>);
  <span class="hljs-keyword">return</span> redisClients[hash % redisClients.length];
}

<span class="hljs-comment">// Endpoint to shorten a URL</span>
app.post(<span class="hljs-string">'/shorten'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { url } = req.body;
  <span class="hljs-keyword">if</span> (!url) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send(<span class="hljs-string">'URL is required'</span>);

  <span class="hljs-keyword">const</span> shortId = shortid.generate();
  <span class="hljs-keyword">const</span> redisClient = getRedisClient(shortId);

  <span class="hljs-keyword">await</span> redisClient.set(shortId, url);
  res.json({ <span class="hljs-attr">shortUrl</span>: <span class="hljs-string">`http://localhost:<span class="hljs-subst">${process.env.PORT}</span>/<span class="hljs-subst">${shortId}</span>`</span> });
});

<span class="hljs-comment">// Endpoint to retrieve the original URL</span>
app.get(<span class="hljs-string">'/:shortId'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { shortId } = req.params;
  <span class="hljs-keyword">const</span> redisClient = getRedisClient(shortId);

  redisClient.get(shortId, <span class="hljs-function">(<span class="hljs-params">err, url</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err || !url) {
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).send(<span class="hljs-string">'URL not found'</span>);
    }
    res.redirect(url);
  });
});

app.listen(process.env.PORT, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on port <span class="hljs-subst">${process.env.PORT}</span>`</span>);
});
</code></pre>
<p>As you can see in this code, we have:</p>
<ol>
<li><p><strong>Consistent Hashing</strong>:</p>
<ul>
<li><p>We distribute keys (shortened URLs) across multiple Redis clients using a simple hash function.</p>
</li>
<li><p>The hash function ensures that URLs are distributed evenly across the Redis instances.</p>
</li>
</ul>
</li>
<li><p><strong>URL Shortening</strong>:</p>
<ul>
<li><p>The <strong>/shorten</strong> endpoint accepts a long URL and generates a short ID using the <strong>shortid</strong> library.</p>
</li>
<li><p>The shortened URL is stored in one of the Redis instances using our hash function.</p>
</li>
</ul>
</li>
<li><p><strong>URL Redirection</strong>:</p>
<ul>
<li><p>The <strong>/:shortId</strong> endpoint retrieves the original URL from the cache and redirects the user.</p>
</li>
<li><p>If the URL is not found in the cache, a <strong>404</strong> response is returned.</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-step-4-implementing-cache-invalidation">Step 4: Implementing Cache Invalidation</h2>
<p>In a real-world application, URLs may expire or change over time. To handle this, we need to implement <strong>cache invalidation</strong>.</p>
<h3 id="heading-adding-expiry-to-cached-urls">Adding Expiry to Cached URLs</h3>
<p>Let's modify our <strong>index.js</strong> file to set an expiration time for each cached entry:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Endpoint to shorten a URL with expiration</span>
app.post(<span class="hljs-string">'/shorten'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { url, ttl } = req.body; <span class="hljs-comment">// ttl (time-to-live) is optional</span>
  <span class="hljs-keyword">if</span> (!url) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send(<span class="hljs-string">'URL is required'</span>);

  <span class="hljs-keyword">const</span> shortId = shortid.generate();
  <span class="hljs-keyword">const</span> redisClient = getRedisClient(shortId);

  <span class="hljs-keyword">await</span> redisClient.set(shortId, url, <span class="hljs-string">'EX'</span>, ttl || <span class="hljs-number">3600</span>); <span class="hljs-comment">// Default TTL of 1 hour</span>
  res.json({ <span class="hljs-attr">shortUrl</span>: <span class="hljs-string">`http://localhost:<span class="hljs-subst">${process.env.PORT}</span>/<span class="hljs-subst">${shortId}</span>`</span> });
});
</code></pre>
<ul>
<li><p><strong>TTL (Time-To-Live)</strong>: We set a default expiration time of <strong>1 hour</strong> for each shortened URL. You can customize the TTL for each URL if needed.</p>
</li>
<li><p><strong>Cache Invalidation</strong>: When the TTL expires, the entry is automatically removed from the cache.</p>
</li>
</ul>
<h2 id="heading-step-5-monitoring-cache-metrics">Step 5: Monitoring Cache Metrics</h2>
<p>To monitor <strong>cache hits</strong> and <strong>misses</strong>, we’ll add some logging to our endpoints in <strong>index.js</strong>:</p>
<pre><code class="lang-javascript">app.get(<span class="hljs-string">'/:shortId'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { shortId } = req.params;
  <span class="hljs-keyword">const</span> redisClient = getRedisClient(shortId);

  redisClient.get(shortId, <span class="hljs-function">(<span class="hljs-params">err, url</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err || !url) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Cache miss for key: <span class="hljs-subst">${shortId}</span>`</span>);
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).send(<span class="hljs-string">'URL not found'</span>);
    }
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Cache hit for key: <span class="hljs-subst">${shortId}</span>`</span>);
    res.redirect(url);
  });
});
</code></pre>
<p>Here’s what’s going on in this code:</p>
<ul>
<li><p><strong>Cache Hits</strong>: If a URL is found in the cache, it’s a cache hit.</p>
</li>
<li><p><strong>Cache Misses</strong>: If a URL is not found, it’s a cache miss.</p>
</li>
<li><p>This logging will help you monitor the performance of your distributed cache.</p>
</li>
</ul>
<h2 id="heading-step-6-testing-the-application">Step 6: Testing the Application</h2>
<ol>
<li><strong>Start your Redis instances</strong>:</li>
</ol>
<pre><code class="lang-javascript">docker start redis1 redis2 redis3
</code></pre>
<ol start="2">
<li><strong>Run the Node.js server</strong>:</li>
</ol>
<pre><code class="lang-javascript">node index.js
</code></pre>
<ol start="3">
<li><p><strong>Test the endpoints</strong> using <code>curl</code> or Postman:</p>
<ul>
<li><p>Shorten a URL:</p>
<pre><code class="lang-javascript">  POST http:<span class="hljs-comment">//localhost:3000/shorten</span>
  Body: { <span class="hljs-string">"url"</span>: <span class="hljs-string">"https://example.com"</span> }
</code></pre>
</li>
<li><p>Access the shortened URL:</p>
<pre><code class="lang-javascript">  GET http:<span class="hljs-comment">//localhost:3000/{shortId}</span>
</code></pre>
</li>
</ul>
</li>
</ol>
<h2 id="heading-conclusion-what-youve-learned">Conclusion: What You’ve Learned</h2>
<p>Congratulations! You’ve successfully built a scalable <strong>URL shortener service</strong> with <strong>distributed caching</strong> using Node.js and Redis. Throughout this tutorial, you’ve learned how to:</p>
<ol>
<li><p>Implement <strong>consistent hashing</strong> to distribute cache entries across multiple Redis instances.</p>
</li>
<li><p>Optimize your application with <strong>cache invalidation strategies</strong> to keep data up-to-date.</p>
</li>
<li><p>Use <strong>Docker</strong> to simulate a distributed environment with multiple Redis nodes.</p>
</li>
<li><p>Monitor <strong>cache hits and misses</strong> to optimize performance.</p>
</li>
</ol>
<h3 id="heading-next-steps">Next Steps:</h3>
<ul>
<li><p><strong>Add a Database</strong>: Store URLs in a database for persistence beyond the cache.</p>
</li>
<li><p><strong>Implement Analytics</strong>: Track click counts and analytics for shortened URLs.</p>
</li>
<li><p><strong>Deploy to the Cloud</strong>: Deploy your application using Kubernetes for auto-scaling and resilience.</p>
</li>
</ul>
<p>Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
