<?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[ Redis - 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[ Redis - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 05 Jun 2026 10:27:04 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/redis/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Persist State in Time-Series Models with Docker and Redis ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever built a brilliant time-series model, one that could forecast sales or predict stock prices, only to watch it fail in the real world? Well, this is a common frustration. Your model works perfectly on your machine, but the moment you depl... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-persist-state-in-time-series-models-with-docker-and-redis/</link>
                <guid isPermaLink="false">68e70d838fa4b92d9a027ebe</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Time Series Forecasting ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PersistentVolumes ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chirag Agrawal ]]>
                </dc:creator>
                <pubDate>Thu, 09 Oct 2025 01:18:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759972706788/66d45afa-f86b-4365-8a55-8b6873df718b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever built a brilliant time-series model, one that could forecast sales or predict stock prices, only to watch it fail in the real world? Well, this is a common frustration. Your model works perfectly on your machine, but the moment you deploy it in a Docker container, it seems to develop amnesia. It forgets everything it knew yesterday, making its predictions for tomorrow useless.</p>
<p>Don’t worry. This isn't likely a flaw in your model. It's a clash between how time-series models and Docker containers are designed to work.</p>
<p>Time-series models are all about memory. They need to remember the past to predict the future. But Docker containers are built to be stateless and forgetful, wiping their memory clean with every restart. This fundamental conflict can turn a powerful model into a worthless one in production.</p>
<p>In this article, we’ll solve that problem. We're going to give your time-series model a permanent memory. You'll learn how to build a production-ready prediction service that uses Redis as an external brain and Docker volumes to ensure that memory survives any restart. We'll walk through a hands-on example, step-by-step, so you can learn how to build a system that is both intelligent and incredibly reliable.</p>
<h3 id="heading-what-well-cover">What we’ll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-who-is-this-guide-for">Who is This Guide For?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-problem">Understanding the Problem</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-so-what-is-a-time-series-model">So, what is a time-series model?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-1-containers-are-ephemeral-by-design">1. Containers are ephemeral by design</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-lost-context-between-predictions">2. Lost context between predictions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-model-amnesia-on-restart">3. Model amnesia on restart</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-the-solution-external-state-store">The Solution: External State Store</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hands-on-implementation">Hands-On Implementation</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-start-with-the-broken-approach">Start with the broken approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-fix-it-with-volumes">How to fix it with volumes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-the-code-handles-state">How the code handles state</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-test-the-health-endpoint">Test the health endpoint</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-what-about-scaling">What About Scaling?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-horizontal-scaling-with-redis-cluster">Horizontal scaling with Redis Cluster</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-high-availability-with-redis-sentinel">High availability with Redis Sentinel</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-managed-redis-services">Use managed Redis services</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-common-pitfalls-to-avoid">Common Pitfalls to Avoid</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-dont-assume-volumes-work">Don't assume volumes work</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dont-ignore-redis-memory-limits">Don't ignore Redis memory limits</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dont-skip-monitoring">Don't skip monitoring</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-who-is-this-guide-for">Who is This Guide For?</h2>
<p>To get the most out of this tutorial, it’ll be helpful to have a few things under your belt. We’ll be diving into some code and command-line work, so a little preparation will go a long way.</p>
<ul>
<li><p>The main tools for this project are <a target="_blank" href="https://docs.docker.com/get-started/get-docker/">Docker</a> and <a target="_blank" href="https://docs.docker.com/compose/">Docker Compose</a>. Make sure you have them installed and running on your computer.</p>
</li>
<li><p>You’ll also find it easier to follow along if you’re comfortable with the basics of Docker, Python, and the <a target="_blank" href="http://flask.palletsprojects.com/en/stable/quickstart/">Flask</a> web framework. A bit of command-line experience will also be handy for running the commands in the tutorial.</p>
</li>
<li><p>But don't worry if you've never used <a target="_blank" href="https://redis.io/docs/latest/">Redis</a> before. All you need to know is that it’s a fast, in-memory database. We’ll handle the rest along the way.</p>
</li>
</ul>
<p>Think of this as a guided tour. As long as you're curious and have the basic tools ready, you'll be in great shape.</p>
<h2 id="heading-understanding-the-problem">Understanding the Problem</h2>
<p>Before jumping into solutions, let's first clarify what a time-series model is and then explore why containerizing it is so tricky.</p>
<h3 id="heading-so-what-is-a-time-series-model">So, what is a time-series model?</h3>
<p>Simply put, a time-series model is a type of model that analyzes data points collected over time to predict future values. Think of it like predicting the weather. A meteorologist doesn't just look at the sky right now. They look at the temperature, pressure, and wind patterns from the last few hours and days to forecast what will happen tomorrow.</p>
<p>Time-series models do the same thing with data, whether it's website traffic, stock prices, or energy consumption. The key takeaway is that history matters. The sequence of past events provides the context needed to make an intelligent prediction about the future.</p>
<p>Now, here’s what breaks when you put these models in Docker.</p>
<h3 id="heading-1-containers-are-ephemeral-by-design">1. Containers are ephemeral by design</h3>
<p>Docker containers are meant to be stateless. This works great for most APIs. A user profile endpoint? Stateless. A sentiment analysis model? Stateless. They take an input, return an output, and forget everything in between.</p>
<p>Time-series models don't work this way. They need context from previous predictions. Without it, your model is essentially blind.</p>
<h3 id="heading-2-lost-context-between-predictions">2. Lost context between predictions</h3>
<p>Each prediction happens in isolation. Your model receives a single data point and makes a guess without knowing what came before. This defeats the entire purpose of time-series modeling.</p>
<p>You may think: "I'll just load all historical data on every request." But that approach fails for two reasons:</p>
<ul>
<li><p>It's slow. Really slow if you have thousands of data points</p>
</li>
<li><p>It doesn't scale. When you have multiple series or high request volume, you'll hit performance walls fast</p>
</li>
</ul>
<h3 id="heading-3-model-amnesia-on-restart">3. Model amnesia on restart</h3>
<p>Every time you deploy a new version or the container crashes, all accumulated state disappears. Your model starts from scratch. In production, this is unacceptable.</p>
<h2 id="heading-the-solution-external-state-store">The Solution: External State Store</h2>
<p>Instead of keeping state inside the container, we’ll move it outside. Redis becomes the model's memory.</p>
<p>The pattern looks like this:</p>
<pre><code class="lang-plaintext">Client Request → Flask API → Redis → Prediction with Context
</code></pre>
<p>Your container stays stateless and replaceable. But the system as a whole maintains state through Redis.</p>
<h2 id="heading-hands-on-implementation">Hands-On Implementation</h2>
<p>Let's build this. Clone the demo repository:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/ag-chirag/docker-redis-time-series
<span class="hljs-built_in">cd</span> docker-redis-time-series
</code></pre>
<h3 id="heading-start-with-the-broken-approach">Start with the broken approach</h3>
<p>The <code>docker-compose.initial.yml</code> file shows what NOT to do:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">api:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">./flask-api</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"5000:5000"</span>

  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">redis:alpine</span>
</code></pre>
<p>Notice what's missing? No volumes. Redis stores data in the container's filesystem, which means that data is temporary.</p>
<p>Run it:</p>
<pre><code class="lang-bash">docker compose -f docker-compose.initial.yml up
</code></pre>
<p>Make a few predictions:</p>
<pre><code class="lang-bash">curl -X POST http://localhost:5000/predict \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{
    "series_id": "demo",
    "historical_data": [
      {"timestamp": "2024-01-01T12:00:00", "value": 10},
      {"timestamp": "2024-01-01T12:01:00", "value": 20},
      {"timestamp": "2024-01-01T12:02:00", "value": 30}
    ]
  }'</span>
</code></pre>
<p>You'll get a response showing Redis is working:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"data_points_used"</span>: <span class="hljs-number">3</span>,
  <span class="hljs-attr">"prediction"</span>: <span class="hljs-number">40</span>,
  <span class="hljs-attr">"redis_connected"</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<p>Now restart the services:</p>
<pre><code class="lang-bash">docker compose down
docker compose -f docker-compose.initial.yml up
</code></pre>
<p>Make another prediction. Check the <code>data_points_used</code> field. It reset. All your historical data is gone. This is exactly what we're trying to avoid.</p>
<h3 id="heading-how-to-fix-it-with-volumes">How to fix it with volumes</h3>
<p>The correct <code>docker-compose.yml</code> adds persistence:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">api:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">./flask-api</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"5000:5000"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">REDIS_HOST=redis</span>

  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">redis:alpine</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">redis-server</span> <span class="hljs-string">--appendonly</span> <span class="hljs-literal">yes</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">redis_data:/data</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">redis_data:</span>
</code></pre>
<h4 id="heading-so-what-is-a-volume-and-how-does-it-work">So, what is a volume and how does it work?</h4>
<p>Think of a Docker volume as a dedicated external hard drive for your container. By default, when a container writes data, it does so to a temporary layer that gets destroyed when the container is removed. A volume provides a way to save that data permanently.</p>
<p>Here’s how it works:</p>
<ol>
<li><p>Docker creates and manages a special storage area on the host machine, completely separate from any container's filesystem. In our docker-compose.yml, the <code>volumes: redis_data:</code> section at the bottom tells Docker to create a named volume called <code>redis_data</code>.</p>
</li>
<li><p>When the Redis container starts, the <code>volumes: - redis_data:/data</code> line tells Docker to "plug in" this external hard drive. It connects the <code>redis_data</code> volume to the <code>/data</code> directory inside the container.</p>
</li>
<li><p>Now, whenever the Redis process inside the container writes data to its <code>/data</code> directory (which we've configured it to do), it's actually writing to the <code>redis_data</code> volume on the host machine.</p>
</li>
<li><p>When you run docker compose down, the Redis container is destroyed, but the <code>redis_data</code> volume is untouched. It's like unplugging the external hard drive, and the data is still safe. The next time you run docker compose up, a brand new Redis container is created, the volume is re-attached, and Redis finds all its old data right where it left it.</p>
</li>
</ol>
<p>This mechanism is the key to giving our stateful service a memory that survives restarts.</p>
<p>Run the corrected version:</p>
<pre><code class="lang-bash">docker compose up --build
</code></pre>
<p>Send several predictions to build up state:</p>
<pre><code class="lang-bash"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> {1..5}; <span class="hljs-keyword">do</span>
  curl -X POST http://localhost:5000/predict \
    -H <span class="hljs-string">"Content-Type: application/json"</span> \
    -d <span class="hljs-string">"{
      \"series_id\": \"demo\",
      \"historical_data\": [{\"timestamp\": \"2024-01-01T12:0<span class="hljs-variable">$i</span>:00\", \"value\": <span class="hljs-subst">$((i*10)</span>)}]
    }"</span>
<span class="hljs-keyword">done</span>
</code></pre>
<p>Now comes the test. Restart everything:</p>
<pre><code class="lang-bash">docker compose down
docker compose up
</code></pre>
<p>Make another prediction. Look at <code>data_points_used</code>. It includes all previous points. The model picks up exactly where it left off.</p>
<p>This works because the volume exists independently of the container lifecycle.</p>
<h3 id="heading-how-the-code-handles-state">How the code handles state</h3>
<p>The Flask API in <code>flask-api/app.py</code> stores each data point in Redis using sorted sets:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">store_data_point</span>(<span class="hljs-params">series_id, timestamp, value</span>):</span>
    key = <span class="hljs-string">f"ts:<span class="hljs-subst">{series_id}</span>"</span>
    redis_client.zadd(key, {json.dumps({<span class="hljs-string">"ts"</span>: timestamp, <span class="hljs-string">"val"</span>: value}): timestamp})
</code></pre>
<p>When making predictions, it retrieves recent history:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_recent_data</span>(<span class="hljs-params">series_id, limit=<span class="hljs-number">100</span></span>):</span>
    key = <span class="hljs-string">f"ts:<span class="hljs-subst">{series_id}</span>"</span>
    data = redis_client.zrange(key, -limit, <span class="hljs-number">-1</span>)
    <span class="hljs-keyword">return</span> [json.loads(d) <span class="hljs-keyword">for</span> d <span class="hljs-keyword">in</span> data]
</code></pre>
<p>Redis sorted sets give you automatic time ordering. The volume ensures this data survives restarts.</p>
<h3 id="heading-test-the-health-endpoint">Test the health endpoint</h3>
<p>Check that everything is connected properly:</p>
<pre><code class="lang-bash">curl http://localhost:5000/health
</code></pre>
<p>You should see:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"model_loaded"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"redis_connected"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"status"</span>: <span class="hljs-string">"healthy"</span>
}
</code></pre>
<p>If <code>redis_connected</code> is false, check your Docker logs. Common issues are network configuration or Redis not starting properly.</p>
<h2 id="heading-what-about-scaling">What About Scaling?</h2>
<p>This setup works well for single-instance deployments. When traffic increases, you have a few options.</p>
<h3 id="heading-horizontal-scaling-with-redis-cluster">Horizontal scaling with Redis Cluster</h3>
<p>For high throughput, distribute your data across multiple Redis nodes. Redis Cluster handles sharding automatically.</p>
<h3 id="heading-high-availability-with-redis-sentinel">High availability with Redis Sentinel</h3>
<p>Add failover capability so your state store doesn't become a single point of failure. Sentinel monitors Redis instances and promotes replicas when the primary fails.</p>
<h3 id="heading-use-managed-redis-services">Use managed Redis services</h3>
<p>AWS ElastiCache, Azure Cache for Redis, or Google Cloud Memorystore handle the operational burden. You focus on your model, they handle Redis reliability.</p>
<p>The key insight: your API containers remain stateless. You scale the state store independently.</p>
<h2 id="heading-common-pitfalls-to-avoid">Common Pitfalls to Avoid</h2>
<p>I can't emphasize this enough: test your persistence before deploying to production.</p>
<h3 id="heading-dont-assume-volumes-work">Don't assume volumes work</h3>
<p>Actually restart your containers and verify state persists. I've seen deployments fail because someone forgot to mount the volume in production.</p>
<h3 id="heading-dont-ignore-redis-memory-limits">Don't ignore Redis memory limits</h3>
<p>Redis keeps everything in memory. Monitor your memory usage. Set maxmemory policies appropriate for your workload. If you run out of memory, Redis will start evicting keys or refuse writes.</p>
<h3 id="heading-dont-skip-monitoring">Don't skip monitoring</h3>
<p>Add health checks. Monitor Redis connection status. Track prediction latency. You want to know when things break, not learn about it from angry users.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Time-series models need memory. Docker containers lose memory by default. The solution is simple: separate state from compute.</p>
<p>Use Redis as an external state store. Use Docker volumes to persist that state. Your model stays smart, your containers stay replaceable, and your deployments become reliable.</p>
<p>The full working code is available at <a target="_blank" href="https://github.com/ag-chirag/docker-redis-time-series">github.com/ag-chirag/docker-redis-time-series</a>. Clone it, run it, break it, learn from it.</p>
<p>And remember: the simplest solution that works is usually the right one. You don't always need Kubernetes and StatefulSets. Sometimes Docker Compose and a volume are enough.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Caching a Next.js API using Redis and Sevalla ]]>
                </title>
                <description>
                    <![CDATA[ When you hear about Next.js, your first thought may be static websites or React-driven frontends. But that’s just part of the story. Next.js can also power full-featured backend APIs that you can host and scale just like any other backend service. In... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/caching-a-nextjs-api-using-redis-and-sevalla/</link>
                <guid isPermaLink="false">68af2baaa845284174a27982</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ caching ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 27 Aug 2025 16:00:42 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756310410998/ee5f34fd-0efe-4efc-9e91-2baa826edff9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you hear about Next.js, your first thought may be static websites or React-driven frontends. But that’s just part of the story. Next.js can also power full-featured backend APIs that you can host and scale just like any other backend service.</p>
<p><a target="_blank" href="https://www.freecodecamp.org/news/how-to-deploy-a-nextjs-api-with-postgresql-and-sevalla/">In an earlier article</a>, I walked through building a Next.js API and deploying it with Sevalla. The example stored data in a PostgreSQL database and handled requests directly. That worked fine, but as traffic grows, APIs that hit the database on every request can slow down.</p>
<p>This is where caching comes in. By adding Redis as a cache layer, we can make our Next.js API much faster and more efficient. In this article, we’ll see how to add Redis caching to our API, deploy it with <a target="_blank" href="https://sevalla.com/">Sevalla</a>, and show measurable improvements.</p>
<p>In the last article, I explained the API in detail. So you can <a target="_blank" href="https://github.com/manishmshiva/nextjs-api-pgsql">use this repository</a> to start with as the base for this project.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-caching-matters">Why Caching Matters</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-redis">What is Redis</a>?</p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-the-project">Setting Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-provisioning-redis">Provisioning Redis</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-updating-cache-on-reads">Updating Cache on Reads</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-updating-cache-on-writes">Updating Cache on Writes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deploying-to-sevalla">Deploying to Sevalla</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-redis-works-well-with-nextjs-apis">Why Redis Works Well with Next.js APIs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-why-caching-matters">Why Caching Matters</h2>
<p>Every time your API hits the database, it consumes time and resources. Databases are great at storing and querying structured data, but they aren’t optimized for speed at scale when you need to serve thousands of read requests per second.</p>
<p>Caching solves this by keeping frequently accessed data in memory. Instead of asking the database every time, the API can return data directly from cache if it’s available. Redis is perfect for this because it’s an in-memory key-value store designed for performance.</p>
<p>For example, if you fetch the list of users from the database on every request, it might take 200ms to run the query and return results. With Redis caching, the first request stores the result in memory, and subsequent requests can return the same data in less than 10ms. That’s an order-of-magnitude improvement.</p>
<h2 id="heading-what-is-redis">What is Redis?</h2>
<p><a target="_blank" href="https://www.freecodecamp.org/news/how-in-memory-caching-works-in-redis/">Redis</a> is an in-memory data store that works like a super-fast database. Instead of writing and reading from disk, it keeps data in memory, which makes it incredibly fast. That’s why it’s often used as a cache, where speed is more important than long-term storage.</p>
<p>It’s designed to handle high-throughput workloads with very low latency, which means it can respond in microseconds. This makes it a perfect fit for use cases like caching API responses, storing session data, or even powering real-time applications like chat systems and leaderboards.</p>
<p>Unlike a traditional database, Redis focuses on simplicity and speed. It stores data as key-value pairs, so you can quickly fetch or update values without writing complex queries. And because it supports advanced data types like lists, sets, and hashes, it’s much more flexible than a plain key-value store.</p>
<p>When combined with an API like the one we built in Next.js, Redis helps you reduce load on the main database and deliver blazing-fast responses to clients.</p>
<h2 id="heading-setting-up-the-project">Setting Up the Project</h2>
<p>Let’s clone the repository:</p>
<pre><code class="lang-typescript">git clone git<span class="hljs-meta">@github</span>.com:manishmshiva/nextjs-api-pgsql.git next-api
</code></pre>
<p>Now let’s go into the directory and do an npm install to install the packages.</p>
<pre><code class="lang-typescript">cd next-api
npm i
</code></pre>
<p>Create a .env file and add the database URL from Sevalla into an environment variable.</p>
<pre><code class="lang-typescript">cat .env
</code></pre>
<p>The .env file should look like this:</p>
<pre><code class="lang-typescript">PGSQL_URL=postgres:<span class="hljs-comment">//&lt;username&gt;:&lt;password&gt;-@asia-east1-001.proxy.kinsta.app:30503/&lt;db_name&gt;</span>
</code></pre>
<p>Now let’s make sure the application works as expected by starting the application and making a couple of API requests.</p>
<p>Starting the app:</p>
<pre><code class="lang-typescript">npm run dev
</code></pre>
<p>Let’s make sure the database is connected. Go to <code>localhost:3000</code> on your browser. It should return the following JSON:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755607650708/543df6fe-3bea-4eb2-b962-13df35b6fb2c.png" alt="GET /" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Let’s create a new user. To create a new entry in the DB using <a target="_blank" href="https://www.postman.com/">Postman</a>, send a POST request with the following JSON:</p>
<pre><code class="lang-typescript">{<span class="hljs-string">"id"</span>:<span class="hljs-string">"d9553bb7-2c72-4d92-876b-9c3b40a8c62c"</span>,<span class="hljs-string">"name"</span>:<span class="hljs-string">"Larry"</span>,<span class="hljs-string">"email"</span>:<span class="hljs-string">"larry@example.com"</span>,<span class="hljs-string">"age"</span>:<span class="hljs-string">"25"</span>}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755607596858/7c8c71b5-7868-47a1-ae8d-172474f6d75b.png" alt="Postman test" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Let’s ensure the record is created by going to <code>localhost:3000/users</code> in the browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755607717319/d6743d2a-8373-4d81-afee-1f034e1954e1.png" alt="Postman response for /users" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Great. Now let’s cache these APIs using Redis.</p>
<h2 id="heading-provisioning-redis">Provisioning Redis</h2>
<p>Let’s go to <a target="_blank" href="https://app.sevalla.com/login">Sevalla’s</a> dashboard and click on “Databases”. Choose “Redis” from the list, and leave the rest of the options as defaults.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755415913475/0ba7badb-2c67-474a-a5b1-6d72b5bdc5f3.png" alt="Create database" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Once the database is created, switch on the “external connection” option and copy the publicly accessible URL.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755611728877/6e139e09-8484-4a50-b007-32ecdb266afb.png" alt="Update network settings" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>This is how it should look in the .env file:</p>
<pre><code class="lang-typescript">REDIS_URL=redis:<span class="hljs-comment">//default:&lt;password&gt;@&lt;host&gt;:&lt;port&gt;</span>
</code></pre>
<p>Now install a Redis client for Node.js:</p>
<pre><code class="lang-bash">npm install ioredis
</code></pre>
<p>We can now connect to Redis and use it as a cache layer for our users API. Let’s see how to implement caching.</p>
<h2 id="heading-updating-cache-on-reads">Updating Cache on Reads</h2>
<p>Here’s the updated <code>users/route.ts</code> that uses Redis:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
<span class="hljs-keyword">import</span> { Client } <span class="hljs-keyword">from</span> <span class="hljs-string">"pg"</span>;
<span class="hljs-keyword">import</span> Redis <span class="hljs-keyword">from</span> <span class="hljs-string">"ioredis"</span>;

<span class="hljs-keyword">const</span> redis = <span class="hljs-keyword">new</span> Redis(process.env.REDIS_URL!);

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">readUsers</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> Client({
    connectionString: process.env.PGSQL_URL,
  });
  <span class="hljs-keyword">await</span> client.connect();

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> client.query(<span class="hljs-string">"SELECT id, name, email, age FROM users"</span>);
    <span class="hljs-keyword">return</span> result.rows;
  } <span class="hljs-keyword">finally</span> {
    <span class="hljs-keyword">await</span> client.end();
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Check cache first</span>
    <span class="hljs-keyword">const</span> cached = <span class="hljs-keyword">await</span> redis.get(<span class="hljs-string">"users"</span>);
    <span class="hljs-keyword">if</span> (cached) {
      <span class="hljs-keyword">return</span> NextResponse.json(<span class="hljs-built_in">JSON</span>.parse(cached));
    }

    <span class="hljs-comment">// Fallback to database if not cached</span>
    <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> readUsers();

    <span class="hljs-comment">// Store result in cache with 60s TTL</span>
    <span class="hljs-keyword">await</span> redis.set(<span class="hljs-string">"users"</span>, <span class="hljs-built_in">JSON</span>.stringify(users), <span class="hljs-string">"EX"</span>, <span class="hljs-number">60</span>);

    <span class="hljs-keyword">return</span> NextResponse.json(users);
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-keyword">return</span> NextResponse.json({ error: <span class="hljs-string">"Failed to fetch users"</span> }, { status: <span class="hljs-number">500</span> });
  }
}
</code></pre>
<p>Now, when you hit <code>/users</code>:</p>
<ol>
<li><p>The API first checks Redis.</p>
</li>
<li><p>If the data exists, it returns it instantly.</p>
</li>
<li><p>If not, it queries PostgreSQL, saves the result in Redis, and then returns it.</p>
</li>
</ol>
<p>This makes repeated requests extremely fast. You can adjust the cache expiry (<code>EX 60</code>) depending on how fresh your data needs to be.</p>
<p>Without Redis caching, fetching <code>/users</code> ten times means ten database queries. Each might take around 150–200ms depending on database size and network latency.</p>
<p>With Redis, the first request still takes ~200ms since it populates the cache. But every request after that is nearly instant, often under 10ms. That’s a <strong>20x improvement</strong>.</p>
<p>This speedup matters when your API faces hundreds or thousands of requests per second. Caching not only reduces latency but also lightens the load on your database.</p>
<h2 id="heading-updating-cache-on-writes">Updating Cache on Writes</h2>
<p>Right now, only GET requests use the cache. But what if we add new users? The cache would still return the old data.</p>
<p>The solution is to update or clear the cache whenever a write happens. Let’s update the <code>POST</code> handler:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: Request</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> body = <span class="hljs-keyword">await</span> req.json();
    <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> Client({
      connectionString: process.env.PGSQL_URL,
    });
    <span class="hljs-keyword">await</span> client.connect();

    <span class="hljs-keyword">const</span> query = <span class="hljs-string">`
      INSERT INTO users (id, name, email, age)
      VALUES ($1, $2, $3, $4)
      RETURNING *;
    `</span>;

    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> client.query(query, [
      body.id,
      body.name,
      body.email,
      body.age,
    ]);

    <span class="hljs-keyword">await</span> client.end();

    <span class="hljs-comment">// Invalidate cache so next GET fetches fresh data</span>
    <span class="hljs-keyword">await</span> redis.del(<span class="hljs-string">"users"</span>);

    <span class="hljs-keyword">return</span> NextResponse.json(result.rows[<span class="hljs-number">0</span>]);
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-keyword">return</span> NextResponse.json({ error: <span class="hljs-string">"Failed to add user"</span> }, { status: <span class="hljs-number">500</span> });
  }
}
</code></pre>
<p>Now whenever a new user is created, the cache for <code>users</code> is cleared. The next GET request will fetch from the database, refresh the cache, and then continue serving cached data.</p>
<h2 id="heading-deploying-to-sevalla"><strong>Deploying to Sevalla</strong></h2>
<p>Push your code to GitHub or <a target="_blank" href="https://github.com/manishmshiva/nextjs-api-redis">fork my repository</a>. Now lets go to Sevalla and create a new app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754545093624/9747a06d-0dcf-482a-89b9-732b9937b1dc.png" alt="Sevalla create app" width="600" height="400" loading="lazy"></p>
<p>Choose your repository from the dropdown and check “Automatic deployment on commit”. This will ensure that the deployment is automatic every time you push code. Choose “Hobby” under the resources section.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755683871627/cc8bd555-caaa-43f2-a9a3-f3f0d1481108.png" alt="create new app" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Click “Create” and not “Create and deploy”. We haven’t added our PostgreSQL URL and Redis URL as environment variables, so the app will crash if you try to deploy it.</p>
<p>Go to the “Environment variables” section and add the key “PGSQL_URL” and the URL in the value field. Do the same for the “REDIS_URL” key and add the Redis URL.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755683943610/97932885-29aa-4cef-b999-689f0871809e.png" alt="Adding environment variables" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Now go back to the “Overview” section and click “Deploy now”.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755684196447/a1da5802-aa2c-47f6-8837-14f7e40198fd.png" alt="App Deployment" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Once deployment is complete, click “Visit app” to get the live URL of your API. You can replace <a target="_blank" href="http://localhost:3000">localhost:3000</a> with the new URL in Postman and test your API.</p>
<h2 id="heading-why-redis-works-well-with-nextjs-apis">Why Redis Works Well with Next.js APIs</h2>
<p>Redis is lightweight, blazing fast, and perfect for caching API responses. In the context of Next.js, it fits naturally because:</p>
<ul>
<li><p>The API routes run server-side where Redis can be queried directly.</p>
</li>
<li><p>Caching logic is simple to add around database calls.</p>
</li>
<li><p>Redis can be used for more than caching – things like rate limiting, session storage, and pub/sub are also common patterns.</p>
</li>
</ul>
<p>By combining Next.js, PostgreSQL, and Redis on Sevalla, you get a stack that is fast, scalable, and easy to deploy.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Caching isn’t just an optimization – it’s a necessity for real-world APIs. Next.js helps you build robust backend APIs that can be deployed easily. By adding Redis to the mix, those APIs can handle scale without breaking a sweat.</p>
<p>Sevalla ties it all together by providing managed PostgreSQL, Redis, and app hosting in one place. With a few environment variables and a GitHub repo, you can go from local dev to a production-ready, cached API in minutes.</p>
<p><em>Hope you enjoyed this article. Signup for my free AI newsletter</em> <a target="_blank" href="https://www.turingtalks.ai/"><strong><em>TuringTalks.ai</em></strong></a> <em>for more hands-on tutorials on AI. You can also find me on</em> <a target="_blank" href="https://www.linkedin.com/in/manishmshiva"><strong><em>Linkedin</em></strong></a><strong><em>.</em></strong></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How In-Memory Caching Works in Redis ]]>
                </title>
                <description>
                    <![CDATA[ When you’re building a web app or API that needs to respond quickly, caching is often the secret sauce. Without it, your server can waste time fetching the same data over and over again – from a database, a third-party API, or a slow storage system. ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-in-memory-caching-works-in-redis/</link>
                <guid isPermaLink="false">6877d117ba632c15e613aac8</guid>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ caching ]]>
                    </category>
                
                    <category>
                        <![CDATA[ development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ memory ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 16 Jul 2025 16:19:35 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752680755362/97cde2e5-3bb3-4b5d-b073-dcbf03c7f871.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re building a web app or API that needs to respond quickly, caching is often the secret sauce.</p>
<p>Without it, your server can waste time fetching the same data over and over again – from a database, a third-party API, or a slow storage system.</p>
<p>But when you store that data in memory, the same information can be served up in milliseconds. That’s where Redis comes in.</p>
<p>Redis is a fast, flexible tool that stores your data in RAM and lets you retrieve it instantly. Whether you’re building a dashboard, automating social media posts, or managing user sessions, Redis can make your system faster, more efficient, and easier to scale.</p>
<p>In this article, you’ll learn how in-memory caching works and why Redis is a go-to choice for many developers.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-in-memory-caching">What Is In-Memory Caching?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-redis">What Is Redis?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-work-with-redis">How to Work with Redis</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-redis-installation">Redis Installation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-redis-data-types">Redis Data Types</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-redis-with-python">Redis with Python</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-real-life-use-cases">Real-Life Use Cases</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-in-memory-caching"><strong>What Is In-Memory Caching?</strong></h2>
<p>In-memory caching is a way of storing data in the system’s RAM instead of fetching it from a database or external source every time it’s needed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752582672642/78e262d7-76a3-4bf3-886c-32a190d190b7.webp" alt="Diagram showing how caching works" class="image--center mx-auto" width="1100" height="776" loading="lazy"></p>
<p>Since RAM is incredibly fast compared to disk storage, you can access cached data almost instantly. This approach is perfect for information that doesn’t change very often, like API responses, user profiles, or rendered HTML pages.</p>
<p>Rather than repeatedly running the same queries or API calls, your app checks the cache first. If the data is there, it’s used right away. If it’s not, you fetch it from the source, save it to the cache, and then return it.</p>
<p>This technique reduces load on your backend, improves response time, and can dramatically improve your app’s performance under heavy traffic.</p>
<h2 id="heading-what-is-redis"><strong>What Is Redis?</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752582701613/951f7322-0c49-4437-b97b-6502bd93483a.webp" alt="Redis" class="image--center mx-auto" width="300" height="168" loading="lazy"></p>
<p><a target="_blank" href="https://redis.io/">Redis</a> is an open-source, in-memory data store that developers use to cache and manage data in real time.</p>
<p>Unlike traditional databases, Redis stores everything in memory, which makes data retrieval incredibly fast. But Redis isn’t just a simple key-value store. It offers a wide range of data types, from strings and lists to sets, hashes, and sorted sets.</p>
<p>Redis is also capable of handling more advanced tasks like pub/sub messaging, streams, and geospatial queries. Despite its power, Redis is lightweight and easy to get started with.</p>
<p>You can run it on your local machine, deploy it on a server, or even use managed Redis services offered by cloud providers. It’s trusted by major companies and used in all kinds of applications, from caching and session storage to real-time analytics and job queues.</p>
<h2 id="heading-how-to-work-with-redis"><strong>How to Work with Redis</strong></h2>
<h3 id="heading-redis-installation">Redis Installation</h3>
<p>Getting Redis up and running is surprisingly simple. You can find the installation instructions based on your operating system <a target="_blank" href="https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/">in the documentation</a>.</p>
<p>To make sure Redis is working, run:</p>
<pre><code class="lang-plaintext">redis-cli ping
# Should respond with "PONG"
</code></pre>
<h3 id="heading-redis-data-types">Redis Data Types</h3>
<p>Redis gives you several built-in types that let you store and manage data in flexible ways.</p>
<p><strong>Strings</strong>: Simple key ↔ value pairs.</p>
<pre><code class="lang-plaintext">SET username "Emily"
GET username
</code></pre>
<p><strong>Lists</strong>: Ordered collections which are great for queues and timelines.</p>
<pre><code class="lang-plaintext">LPUSH tasks "task1"
RPUSH tasks "task2"
LRANGE tasks 0 -1
</code></pre>
<p><strong>Hashes</strong>: Like JSON objects, great for user profiles.</p>
<pre><code class="lang-plaintext">HSET user:1 name "Alice"
HSET user:1 email "alice@example.com"
HGETALL user:1
</code></pre>
<p><strong>Sets</strong>: Unordered collections, ideal for tags or unique items.</p>
<pre><code class="lang-plaintext">SADD tags "python"
SADD tags "redis"
SMEMBERS tags
</code></pre>
<p><strong>Sorted Sets</strong>: Sets with scores – useful for leaderboards.</p>
<pre><code class="lang-plaintext">ZADD leaderboard 100 "Bob"
ZADD leaderboard 200 "Carol"
ZRANGE leaderboard 0 -1 WITHSCORES
</code></pre>
<p>Redis also supports Bitmaps, hyperloglogs, streams, geospatial indexes, and keeps expanding its support for <a target="_blank" href="https://redis.io/technology/data-structures/">data structures</a>.</p>
<h3 id="heading-redis-with-python">Redis with Python</h3>
<p>If you’re working in Python, using Redis is just as easy. After installing the <code>redis</code> Python library using <code>pip install redis</code>, you can connect to your Redis server and start setting and getting keys right away.</p>
<p>Here is some simple <a target="_blank" href="https://www.freecodecamp.org/news/learn-python-free-python-courses-for-beginners/">Python code</a> to work with Redis:</p>
<pre><code class="lang-plaintext">import redis

# Connect to the local Redis server on default port 6379 and use database 0
r = redis.Redis(host='localhost', port=6379, db=0)

# --- Basic String Example ---

# Set a key called 'welcome' with a string value
r.set('welcome', 'Hello, Redis!')

# Get the value of the key 'welcome'
# Output will be a byte string: b'Hello, Redis!'
print(r.get('welcome'))


# --- Hash Example (like a Python dict) ---

# Create a Redis hash under the key 'user:1'
# This hash stores fields 'name' and 'email' for a user
r.hset('user:1', mapping={
    'name': 'Alice',
    'email': 'alice@example.com'
})

# Get all fields and values in the hash as a dictionary of byte strings
# Output: {b'name': b'Alice', b'email': b'alice@example.com'}
print(r.hgetall('user:1'))


# --- List Example (acts like a queue or stack) ---

# Push 'Task A' to the left of the list 'tasks'
r.lpush('tasks', 'Task A')

# Push 'Task B' to the left of the list 'tasks' (it becomes the first item)
r.lpush('tasks', 'Task B')

# Retrieve all elements from the list 'tasks' (from index 0 to -1, meaning the full list)
# Output: [b'Task B', b'Task A']
print(r.lrange('tasks', 0, -1))
</code></pre>
<p>You might store a user's session data, queue background tasks, or even cache rendered HTML pages. Redis commands are fast and atomic, which means you don’t have to worry about data collisions or inconsistency in high-traffic environments.</p>
<p>One of the most useful features in Redis is key expiration. You can tell Redis to automatically delete a key after a certain period, which is especially handy for session data or temporary caches.</p>
<p>You can set a time-to-live (TTL) on keys, so Redis removes them automatically</p>
<pre><code class="lang-plaintext">SET session:1234 "some data" EX 3600  # Expires in 1 hour
</code></pre>
<p>Redis also supports persistence, so even though it’s an in-memory store, your data can survive a reboot.</p>
<p>Redis isn’t limited to small apps. It scales easily through replication, clustering, and <a target="_blank" href="https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/">Sentinel</a>.</p>
<p>Replication allows you to create read-only copies of your data, which helps distribute the load. Clustering breaks your data into chunks and spreads them across multiple servers. And Sentinel handles automatic failover to keep your system running even if one server goes down.</p>
<h2 id="heading-real-life-use-cases"><strong>Real-Life Use Cases</strong></h2>
<p>One of the most common uses for Redis is caching API responses.</p>
<p>Let’s say you have an app that displays weather data. Rather than calling the <a target="_blank" href="https://openweathermap.org/api">weather API</a> every time a user loads the page, you can cache the response for each city in Redis for 5 or 10 minutes. That way, you only fetch new data occasionally, and your app becomes much faster and cheaper to run.</p>
<p>Another powerful use case is <a target="_blank" href="https://gtcsys.com/faq/what-are-the-best-practices-for-caching-and-session-management-in-web-application-development-2/">session management</a>. In web applications, every logged-in user has a session that tracks who they are and what they’re doing. Redis is a great place to store this session data because it’s fast and temporary.</p>
<p>You can store the session ID as a key, with the user’s information in a hash. Add an expiration time, and you’ve got automatic session timeout built in. Since Redis is so fast and supports high-concurrency access, it’s a great fit for applications with thousands of users logging in at the same time.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>In-memory caching is one of the simplest and most effective ways to speed up your app, and Redis makes it incredibly easy to implement. It’s not just a cache, it’s a toolkit for building fast, scalable, real-time systems. You can start small by caching a few pages or API responses, and as your needs grow, Redis grows with you.</p>
<p>If you’re just getting started, try running Redis locally and experimenting with different data types. Store some strings, build a simple task queue with lists, or track user scores with a sorted set. The more you explore, the more you’ll see how Redis can help your application run faster, smarter, and more efficiently.</p>
<p>Enjoyed this article? <a target="_blank" href="https://www.linkedin.com/in/manishmshiva">Connect with me on Linkedin</a>. See you soon with another topic.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Flexible API with Feature Flags Using Open Source Tools ]]>
                </title>
                <description>
                    <![CDATA[ Feature flagging has changed the paradigm of how backend developers can test and modify the things they build. With feature flags, we can enable and disable a feature or change the functionality of something on the fly with a single click (no need to... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-flexible-api-with-feature-flags-using-open-source-tools/</link>
                <guid isPermaLink="false">673d179a27c7af0d174dc2fc</guid>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                    <category>
                        <![CDATA[   feature flags ]]>
                    </category>
                
                    <category>
                        <![CDATA[ backend developments ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Pradumna Saraf ]]>
                </dc:creator>
                <pubDate>Tue, 19 Nov 2024 22:56:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732044691446/abd5596c-3523-4278-957c-109388690bcc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Feature flagging has changed the paradigm of how backend developers can test and modify the things they build. With feature flags, we can enable and disable a feature or change the functionality of something on the fly with a single click (no need to redeploy).</p>
<p>In this tutorial, we will see how feature flags help us to enable and disable a feature/a part of code whenever we want from the UI, without the need to redeploy the whole code.</p>
<p>To understand things more deeply, we will build an app from scratch, look at feature flagging capabilities, and use a tool called Flagsmith to manage our created feature flags from a single dashboard.</p>
<h2 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-feature-flag">What is a Feature Flag?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-feature-flags-for-backend-development">Feature Flags for Backend Development</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-open-source-tools">Why Use Open Source Tools?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-code">Let’s Code!</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-initializing-the-tools">Initializing the tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-endpoints-for-the-api">Creating endpoints for the API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-feature-flagging">How to Add Feature Flagging</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-feature-flag-code-logic">Understanding the Feature Flag code logic</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-feature-flags-in-the-flasgsmith-dashboard">How to Create Feature Flags in the Flasgsmith Dashboard</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-rate-limiting-feature-flag">Rate Limiting Feature Flag</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-beta-feature-flag">Beta Feature Flag</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-getting-the-access-key">Getting the Access Key</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-running-the-api">Running the API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-updating-the-ratelimit-flag">Updating the rate_limit Flag</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-feature-flags-with-the-github-app">How to Integrate Feature Flags with the GitHub App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-the-flagsmith-github-app">Testing the Flagsmith GitHub App</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p><a target="_blank" href="https://go.dev/">Golang</a> installed and a medium-level understanding of it.</p>
</li>
<li><p>A running <a target="_blank" href="https://redis.io">Redis</a> instance (Remote or local instance)</p>
</li>
<li><p><a target="_blank" href="https://www.flagsmith.com/">Flagsmith</a> Account (It’s Free. We will cover this later in the article.)</p>
</li>
</ul>
<h2 id="heading-what-is-a-feature-flag">What is a Feature Flag?</h2>
<p>Feature Flag is a technique in development that allows teams to turn features on or off without modifying the source code or redeploying.</p>
<p>To make it a bit simpler, think of them as functioning sort of like conditional statements (for example, if-else statements): based on when something’s true or false, it determines the code path that will be executed.</p>
<h2 id="heading-feature-flags-for-backend-development">Feature Flags for Backend Development</h2>
<p>You may have seen feature flags used in frontends and websites, but there is much more to them. You can use them on the server side to modify the functionality of an API, doing things like modifying/setting the rate limit, changing the API endpoint's functionality or completely turning it off. As backend developers, we can level up our testing with feature flags.</p>
<p>To demonstrate this, we will go through building a demo app. The demo app is curated to show feature flagging capabilities from modifying the functionality (rate limit) on the fly to adding a new endpoint to the API for beta testing or initial rolling purposes. We’ll use entirely open-source tools along the way!</p>
<h2 id="heading-why-use-open-source-tools">Why Use Open Source Tools?</h2>
<p>We will be using open source tools to build this app (Golang, <a target="_blank" href="https://redis.io/">Redis</a>, and <a target="_blank" href="https://www.flagsmith.com/?utm_source=thirdparty&amp;utm_medium=freecodecamp&amp;utm_campaign=pradumna">Flagsmith</a>). Open source brings more transparency and trust and encourages collaboration with the global community of backend developers.</p>
<p>By integrating open source tools, we get full visibility as we build and test. For example, we will integrate feature flags with GitHub, which lets us track the lifecycle of a feature by linking a Flagsmith feature flag with a GitHub Pull Request or Issue. This lets us stay updated with the changes to our features without having to manually track each modification. We can easily track the status of our features across different environments.</p>
<h2 id="heading-lets-code">Let’s Code!</h2>
<p>In this tutorial, you’ll see how the functionality of an app changes before and after testing with feature flagging mechanisms. The tools and frameworks we’ll use are Golang, Docker, Redis, Flagsmith, and GitHub. As discussed, all are open source and free to create an account to test.</p>
<p>To get started, open your favourite IDE, initialize a Golang project, and then copy the below code in the <code>main.go</code> file. Then run <code>go mod tidy</code> to install all the dependencies it needs.</p>
<p>Let’s understand what’s going on in the below code snippet:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"errors"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"os"</span>
    <span class="hljs-string">"strconv"</span>

    <span class="hljs-string">"github.com/gin-gonic/gin"</span>
    <span class="hljs-string">"github.com/go-redis/redis_rate/v10"</span>
    <span class="hljs-string">"github.com/joho/godotenv"</span>
    <span class="hljs-string">"github.com/redis/go-redis/v9"</span>
)

<span class="hljs-keyword">var</span> (
    redisClient *redis.Client
    limiter     *redis_rate.Limiter
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">initClients</span><span class="hljs-params">()</span></span> {
    redisClient = redis.NewClient(&amp;redis.Options{
        Addr: os.Getenv(<span class="hljs-string">"REDIS_URL"</span>),
    })
    limiter = redis_rate.NewLimiter(redisClient)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    err := godotenv.Load()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Printf(<span class="hljs-string">"Loading environment variable from the host system"</span>)
    } <span class="hljs-keyword">else</span> {
        log.Printf(<span class="hljs-string">"Loading environment from .env file"</span>)
    }

    initClients()
    <span class="hljs-keyword">defer</span> redisClient.Close()

    r := gin.Default()
    r.GET(<span class="hljs-string">"/ping"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        err, remainingLimit := rateLimitCall(c.ClientIP())
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            c.JSON(
                http.StatusTooManyRequests,
                gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Rate Limit Hit"</span>})
        } <span class="hljs-keyword">else</span> {
            c.JSON(
                http.StatusOK,
                gin.H{<span class="hljs-string">"Your left over API request is"</span>: remainingLimit})
        }
    })
    r.GET(<span class="hljs-string">"/beta"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        c.JSON(
            http.StatusOK,
            gin.H{<span class="hljs-string">"message"</span>: <span class="hljs-string">"This is beta endpoint"</span>})
    })
    r.Run(<span class="hljs-string">":"</span> + os.Getenv(<span class="hljs-string">"PORT"</span>))
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">rateLimitCall</span><span class="hljs-params">(ClientIP <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(error, <span class="hljs-keyword">int</span>)</span></span> {
    ctx := context.Background()

    rateLimitString := os.Getenv(<span class="hljs-string">"RATE_LIMIT"</span>)
    RATE_LIMIT, _ := strconv.Atoi(rateLimitString)

    res, err := limiter.Allow(ctx, ClientIP, redis_rate.PerHour(RATE_LIMIT))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

    <span class="hljs-keyword">if</span> res.Remaining == <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"You have hit the Rate Limit for the API. Try again later"</span>), <span class="hljs-number">0</span>
    }

    fmt.Println(<span class="hljs-string">"remaining request for"</span>, ClientIP, <span class="hljs-string">"is"</span>, res.Remaining)
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, res.Remaining
}
</code></pre>
<h3 id="heading-initializing-the-tools">Initializing the Tools</h3>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">initClients</span><span class="hljs-params">()</span></span> {
    redisClient = redis.NewClient(&amp;redis.Options{
        Addr: os.Getenv(<span class="hljs-string">"REDIS_URL"</span>),
    })
    limiter = redis_rate.NewLimiter(redisClient)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    err := godotenv.Load()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Printf(<span class="hljs-string">"Loading environment variable from the host system"</span>)
    } <span class="hljs-keyword">else</span> {
        log.Printf(<span class="hljs-string">"Loading environment from .env file"</span>)
    }

    initClients()
    <span class="hljs-keyword">defer</span> redisClient.Close()

    r := gin.Default()
    ...
    })
</code></pre>
<p>At the top, we declare variables to store Redis and Rate limiter clients to reuse and initialise them once. Then we initialise them in the <code>initClients()</code>.</p>
<p>In <code>main()</code>, first, we load the environment variables from the system or the .env file. Then we call <code>initClients()</code>. This will create clients and store them in the variables we created.</p>
<p>Next, we create a <strong>Gin</strong> router that handles all our incoming requests. These are the environment variables we need in our <code>.env</code> file. For this demo, we need a Redis instance running to store all the data for rate-limiting functionality. We can use Docker or any remote machine – just remember to update <code>REDIS_URL</code> accordingly. I am going to use Docker.</p>
<p>We could also go a mile ahead and get all the environment variables from the feature flags, but we won’t do this here.</p>
<pre><code class="lang-bash">REDIS_URL=localhost:6379
PORT=8080
RATE_LIMIT=10
</code></pre>
<h3 id="heading-creating-endpoints-for-the-api">Creating Endpoints for the API</h3>
<pre><code class="lang-go">r.GET(<span class="hljs-string">"/ping"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        err, remainingLimit := rateLimitCall(c.ClientIP())
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            c.JSON(
                http.StatusTooManyRequests,
                gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Rate Limit Hit"</span>})
        } <span class="hljs-keyword">else</span> {
            c.JSON(
                http.StatusOK,
                gin.H{<span class="hljs-string">"Your left over API request is"</span>: remainingLimit})
        }
    })
    r.GET(<span class="hljs-string">"/beta"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        c.JSON(
            http.StatusOK,
            gin.H{<span class="hljs-string">"message"</span>: <span class="hljs-string">"This is beta endpoint"</span>})
    })
    r.Run(<span class="hljs-string">":"</span> + os.Getenv(<span class="hljs-string">"PORT"</span>))
</code></pre>
<p>Then we create two <strong>GET</strong> endpoints, <code>/ping</code> and <code>/beta</code>. Every time someone hits the <code>/ping</code> endpoint we call the <code>rateLimitCall()</code> function. It checks and sets the rate limit of incoming requests from an <strong>IP address</strong>. All this is stored in the Redis instance we created.</p>
<p>So, now if the user has interacted with the <code>/ping</code> API endpoint for the first time, will create an entry with a limit of <strong>10 per hour</strong>. The limit number <strong>10</strong> comes from the <code>RATE_LIMIT</code> we set, and the hourly refresh form comes from the <code>redis_rate.PerHour(RATE_LIMIT)</code> function.</p>
<p>Next, we check if the user has a remaining limit. If yes, we will return a message with the number of requests they have remaining. Otherwise, if they hit the limit cap, we return a message letting them know this.</p>
<p>Apart from the <code>/ping</code> endpoint, we have another endpoint <code>/beta</code>. It returns a simple message, but later we’ll see how (using feature flags) we can completely turn on and off the functionality of this endpoint.</p>
<h3 id="heading-how-to-add-feature-flagging">How to Add Feature Flagging</h3>
<p>Now it’s time to add feature flagging capabilities to our app. We are going to use <a target="_blank" href="https://flagsmith.com/">Flagsmith</a>. Flagsmith is an open source software that lets us easily create and manage feature flags across web, mobile, and server-side applications.</p>
<p>Using Flagsmith, we can wrap features in a flag and then toggle them on or off for different environments, users, or user segments. And then you’ll be able to manage all of them from the Flagsmith dashboard without needing to redeploy.</p>
<p>So, let’s install the Flagsmith package by running the below command:</p>
<pre><code class="lang-bash">go get github.com/Flagsmith/flagsmith-go-client/v3
</code></pre>
<p>Then we import the package by giving it an alias <strong>flagsmith</strong>. Below is the updated functionality after we apply feature flagging to our existing code.</p>
<p>Let’s understand the changes we’ve made here (I’ll explain below the code snippet):</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"errors"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"os"</span>

    flagsmith <span class="hljs-string">"github.com/Flagsmith/flagsmith-go-client/v3"</span>
    <span class="hljs-string">"github.com/gin-gonic/gin"</span>
    <span class="hljs-string">"github.com/go-redis/redis_rate/v10"</span>
    <span class="hljs-string">"github.com/joho/godotenv"</span>
    <span class="hljs-string">"github.com/redis/go-redis/v9"</span>
)

<span class="hljs-keyword">var</span> (
    redisClient     *redis.Client
    limiter         *redis_rate.Limiter
    flagsmithClient *flagsmith.Client
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">initClients</span><span class="hljs-params">()</span></span> {
    redisClient = redis.NewClient(&amp;redis.Options{
        Addr: os.Getenv(<span class="hljs-string">"REDIS_URL"</span>),
    })
    limiter = redis_rate.NewLimiter(redisClient)
    flagsmithClient = flagsmith.NewClient(os.Getenv(<span class="hljs-string">"FLAGSMITH_ENVIRONMENT_KEY"</span>))
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    err := godotenv.Load()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Printf(<span class="hljs-string">"Loading environment variable from the host system"</span>)
    } <span class="hljs-keyword">else</span> {
        log.Printf(<span class="hljs-string">"Loading environment from .env file"</span>)
    }

    initClients()
    <span class="hljs-keyword">defer</span> redisClient.Close()

    r := gin.Default()
    r.GET(<span class="hljs-string">"/ping"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        err, remainingLimit := rateLimitCall(c.ClientIP())
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            c.JSON(
                http.StatusTooManyRequests,
                gin.H{<span class="hljs-string">"error"</span>: <span class="hljs-string">"Rate Limit Hit"</span>})
        } <span class="hljs-keyword">else</span> {
            c.JSON(
                http.StatusOK,
                gin.H{<span class="hljs-string">"Your left over API request is"</span>: remainingLimit})
        }
    })
    r.GET(<span class="hljs-string">"/beta"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        flags := getFeatureFlags()
        isEnabled, _ := flags.IsFeatureEnabled(<span class="hljs-string">"beta"</span>)
        <span class="hljs-keyword">if</span> isEnabled {
            c.JSON(
                http.StatusOK,
                gin.H{<span class="hljs-string">"message"</span>: <span class="hljs-string">"This is beta endpoint"</span>})
        } <span class="hljs-keyword">else</span> {
            c.String(http.StatusNotFound, <span class="hljs-string">"404 page not found"</span>)
        }
    })

    r.Run(<span class="hljs-string">":"</span> + os.Getenv(<span class="hljs-string">"PORT"</span>))
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">rateLimitCall</span><span class="hljs-params">(ClientIP <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(error, <span class="hljs-keyword">int</span>)</span></span> {

    ctx := context.Background()

    flags := getFeatureFlags()
    rateLimitInterface, _ := flags.GetFeatureValue(<span class="hljs-string">"rate_limit"</span>)
    RATE_LIMIT := <span class="hljs-keyword">int</span>(rateLimitInterface.(<span class="hljs-keyword">float64</span>))
    fmt.Println(<span class="hljs-string">"Current Rate Limit is"</span>, RATE_LIMIT)

    res, err := limiter.Allow(ctx, ClientIP, redis_rate.PerHour(RATE_LIMIT))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

    <span class="hljs-keyword">if</span> res.Remaining == <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"You have hit the Rate Limit for the API. Try again later"</span>), <span class="hljs-number">0</span>
    }

    fmt.Println(<span class="hljs-string">"remaining request for"</span>, ClientIP, <span class="hljs-string">"is"</span>, res.Remaining)
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, res.Remaining
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getFeatureFlags</span><span class="hljs-params">()</span> <span class="hljs-title">flagsmith</span>.<span class="hljs-title">Flags</span></span> {
    ctx := context.Background()
    flags, _ := flagsmithClient.GetEnvironmentFlags(ctx)
    <span class="hljs-keyword">return</span> flags
}
</code></pre>
<h3 id="heading-understanding-the-feature-flag-code-logic">Understanding the Feature Flag Code Logic</h3>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getFeatureFlags</span><span class="hljs-params">()</span> <span class="hljs-title">flagsmith</span>.<span class="hljs-title">Flags</span></span> {
    ctx := context.Background()
    flags, _ := flagsmithClient.GetEnvironmentFlags(ctx)
    <span class="hljs-keyword">return</span> flags
}
</code></pre>
<p>First, let’s directly jump to the new <code>getFeatureFlags()</code> function we created at the bottom. This function will return all the flags we created on the Flagsmith dashboard, by calling the <code>GetEnvironmentFlags()</code> method on <code>flagsmithClient</code>.</p>
<p>We initiated the <code>flagsmithClient</code> inside the <code>initClients()</code> function. The Flagsmith Client needs the access key (the <code>NewClient()</code> function) that we can get from the Flagsmith dashboard. As we did for the Redis and Limter clients, we will store the client in a global variable for reusability. You’ll understand the dashboard, creating flags, and retrieving the key in later steps.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">rateLimitCall</span><span class="hljs-params">(ClientIP <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(error, <span class="hljs-keyword">int</span>)</span></span> {

    ctx := context.Background()

    flags := getFeatureFlags()
    rateLimitInterface, _ := flags.GetFeatureValue(<span class="hljs-string">"rate_limit"</span>)
    RATE_LIMIT := <span class="hljs-keyword">int</span>(rateLimitInterface.(<span class="hljs-keyword">float64</span>))
    fmt.Println(<span class="hljs-string">"Current Rate Limit is"</span>, RATE_LIMIT)

    res, err := limiter.Allow(ctx, ClientIP, redis_rate.PerHour(RATE_LIMIT))
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

    <span class="hljs-keyword">if</span> res.Remaining == <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"You have hit the Rate Limit for the API. Try again later"</span>), <span class="hljs-number">0</span>
    }

    fmt.Println(<span class="hljs-string">"remaining request for"</span>, ClientIP, <span class="hljs-string">"is"</span>, res.Remaining)
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, res.Remaining
}
</code></pre>
<p>Now coming to the <code>rateLimitCall()</code> function, instead of getting <code>RATE_LIMIT</code> from the environment, we get the value from the <code>rate_limit</code> flag (that we will create later). We call <code>getFeatureFlags()</code> and get the flag <code>rate_limit</code> value out from all the flags.</p>
<p>By setting these as feature flags, we can dynamically change the limit anytime from the dashboard. We don’t need to change the code’s functionality or do it the traditional way by changing the <code>RATE_LIMIT</code> value and re-running the server so that it catches new updated values.</p>
<pre><code class="lang-go">    r.GET(<span class="hljs-string">"/beta"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
        flags := getFeatureFlags()
        isEnabled, _ := flags.IsFeatureEnabled(<span class="hljs-string">"beta"</span>)
        <span class="hljs-keyword">if</span> isEnabled {
            c.JSON(
                http.StatusOK,
                gin.H{<span class="hljs-string">"message"</span>: <span class="hljs-string">"This is beta endpoint"</span>})
        } <span class="hljs-keyword">else</span> {
            c.String(http.StatusNotFound, <span class="hljs-string">"404 page not found"</span>)
        }
    })
</code></pre>
<p>Now coming to the <code>/beta</code> endpoint, based on whether the beta flag is enabled or disabled, this endpoint will serve the query. Otherwise, it will act as a non-reachable endpoint and return a 404 error message.</p>
<p>In our example, I have added a basic placeholder message to show how it will work, but this opens new possibilities in testing and initial releases (beta). If the API has a new endpoint, we can wrap the functionality in the feature flag and make it available and unavailable with a single click of a button. Also, we can do a lot more like scheduling and canary releases.</p>
<p>Also, our <code>.env</code> file will look like this. We have removed <code>RATE_LIMIT</code> and added <code>FLAGSMITH_ENVIRONMENT_KEY</code>.</p>
<pre><code class="lang-bash">REDIS_URL=localhost:6379
PORT=8080
FLAGSMITH_ENVIRONMENT_KEY=ser.ZRd***********469
</code></pre>
<h3 id="heading-how-to-create-feature-flags-in-the-flasgsmith-dashboard">How to Create Feature Flags in the Flasgsmith Dashboard</h3>
<p>Let’s head to the Flagsmith dashboard to create the flags we used above and get the access key. If you don’t have a Flagsmith account you can sign up for free <a target="_blank" href="https://app.flagsmith.com/signup">here</a>.</p>
<p>After you sign up you will be prompted to create an organisation and a project. Project separation is good, as it helps us isolate logic for different projects. Once you are done, you will see a dashboard, just like the screenshot below.</p>
<p>We have loads of functionalities from integrations to scheduling the flags to compare the changes. Apart from Go, Flagsmith provides many <a target="_blank" href="https://docs.flagsmith.com/clients/">SDKs</a>. You can click on where the language name is written and it will give you some boilerplate code for that language.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544211942/57f3651f-b62a-4b8f-beb7-4320ef0e0a8e.png" alt="Screenshot of a web interface labeled &quot;Features&quot; for managing feature flags and remote config. It includes examples of Go code for installing the SDK and initializing a project, with options to test API values. There are buttons and tabs for navigation and settings." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-rate-limiting-feature-flag">Rate Limiting Feature Flag</h3>
<p>Now, let's create our first feature flag for the rate limit. Click on the <strong>Create Feature</strong> button in the top right corner. A sidebar window will open up. Set the name, then to make the flag turn on the right way while creating, we can select <strong>Enabled by default.</strong></p>
<p>In the value section, we need to set the flag value. It can take formats like Txt, JSON, XML, and so on. As our feature value is simple text like 20, 30, and so on, we will choose Txt (the default one) and set a random limit – we’ll go with <strong>20</strong>.</p>
<p>You can also give tags and descriptions. Tags can be helpful when filtering out the Feature Flags. For example, we can create a tag <code>backend</code> to filter out all the feature flags related to Backend. The description is a concise explanation of what this particular future flag does when it is enabled (and will help with future understanding).</p>
<p>The screenshot below shows how it will look after filling in the details. Then, click on the <strong>Create Feature</strong> button to create the flag.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544238847/4e5cf3ab-1fb6-4783-afcc-39adcebae48e.png" alt="A screenshot of a web application interface showing the creation of a new feature. On the left, there is a menu with options like Features and SDK Keys. On the right, fields for adding a new feature are visible, including an ID/Name, a toggle for enabling by default, a value set to 20, and options for tags and descriptions. There is a note indicating feature creation for all environments, with a &quot;Create Feature&quot; button at the bottom." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-beta-feature-flag">Beta Feature Flag</h3>
<p>Let’s now create a second, <code>beta</code> feature flag. It will be the same process as the first one, but in this one, we don’t need to set any flag value and leave that column empty. Once we create both flags, our dashboard will look like this. It shows the flag name, value, current state (view), and so on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544256561/3735429f-8dd0-4f0f-a01e-b4a4a7b5aa75.png" alt="A software interface showcasing a &quot;Features&quot; section with toggles for &quot;beta&quot; and &quot;rate_limit&quot; features. The page includes navigation options on the left and buttons for creating features and running tests." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-getting-the-access-key">Getting the Access Key</h3>
<p>To get the Access Key, click on the <strong>SDK Keys</strong> from the sidebar, and click the <strong>Create Server-side Environment Key</strong> button to generate a key. As our app is server-side, it’s good to use that one only. Then copy and paste that key into the value placed in <code>.env</code> for the <code>FLAGSMITH_ENVIRONMENT_KEY</code> key.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544280780/fc37cb29-3069-4e2f-b35b-eea7632c47cd.png" alt="Screenshot of a software interface showing &quot;Client-side Environment Key&quot; and &quot;Server-side Environment Keys&quot; sections. A button labeled &quot;Create Server-side Environment Key&quot; is displayed prominently. The sidebar menu includes options like &quot;SDK Keys&quot; and &quot;Environment Settings.&quot;" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-running-the-api">Running the API</h3>
<p>Now everything is set, so let’s head over back to IDE and run the server by executing the <code>go run main.go</code> command in the terminal. We will see this message In the terminal. In case you encounter any errors, just check that the packages are correctly installed, the variables are correctly set, and the app accesses the Redis instance.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544780659/95fbbb17-43c3-4cd1-b84f-020c08ec38d3.png" alt="Screenshot of a VS Code window showing a Go project with the file &quot;main.go&quot; open. The code includes functions for rate limiting API calls and retrieving feature flags. The terminal at the bottom displays the output of running the application, with warnings and status messages related to a web server." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Now if we visit <a target="_blank" href="http://localhost:8080/ping"><strong>localhost:8080/ping</strong></a>, we will get a message <code>{"Your left over API request is":19}</code>. The limit was 20, we did one request now, and the remaining is 19.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544374092/bc97064c-285e-44fa-990d-51a52a671d26.png" alt="A browser window displaying a webpage at &quot;localhost:8080/ping&quot; showing the JSON message: {&quot;Your left over API request is&quot;: 19}." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-updating-the-ratelimit-flag">Updating the <code>rate_limit</code> Flag</h3>
<p>Let’s update the <code>rate_limit</code> flag value to 10 and see what happens. To do so, again visit the Flagsmith dashboard and click on the flag name. A side menu bar will open. Update the value to 10, and click on the <strong>Update Feature Value</strong> button.</p>
<p>We can also schedule the update. For example, this can be useful when we expect a spike in traffic at a certain timeframe and reduce the limit per user to reduce server load.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730545050685/f253ea86-de3d-4a6a-b5fd-35f489da86cf.png" alt="Screenshot of a software dashboard showing a feature management interface. The &quot;rate_limit&quot; feature is enabled with a value of 10. Options include editing value, segment overrides, and scheduling updates." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>If you now visit <a target="_blank" href="http://localhost:8080/ping"><strong>localhost:8080/ping</strong></a>, you will get a message <code>{"Your left over API request is":8}</code> – because the total limit is 10 and we have already requested two times.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544415108/97e6fd9c-b5a1-4143-877a-6724cf871a6b.png" alt="Browser window displaying a JSON response with the text: &quot;Your left over API request is: 8&quot;." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Let's now test the <code>/beta</code> endpoint. Visit <a target="_blank" href="http://localhost:8080/beta">localhost:8080/beta</a>, and we will see a message <code>{"message":"This is beta endpoint"}</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544479869/623d8b72-3648-46ae-b79f-409da44c1d38.png" alt="Screenshot of a web browser displaying JSON data at the URL &quot;localhost:8080/beta&quot; with the message: &quot;This is beta endpoint&quot;." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Now go back to the Flagsmith dashboard and toggle the switch to disable this flag. Now visit the the URL. You will get a 404 message like this endpoint never existed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544488522/518c58b2-f767-4396-9020-99e8cd01586a.png" alt="Screenshot of a browser window displaying a &quot;404 page not found&quot; error message." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Now that we’ve set up the functionality and demoed the feature flagging capabilities, let’s see how we can integrate the Flasgsmith GitHub App.</p>
<h3 id="heading-how-to-integrate-feature-flags-with-the-github-app">How to Integrate Feature Flags with the GitHub App</h3>
<p>First, make sure you have pushed your app to GitHub. After that, install the GitHub Flasgsmith App on your repo from the <a target="_blank" href="https://github.com/apps/flagsmith">GitHub Marketplace</a>.</p>
<p>By integrating GitHub and Falagsmith, we can view updates on your feature flags/features as comments in GitHub Issues and Pull Requests. This allows us to easily track features, from creating an issue to merging a PR and deploying the changes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544845464/dcce9af3-a34f-420a-b9c4-2968d47fda70.png" alt="Screenshot of the Flagsmith GitHub app integration page, detailing its features and benefits, with an option to install the app." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Then select your organisation and the repositories where you want to install the app. You can install it on all of your repos or select a particular one.</p>
<p>As you install it, you will be auto-redirected to the Flagmsith dashboard to configure and complete the integration. Most of the data will be pre-populated, so you just need to select and add a project, and then save the configuration.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730544640407/8ac36f96-d61b-47f7-ad3a-f914f0f01824.png" alt="Screenshot of a webpage for configuring GitHub integration with Flagsmith. It includes fields for selecting the organization, project, and repository, with options set for &quot;Pradumna,&quot; &quot;go-api,&quot; and &quot;go-redis-flagsmith.&quot; There is an &quot;Add Project&quot; button and a &quot;Save Configuration&quot; button at the bottom." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Once you hit the Save <strong>Configuration Button</strong>, it will redirect you back to the main Flagsmith dashboard where we were previously working.</p>
<p>Now let’s link one of the existing flags with the GitHub issue/pull request (raise a dummy PR/issue to test it), or you can create a new flag to test. Let’s proceed with the beta flag which we already created for the <code>beta</code> endpoint.</p>
<p>To link the flag with an existing issue or a pull request, click on the flag name, and a side menu will pop up from the right. Then, choose the 'Link' tab. Then select the Pull Request option, and choose the Pull Request you want to link. All of your Issues and Pull Requests linked to this flag are visible below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730546404697/0fb1c515-ab42-494d-ac84-87e076d30607.png" alt="A screenshot of a development environment interface showing the &quot;Features&quot; section, with a sidebar menu on the left. The &quot;Edit Feature: beta&quot; panel is open on the right, displaying options to link an issue or pull request and a listed pull request titled &quot;feat: Update the beta endpoint feature (#2)&quot; with its status marked as open." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>To verify that the flag is successfully linked, click the hyperlink with the arrow icon below the <strong>Name</strong> column heading. It will navigate you to that particular Issue/Pull Request on GitHub. You can see that the Flagsmith GitHub App has commented below with all the details, such as environment, enabled value, and so on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730545132237/4601a08e-62d9-4ebc-890e-89430bf6624e.png" alt="GitHub pull request page showing a request titled &quot;feat: Update the beta endpoint feature #2&quot; to merge a commit from the &quot;beta&quot; branch into &quot;main&quot;. It includes a user comment about the update and a Flagsmith bot comment showing feature status for production and development environments. The pull request is open, with no reviews yet." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-testing-the-flagsmith-github-app">Testing the Flagsmith GitHub App</h3>
<p>After this, when you make any changes to the flag settings, such as turning on/off the flag or changing the value, the bot will comment with all the updated details.</p>
<p>Let’s test by turning the flag off. As soon as you turn off the flash from the Dashboard, the bot should comment that the flag has now been disabled:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730545146615/b76f2f21-369a-4617-b55b-abc3201a1c52.png" alt="Image showing a GitHub pull request interface. The pull request is titled &quot;feat: Update the beta endpoint feature #2&quot; and shows an update from the flagsmith bot indicating that the &quot;beta&quot; feature for the &quot;Development&quot; environment is currently disabled." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>That’s it. That is how it’s simple to integrate Flagsmith with GitHub.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>To sum it up, you now know how you can leverage feature flags as a backend developer to change the functionality of your app on the fly.</p>
<p>To take things to the next level, we integrated our demo app with the Flagsmith GitHub app so it could stay updated with the changes to our feature flags’ status on Pull Requests/Issues without having to manually update them.</p>
<p>Check out the Flagsmith <a target="_blank" href="https://github.com/Flagsmith/flagsmith">repo here</a> and don't forget to give each of these projects a star to show your support. You can also join their amazing <a target="_blank" href="https://discord.com/invite/hFhxNtXzgm">community</a> to get technical support.</p>
<p>You can connect with me - Pradumna Saraf, on socials <a target="_blank" href="https://links.pradumnasaraf.dev/">here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <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>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Distributed Rate Limiting System Using Redis and Lua Scripts ]]>
                </title>
                <description>
                    <![CDATA[ In this comprehensive guide, you’ll build a distributed rate limiter using Redis and Lua scripting to control user requests in a high-traffic environment. Rate limiting is crucial in any system to prevent abuse, manage traffic, and protect your resou... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-rate-limiting-system-using-redis-and-lua/</link>
                <guid isPermaLink="false">673ca31d6c7f56d7a860bbdf</guid>
                
                    <category>
                        <![CDATA[ Lua ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Birkaran Sachdev ]]>
                </dc:creator>
                <pubDate>Tue, 19 Nov 2024 14:39:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/CGWK6k2RduY/upload/a5ac857cec1d18720a060fc5e3462cf3.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this comprehensive guide, you’ll build a distributed rate limiter using Redis and Lua scripting to control user requests in a high-traffic environment.</p>
<p>Rate limiting is crucial in any system to prevent abuse, manage traffic, and protect your resources. By leveraging Redis and Lua, you'll build an efficient, scalable rate limiting system that can handle a large number of requests while keeping your backend services safe.</p>
<p>We will also include an interactive demo where users can simulate traffic, observe rate limits being enforced, and view logs of blocked requests.</p>
<h2 id="heading-what-you-will-learn">What You Will Learn</h2>
<ul>
<li><p>How to build a rate limiting system using Redis.</p>
</li>
<li><p>How to use Lua scripts with Redis to achieve atomic operations.</p>
</li>
<li><p>Understanding Redis data structures for efficient request tracking.</p>
</li>
<li><p>Techniques for handling high traffic in a distributed system.</p>
</li>
<li><p>Using Docker to simulate and scale a distributed rate limiter.</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, ensure you have the following installed:</p>
<ul>
<li><p>Node.js (v14 or higher)</p>
</li>
<li><p>Redis</p>
</li>
<li><p>Docker (for simulating a distributed environment)</p>
</li>
<li><p>Basic understanding of Node.js, Redis, and Lua scripting.</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-you-will-learn">What You Will Learn</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<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-how-to-set-up-the-project">Step 1: How to Set Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-how-to-set-up-redis">Step 2: How to Set Up Redis</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-how-to-implement-the-rate-limiter-with-redis-and-lua">Step 3: How to Implement the Rate Limiter with Redis and Lua</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-how-to-create-the-nodejs-api-server">Step 4: How to Create the Node.js API Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-how-to-test-the-rate-limiter">Step 5: How to Test the Rate Limiter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-how-to-visualize-rate-limiting-metrics">Step 6: How to Visualize Rate Limiting Metrics</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-7-how-to-deploy-with-docker">Step 7: How to Deploy with Docker</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>In this tutorial, you will:</p>
<ol>
<li><p>Build a rate limiter using Redis and Lua to enforce request quotas.</p>
</li>
<li><p>Use Lua scripts to ensure atomic operations, avoiding race conditions.</p>
</li>
<li><p>Implement a token bucket algorithm for rate limiting.</p>
</li>
<li><p>Create an interactive demo to simulate high traffic and visualize rate limiting in action.</p>
</li>
</ol>
<h3 id="heading-system-architecture">System Architecture</h3>
<p>You'll build the system with the following components:</p>
<ol>
<li><p><strong>API Server</strong>: Handles incoming user requests.</p>
</li>
<li><p><strong>Redis</strong>: Stores request data and enforces rate limits.</p>
</li>
<li><p><strong>Lua Scripts</strong>: Ensures atomic updates to Redis for rate limiting.</p>
</li>
<li><p><strong>Docker</strong>: Simulates a distributed environment with multiple instances.</p>
</li>
</ol>
<h2 id="heading-step-1-how-to-set-up-the-project">Step 1: How to Set Up the Project</h2>
<p>Let's start by setting up our Node.js project:</p>
<pre><code class="lang-javascript">mkdir distributed-rate-limiter
cd distributed-rate-limiter
npm init -y
</code></pre>
<p>Next, install the required dependencies:</p>
<pre><code class="lang-javascript">npm install express redis dotenv
</code></pre>
<ul>
<li><p><strong>express</strong>: A lightweight web server framework.</p>
</li>
<li><p><strong>redis</strong>: For interacting with Redis.</p>
</li>
<li><p><strong>dotenv</strong>: For managing environment variables.</p>
</li>
</ul>
<p>Create a <strong>.env</strong> file with the following content:</p>
<pre><code class="lang-javascript">REDIS_HOST=localhost
REDIS_PORT=<span class="hljs-number">6379</span>
PORT=<span class="hljs-number">3000</span>
RATE_LIMIT=<span class="hljs-number">5</span>
TIME_WINDOW=<span class="hljs-number">60</span>
</code></pre>
<p>These variables define the Redis host, port, rate limit (number of allowed requests), and the time window (in seconds).</p>
<h2 id="heading-step-2-how-to-set-up-redis">Step 2: How to Set Up Redis</h2>
<p>Before we dive into the code, ensure that Redis is installed and running on your system. If you don’t have Redis installed, you can use Docker to quickly set it up:</p>
<pre><code class="lang-javascript">docker run -p <span class="hljs-number">6379</span>:<span class="hljs-number">6379</span> --name redis-rate-limiter -d redis
</code></pre>
<h2 id="heading-step-3-how-to-implement-the-rate-limiter-with-redis-and-lua">Step 3: How to Implement the Rate Limiter with Redis and Lua</h2>
<p>To efficiently handle rate limiting, we'll use a token bucket algorithm. In this algorithm:</p>
<ol>
<li><p>Each user has a “bucket” of tokens.</p>
</li>
<li><p>Each request consumes a token.</p>
</li>
<li><p>Tokens refill periodically at a set rate.</p>
</li>
</ol>
<p>To ensure atomicity and avoid race conditions, we'll use Lua scripting with Redis. Lua scripts in Redis execute atomically, which means they can’t be interrupted by other operations while running.</p>
<h3 id="heading-how-to-create-a-lua-script-for-rate-limiting">How to Create a Lua Script for Rate Limiting</h3>
<p>Create a file called <strong>rate_limiter.lua</strong>:</p>
<pre><code class="lang-javascript">local key = KEYS[<span class="hljs-number">1</span>]
local limit = tonumber(ARGV[<span class="hljs-number">1</span>])
local <span class="hljs-built_in">window</span> = tonumber(ARGV[<span class="hljs-number">2</span>])
local current = redis.call(<span class="hljs-string">"get"</span>, key)

<span class="hljs-keyword">if</span> current and tonumber(current) &gt;= limit then
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>
<span class="hljs-keyword">else</span>
    <span class="hljs-keyword">if</span> current then
        redis.call(<span class="hljs-string">"incr"</span>, key)
    <span class="hljs-keyword">else</span>
        redis.call(<span class="hljs-string">"set"</span>, key, <span class="hljs-number">1</span>, <span class="hljs-string">"EX"</span>, <span class="hljs-built_in">window</span>)
    end
    <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
end
</code></pre>
<ol>
<li><p><strong>Inputs</strong>:</p>
<ul>
<li><p><strong>KEYS[1]</strong>: The Redis key representing the user’s request count.</p>
</li>
<li><p><strong>ARGV[1]</strong>: The rate limit (maximum number of allowed requests).</p>
</li>
<li><p><strong>ARGV[2]</strong>: The time window (in seconds) for the rate limit.</p>
</li>
</ul>
</li>
<li><p><strong>Logic</strong>:</p>
<ul>
<li><p>If the user has reached the rate limit, return <code>0</code> (request blocked).</p>
</li>
<li><p>If the user is within the limit, increment their request count or set a new count with an expiration if it's the first request.</p>
</li>
<li><p>Return 1 (request allowed).</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-step-4-how-to-create-the-nodejs-api-server">Step 4: How to Create the Node.js API Server</h2>
<p>Create a file called <strong>server.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> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);

<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> client = redis.createClient({
  <span class="hljs-attr">host</span>: process.env.REDIS_HOST,
  <span class="hljs-attr">port</span>: process.env.REDIS_PORT
});

<span class="hljs-keyword">const</span> rateLimitScript = fs.readFileSync(path.join(__dirname, <span class="hljs-string">'rate_limiter.lua'</span>), <span class="hljs-string">'utf8'</span>);

<span class="hljs-keyword">const</span> RATE_LIMIT = <span class="hljs-built_in">parseInt</span>(process.env.RATE_LIMIT);
<span class="hljs-keyword">const</span> TIME_WINDOW = <span class="hljs-built_in">parseInt</span>(process.env.TIME_WINDOW);

<span class="hljs-comment">// Middleware for rate limiting</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rateLimiter</span>(<span class="hljs-params">req, res, next</span>) </span>{
  <span class="hljs-keyword">const</span> ip = req.ip;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> allowed = <span class="hljs-keyword">await</span> client.eval(rateLimitScript, <span class="hljs-number">1</span>, ip, RATE_LIMIT, TIME_WINDOW);
    <span class="hljs-keyword">if</span> (allowed === <span class="hljs-number">1</span>) {
      next();
    } <span class="hljs-keyword">else</span> {
      res.status(<span class="hljs-number">429</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Too many requests. Please try again later.'</span> });
    }
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error in rate limiter:'</span>, err);
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Internal server error'</span> });
  }
}

app.use(rateLimiter);

app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">'Welcome to the Rate Limited API!'</span>);
});

<span class="hljs-keyword">const</span> PORT = process.env.PORT;
app.listen(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">${PORT}</span>`</span>);
});
</code></pre>
<ol>
<li><p><strong>Rate Limiter Middleware</strong>:</p>
<ul>
<li><p>Retrieves the client's IP address and checks if they are within the rate limit using the Lua script.</p>
</li>
<li><p>If the user exceeds the limit, a <code>429</code> response is sent.</p>
</li>
</ul>
</li>
<li><p><strong>API Endpoint</strong>:</p>
<ul>
<li>The root endpoint is rate-limited, so users can only access it a limited number of times within the specified window.</li>
</ul>
</li>
</ol>
<h2 id="heading-step-5-how-to-test-the-rate-limiter">Step 5: How to Test the Rate Limiter</h2>
<ol>
<li><p><strong>Start Redis</strong>:</p>
<pre><code class="lang-basic"> docker start redis-rate-limiter
</code></pre>
</li>
<li><p><strong>Run the Node.js Server</strong>:</p>
<pre><code class="lang-bash"> node server.js
</code></pre>
</li>
<li><p><strong>Simulate Requests</strong>:</p>
<ul>
<li><p>Use <code>curl</code> or Postman to test the rate limiter:</p>
<pre><code class="lang-bash">  curl http://localhost:3000
</code></pre>
</li>
<li><p>Send multiple requests rapidly to see rate limiting in action.</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-step-6-how-to-visualize-rate-limiting-metrics">Step 6: How to Visualize Rate Limiting Metrics</h2>
<p>To monitor rate limiting metrics like cache hits and blocked requests, we'll add logging to the middleware in <strong>server.js</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rateLimiter</span>(<span class="hljs-params">req, res, next</span>) </span>{
  <span class="hljs-keyword">const</span> ip = req.ip;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> allowed = <span class="hljs-keyword">await</span> client.eval(rateLimitScript, <span class="hljs-number">1</span>, ip, RATE_LIMIT, TIME_WINDOW);
    <span class="hljs-keyword">if</span> (allowed === <span class="hljs-number">1</span>) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Allowed request from <span class="hljs-subst">${ip}</span>`</span>);
      next();
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Blocked request from <span class="hljs-subst">${ip}</span>`</span>);
      res.status(<span class="hljs-number">429</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Too many requests. Please try again later.'</span> });
    }
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error in rate limiter:'</span>, err);
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Internal server error'</span> });
  }
}
</code></pre>
<h2 id="heading-step-7-how-to-deploy-with-docker">Step 7: How to Deploy with Docker</h2>
<p>Let’s containerize the application to run it in a distributed environment.</p>
<p>Create a <code>Dockerfile</code>:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"server.js"</span>]</span>
</code></pre>
<p>Build and Run the Docker Container:</p>
<pre><code class="lang-bash">docker build -t rate-limiter .
docker run -p 3000:3000 rate-limiter
</code></pre>
<p>Now you can scale the rate limiter by running multiple instances.</p>
<h2 id="heading-conclusion-what-youve-learned">Conclusion: What You’ve Learned</h2>
<p>Congratulations! You’ve successfully built a distributed rate limiter using Redis and Lua scripts. Throughout this tutorial, you’ve learned how to:</p>
<ol>
<li><p>Implement rate limiting to control user requests in a distributed system.</p>
</li>
<li><p>Use Lua scripts in Redis to perform atomic operations.</p>
</li>
<li><p>Apply a token bucket algorithm to manage request quotas.</p>
</li>
<li><p>Monitor rate limiting metrics to optimize performance.</p>
</li>
<li><p>Use Docker to simulate a scalable distributed environment.</p>
</li>
</ol>
<h3 id="heading-next-steps">Next Steps:</h3>
<ol>
<li><p><strong>Add Rate Limiting by User ID</strong>: Extend the system to support rate limits per user.</p>
</li>
<li><p><strong>Integrate with Nginx</strong>: Use Nginx as a reverse proxy with Redis-backed rate limiting.</p>
</li>
<li><p><strong>Deploy with Kubernetes</strong>: Scale your rate limiter using Kubernetes for high availability.</p>
</li>
</ol>
<p>Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Build a Real-Time Multiplayer Tic-Tac-Toe Game Using WebSockets and Microservices ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, we’ll build a real-time multiplayer Tic-Tac-Toe game using Node.js, Socket.IO, and Redis. This game allows two players to connect from different browser tabs, take turns playing, and see real-time updates as they play. We'll use Red... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-real-time-multiplayer-tic-tac-toe-game-using-websockets-and-microservices/</link>
                <guid isPermaLink="false">673bb7ca83cab2eb3eafb7af</guid>
                
                    <category>
                        <![CDATA[ SocketIO ]]>
                    </category>
                
                    <category>
                        <![CDATA[ websockets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Birkaran Sachdev ]]>
                </dc:creator>
                <pubDate>Mon, 18 Nov 2024 21:55:22 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731400068976/3c951db9-929a-4d13-ba77-759932833a9a.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, we’ll build a <strong>real-time multiplayer Tic-Tac-Toe game</strong> using <strong>Node.js</strong>, <strong>Socket.IO</strong>, and <strong>Redis</strong>. This game allows two players to connect from different browser tabs, take turns playing, and see real-time updates as they play. We'll use <strong>Redis</strong> to manage game state synchronization across multiple WebSocket servers, making our application scalable.</p>
<p>By the end, you'll have a fully functional game with real-time capabilities and a solid understanding of how to use WebSockets and Redis to build scalable real-time applications.</p>
<h3 id="heading-what-you-will-learn">What You Will Learn</h3>
<ul>
<li><p>How to use <strong>Socket.IO</strong> for real-time communication.</p>
</li>
<li><p>How to use <strong>Redis Pub/Sub</strong> to synchronize game state across multiple clients.</p>
</li>
<li><p>How to set up a scalable WebSocket server architecture.</p>
</li>
</ul>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Before we start, make sure you have the following installed:</p>
<ul>
<li><p>Node.js (v16 or higher)</p>
</li>
<li><p>Redis</p>
</li>
<li><p>Docker (optional, for running Redis in a container)</p>
</li>
<li><p>Basic knowledge of JavaScript, Node.js, and WebSockets.</p>
</li>
</ul>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></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-your-development-environment">Step 1: Setting Up Your Development Environment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-setting-up-the-project">Step 2: Setting Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-implementing-the-websocket-server-with-redis">Step 3: Implementing the WebSocket Server with Redis</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-implement-the-react-frontend-interface">Step 4: Implement the React Frontend interface</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-running-the-application">Step 5: Running the Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-viewing-redis-messages-in-real-time">Step 6: Viewing Redis Messages in Real-Time</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-demo">Demo</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-project-overview"><strong>Project Overview</strong></h2>
<p>We'll build a real-time Tic-Tac-Toe game with the following features:</p>
<ul>
<li><p><strong>Two players</strong> can connect and play a game.</p>
</li>
<li><p>The game board updates in real-time across different browser tabs.</p>
</li>
<li><p>The game announces a winner or declares a draw when the board is full.</p>
</li>
</ul>
<p>We’ll use:</p>
<ul>
<li><p><strong>Node.js</strong> with <strong>Socket.IO</strong> for handling WebSocket connections.</p>
</li>
<li><p><strong>Redis</strong> Pub/Sub to manage game state synchronization across clients.</p>
</li>
</ul>
<h2 id="heading-step-1-setting-up-your-development-environment"><strong>Step 1: Setting Up Your Development Environment</strong></h2>
<h3 id="heading-installing-nodejs"><strong>Installing Node.js</strong></h3>
<p>Ensure you have Node.js installed on your system:</p>
<pre><code class="lang-bash">node -v
</code></pre>
<p>If you don’t have it installed, download it from <a target="_blank" href="https://nodejs.org/en">Node.js.</a></p>
<h3 id="heading-installing-redis"><strong>Installing Redis</strong></h3>
<p>You can install Redis locally or run it in a Docker container.</p>
<h4 id="heading-macos-using-homebrew"><strong>macOS (Using Homebrew)</strong></h4>
<p>First, ensure that you have <a target="_blank" href="https://brew.sh/">Homebrew</a> installed on your system before running the commands below:</p>
<pre><code class="lang-bash">brew install redis
brew services start redis
</code></pre>
<p>Verify that the Redis container is running with the following command:</p>
<pre><code class="lang-bash">redis-cli ping
</code></pre>
<p>You should see:</p>
<pre><code class="lang-bash">PONG
</code></pre>
<h4 id="heading-using-docker-to-run-redis"><strong>Using Docker to Run Redis</strong></h4>
<pre><code class="lang-bash">docker run --name redis-server -p 6379:6379 -d redis
</code></pre>
<p>Check if Redis is running using:</p>
<pre><code class="lang-bash">docker <span class="hljs-built_in">exec</span> -it redis-server redis-cli ping
</code></pre>
<h2 id="heading-step-2-setting-up-the-project"><strong>Step 2: Setting Up the Project</strong></h2>
<h3 id="heading-1-create-the-project-directory"><strong>1. Create the Project Directory</strong></h3>
<pre><code class="lang-bash">mkdir tic-tac-toe
<span class="hljs-built_in">cd</span> tic-tac-toe
npm init -y
</code></pre>
<h3 id="heading-2-install-dependencies"><strong>2. Install Dependencies</strong></h3>
<pre><code class="lang-bash">npm install express socket.io redis dotenv
</code></pre>
<h3 id="heading-3-create-environment-variables"><strong>3. Create Environment Variables</strong></h3>
<p>Create a <code>.env</code> file in your project root with the following contents:</p>
<pre><code class="lang-bash">PORT=3000
REDIS_HOST=localhost
REDIS_PORT=6379
</code></pre>
<h2 id="heading-step-3-implementing-the-websocket-server-with-redis"><strong>Step 3: Implementing the WebSocket Server with Redis</strong></h2>
<p>In this step, we'll set up a WebSocket server that handles real-time game interactions using <strong>Node.js</strong>, <strong>Socket.IO</strong>, and <strong>Redis</strong>. This server will manage the game state, handle player moves, and ensure synchronization across multiple clients using Redis Pub/Sub.</p>
<p>We'll break down each section of the code so you understand exactly how everything fits together.<strong>Server Code Explanation</strong></p>
<p>Create a file named <code>server.js</code> and add the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>;
<span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> http <span class="hljs-keyword">from</span> <span class="hljs-string">'http'</span>;
<span class="hljs-keyword">import</span> { Server } <span class="hljs-keyword">from</span> <span class="hljs-string">'socket.io'</span>;
<span class="hljs-keyword">import</span> { createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'redis'</span>;

dotenv.config(); <span class="hljs-comment">// Load environment variables from .env file</span>

<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> server = http.createServer(app);
<span class="hljs-keyword">const</span> io = <span class="hljs-keyword">new</span> Server(server, {
  <span class="hljs-attr">cors</span>: {
    <span class="hljs-attr">origin</span>: <span class="hljs-string">"http://localhost:5173"</span>,
    <span class="hljs-attr">methods</span>: [<span class="hljs-string">"GET"</span>, <span class="hljs-string">"POST"</span>],
  }
});
</code></pre>
<ul>
<li><p><strong>dotenv</strong>: Loads environment variables from a <code>.env</code> file to keep sensitive information like ports and keys secure.</p>
</li>
<li><p><strong>express</strong>: Sets up a basic Express server to handle HTTP requests.</p>
</li>
<li><p><strong>http</strong>: We create an HTTP server using Node's built-in <code>http</code> module, which we'll use with <strong>Socket.IO</strong> for WebSocket communication.</p>
</li>
<li><p><strong>Socket.IO</strong>: This library enables real-time, bidirectional communication between the server and clients.</p>
</li>
<li><p><strong>CORS Configuration</strong>: Allows cross-origin requests from our frontend running on <code>localhost:5173</code>.</p>
</li>
</ul>
<p>Then, to create Redis publisher and subscriber clients, we’ll add the following code to <code>server.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Initialize Redis clients</span>
<span class="hljs-keyword">const</span> pubClient = createClient();
<span class="hljs-keyword">const</span> subClient = createClient();
<span class="hljs-keyword">await</span> pubClient.connect();
<span class="hljs-keyword">await</span> subClient.connect();
</code></pre>
<p>We use <strong>Redis</strong> to handle real-time data synchronization between connected clients.</p>
<ul>
<li><p><strong>pubClient</strong>: Used to publish messages (like game state updates).</p>
</li>
<li><p><strong>subClient</strong>: Subscribes to messages (listens for updates).</p>
</li>
</ul>
<ul>
<li><strong>connect()</strong>: Establishes a connection to the Redis server.</li>
</ul>
<p>In this paradigm, one client is used to publish updates, and the other one subscribes to updates. This helps avoid blocking behavior, since Redis clients in <strong>subscribe</strong> mode can only receive messages.</p>
<p>To subscribe to Redis channels for game updates, we’ll add the following code to <code>server.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Subscribe to the Redis channel for game updates</span>
<span class="hljs-keyword">await</span> subClient.subscribe(<span class="hljs-string">'game-moves'</span>, <span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
  gameState = <span class="hljs-built_in">JSON</span>.parse(message);
  io.emit(<span class="hljs-string">'gameState'</span>, gameState);
});
</code></pre>
<ul>
<li><p><strong>subClient.subscribe</strong>: Listens for messages on the <code>game-moves</code> channel.</p>
</li>
<li><p>Whenever a new move is made by a player, the game state is updated in Redis, and all connected clients are informed of the new state.</p>
</li>
<li><p>The <code>message</code> parameter contains the game state as a string. We parse it into a JavaScript object and broadcast the updated state using <strong>Socket.IO</strong>.</p>
</li>
</ul>
<p>Next, to define the game state and functions, we’ll add the following code to <code>server.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Define initial game state</span>
<span class="hljs-keyword">let</span> gameState = {
  <span class="hljs-attr">board</span>: <span class="hljs-built_in">Array</span>(<span class="hljs-number">9</span>).fill(<span class="hljs-literal">null</span>),
  <span class="hljs-attr">xIsNext</span>: <span class="hljs-literal">true</span>,
};

<span class="hljs-comment">// Function to reset the game</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">resetGame</span>(<span class="hljs-params"></span>) </span>{
  gameState = {
    <span class="hljs-attr">board</span>: <span class="hljs-built_in">Array</span>(<span class="hljs-number">9</span>).fill(<span class="hljs-literal">null</span>),
    <span class="hljs-attr">xIsNext</span>: <span class="hljs-literal">true</span>,
  };
}
</code></pre>
<ul>
<li><p><strong>gameState</strong>: Keeps track of the current state of the board and whose turn it is (<code>xIsNext</code>).</p>
<ul>
<li><p>The board is represented as an array of 9 cells (each can be 'X', 'O', or <code>null</code>).</p>
</li>
<li><p>The <code>xIsNext</code> flag determines which player's turn it is.</p>
</li>
</ul>
</li>
<li><p><strong>resetGame()</strong>: Resets the board and turn indicator to their initial state, allowing for a new game to start.</p>
</li>
</ul>
<p>Next, to handle WebSocket connections, let’s add the following code to <code>server.js</code>:</p>
<pre><code class="lang-javascript">io.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'New client connected:'</span>, socket.id);

  <span class="hljs-comment">// Send the current game state to the newly connected client</span>
  socket.emit(<span class="hljs-string">'gameState'</span>, gameState);
</code></pre>
<ul>
<li><p>The <code>io.on('connection')</code> event is triggered when a new client connects.</p>
</li>
<li><p><strong>socket.id</strong>: A unique identifier for each connected client.</p>
</li>
<li><p>We immediately send the current <code>gameState</code> to the new client so they can see the current board.</p>
</li>
</ul>
<p>To handle player moves, we’ll add the following code to <code>server.js</code>:</p>
<pre><code class="lang-javascript">  <span class="hljs-comment">// Handle player moves</span>
  socket.on(<span class="hljs-string">'makeMove'</span>, <span class="hljs-function">(<span class="hljs-params">index</span>) =&gt;</span> {
    <span class="hljs-comment">// Prevent making a move if cell is already taken or game is over</span>
    <span class="hljs-keyword">if</span> (gameState.board[index] || calculateWinner(gameState.board)) <span class="hljs-keyword">return</span>;

    <span class="hljs-comment">// Update the board and switch turns</span>
    gameState.board[index] = gameState.xIsNext ? <span class="hljs-string">'X'</span> : <span class="hljs-string">'O'</span>;
    gameState.xIsNext = !gameState.xIsNext;

    <span class="hljs-comment">// Publish the updated game state to Redis</span>
    pubClient.publish(<span class="hljs-string">'game-moves'</span>, <span class="hljs-built_in">JSON</span>.stringify(gameState));
    io.emit(<span class="hljs-string">'gameState'</span>, gameState);
  });
</code></pre>
<ul>
<li><p><strong>makeMove</strong>: This event is triggered when a player clicks on a cell.</p>
<ul>
<li><p><strong>Validation</strong>: We check if the cell is already occupied or if the game has ended before making a move.</p>
</li>
<li><p><strong>Updating Game State</strong>: If the move is valid, we update the board and switch turns.</p>
</li>
</ul>
</li>
<li><p>The updated game state is then:</p>
<ol>
<li><p><strong>Published to Redis</strong>: This ensures that all instances of the server stay in sync.</p>
</li>
<li><p><strong>Broadcasted to all clients</strong>: This immediately updates the game board for all players.</p>
</li>
</ol>
</li>
</ul>
<p>To handle game restarts, we’ll add the following code to <code>server.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Handle game restarts</span>
socket.on(<span class="hljs-string">'restartGame'</span>, <span class="hljs-function">() =&gt;</span> {
  resetGame();
  io.emit(<span class="hljs-string">'gameState'</span>, gameState);
});
</code></pre>
<p>To handle client disconnection handling, we’ll add the following code to <code>server.js</code>:</p>
<pre><code class="lang-javascript"> socket.on(<span class="hljs-string">'disconnect'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Client disconnected:'</span>, socket.id);
  });
});
</code></pre>
<p>Finally, to process the logic of the game, we’ll add the following functions to <code>server.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Function to check if there's a winner</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateWinner</span>(<span class="hljs-params">board</span>) </span>{
  <span class="hljs-keyword">const</span> lines = [
    [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>], [<span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>], [<span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>],
    [<span class="hljs-number">0</span>, <span class="hljs-number">3</span>, <span class="hljs-number">6</span>], [<span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">7</span>], [<span class="hljs-number">2</span>, <span class="hljs-number">5</span>, <span class="hljs-number">8</span>],
    [<span class="hljs-number">0</span>, <span class="hljs-number">4</span>, <span class="hljs-number">8</span>], [<span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">6</span>]
  ];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> [a, b, c] <span class="hljs-keyword">of</span> lines) {
    <span class="hljs-keyword">if</span> (board[a] &amp;&amp; board[a] === board[b] &amp;&amp; board[a] === board[c]) {
      <span class="hljs-keyword">return</span> board[a];
    }
  }
  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isBoardFull</span>(<span class="hljs-params">board</span>) </span>{
  <span class="hljs-keyword">return</span> board.every(<span class="hljs-function">(<span class="hljs-params">cell</span>) =&gt;</span> cell !== <span class="hljs-literal">null</span>);
}
</code></pre>
<ul>
<li><p><strong>calculateWinner()</strong>: Checks if there’s a winning combination on the board.</p>
</li>
<li><p><strong>isBoardFull()</strong>: Checks if all cells are filled, indicating a draw.</p>
</li>
</ul>
<h2 id="heading-step-4-implement-the-react-frontend-interface"><strong>Step 4: Implement the React Frontend interface</strong></h2>
<p>In this step, we build a simple and interactive React frontend for our Tic-Tac-Toe game. This frontend allows players to connect to the WebSocket server, make moves, and see the game board update in real-time.</p>
<p>In <code>App.jsx</code>, add the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> io <span class="hljs-keyword">from</span> <span class="hljs-string">'socket.io-client'</span>;

<span class="hljs-keyword">const</span> socket = io(<span class="hljs-string">'http://localhost:3000'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [gameState, setGameState] = useState({
    <span class="hljs-attr">board</span>: <span class="hljs-built_in">Array</span>(<span class="hljs-number">9</span>).fill(<span class="hljs-literal">null</span>),
    <span class="hljs-attr">xIsNext</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">winner</span>: <span class="hljs-literal">null</span>
  });

  useEffect(<span class="hljs-function">() =&gt;</span> {
    socket.on(<span class="hljs-string">'gameState'</span>, <span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> {
      setGameState(state);
    });

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> socket.off(<span class="hljs-string">'gameState'</span>);
  }, []);

  <span class="hljs-keyword">const</span> handleClick = <span class="hljs-function">(<span class="hljs-params">index</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (gameState.board[index] || gameState.winner) <span class="hljs-keyword">return</span>;
    socket.emit(<span class="hljs-string">'makeMove'</span>, index);
  };

  <span class="hljs-keyword">const</span> renderCell = <span class="hljs-function">(<span class="hljs-params">index</span>) =&gt;</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> handleClick(index)}&gt;{gameState.board[index]}<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
  );

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Multiplayer Tic-Tac-Toe<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"board"</span>&gt;</span>
        {[...Array(9)].map((_, i) =&gt; renderCell(i))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> socket.emit('restartGame')}&gt;Restart Game<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Here is a summary of how the React app is broken down:</p>
<ul>
<li><p><strong>WebSocket Connection</strong>:</p>
<ul>
<li>The frontend establishes a connection to the server using <code>socket.io-client</code>.</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>State Management</strong>:</p>
<ul>
<li><p>The game state (<code>gameState</code>) is managed with React's <code>useState</code> and includes:</p>
<ul>
<li><p>The <strong>board</strong> (9 cells).</p>
</li>
<li><p>The flag <strong>xIsNext</strong> to indicate the current player's turn.</p>
</li>
<li><p>The <strong>winner</strong> status.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Real-Time Updates</strong>:</p>
<ul>
<li><p>The <code>useEffect</code> hook:</p>
<ul>
<li><p>Listens for <code>gameState</code> updates from the server.</p>
</li>
<li><p>Updates the local game state when changes are detected.</p>
</li>
<li><p>Cleans up the WebSocket listener when the component is unmounted.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Handling Player Moves</strong>:</p>
<ul>
<li><p>The <code>handleClick</code> function:</p>
<ul>
<li><p>Checks if a cell is already occupied or if the game has a winner before allowing a move.</p>
</li>
<li><p>Sends a <code>makeMove</code> event to the server with the clicked cell index.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Game Board Rendering</strong>:</p>
<ul>
<li><p>The <code>renderCell</code> function creates a button for each cell on the board.</p>
</li>
<li><p>The board is displayed using a 3x3 grid.</p>
</li>
</ul>
</li>
<li><p><strong>Restart Game</strong>:</p>
<ul>
<li>The "Restart Game" button emits a <code>restartGame</code> event to reset the game board for all players.</li>
</ul>
</li>
<li><p><strong>User Interface</strong>:</p>
<ul>
<li>A simple and interactive layout that allows players to take turns and see updates in real-time.</li>
</ul>
</li>
</ul>
<h2 id="heading-step-5-running-the-application"><strong>Step 5: Running the Application</strong></h2>
<h3 id="heading-starting-the-backend"><strong>Starting the Backend</strong></h3>
<p>To start the backend server, open a new terminal window and run the following commands:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> tic-tac-toe
npm start
</code></pre>
<h3 id="heading-starting-the-frontend"><strong>Starting the Frontend</strong></h3>
<p>To start the React frontend server, open a new terminal window and run the commands below (do not use the same one which the backend server is running on, as you need both running simultaneously to run the game).</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> tic-tac-toe-client
npm run dev
</code></pre>
<h3 id="heading-accessing-the-game"><strong>Accessing the Game</strong></h3>
<p>Open your browser and navigate to:</p>
<pre><code class="lang-bash">http://localhost:5173
</code></pre>
<h2 id="heading-step-6-viewing-redis-messages-in-real-time"><strong>Step 6: Viewing Redis Messages in Real-Time</strong></h2>
<p>While the game is running, you can view Redis messages to see real-time game state updates.</p>
<p>Open a terminal and run:</p>
<pre><code class="lang-bash">redis-cli
SUBSCRIBE game-moves
</code></pre>
<p>This will display game updates:</p>
<pre><code class="lang-bash">1) <span class="hljs-string">"message"</span>
2) <span class="hljs-string">"game-moves"</span>
3) <span class="hljs-string">"{\"board\":[\"X\",null,\"O\",null,\"X\",null,null,null,null],\"xIsNext\":false}"</span>
</code></pre>
<p>Every time a move is made or the game state changes, the server publishes the updated game state to the <code>game-moves</code> channel. Using <code>redis-cli</code>, you can monitor these updates in real-time, as the game is being played.</p>
<h2 id="heading-demo"><strong>Demo</strong></h2>
<p>In this demo, you'll see the Tic Tac Toe game running locally, demonstrating real-time updates as players take turns.</p>
<p>The gameplay showcases features such as turn switching, board updates, and game state announcements (winner or draw). This highlights how the game leverages WebSocket communication to provide a smooth, interactive experience.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/2aCllaBR6Xg" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Congratulations, you’ve successfully built a real-time multiplayer Tic-Tac-Toe game using Node.js, Socket.IO, and Redis. Here’s what you’ve learned:</p>
<ul>
<li><p>Real-time WebSocket communication using <strong>Socket.IO</strong>.</p>
</li>
<li><p>Game state management using <strong>Redis Pub/Sub</strong>.</p>
</li>
<li><p>Building a responsive front-end with <strong>React</strong>.</p>
</li>
</ul>
<h3 id="heading-next-steps"><strong>Next Steps</strong></h3>
<ul>
<li><p>Add player authentication.</p>
</li>
<li><p>Implement a chat feature.</p>
</li>
<li><p>Deploy your application to a cloud provider for scalability.</p>
</li>
</ul>
<p>Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Queues in Web Applications – Node.js and Redis Tutorial ]]>
                </title>
                <description>
                    <![CDATA[ When you're building large scale web applications, speed is a major priority. Users don't want to wait long for responses anymore, and they shouldn't have to. But some processes take time, and they cannot be made any faster or eliminated. Message que... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-queues-in-web-applications/</link>
                <guid isPermaLink="false">66c4c699744830ebca763790</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ queue ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Zubair Idris Aweda ]]>
                </dc:creator>
                <pubDate>Thu, 06 Jul 2023 16:23:18 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/businessmen02.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you're building large scale web applications, speed is a major priority. Users don't want to wait long for responses anymore, and they shouldn't have to. But some processes take time, and they cannot be made any faster or eliminated.</p>
<p>Message queues help solve this problem by providing an additional branch to the usual request-response journey. This additional branch helps make sure users can get immediate responses, and the time-consuming processes can be done on the side. Everybody goes home happy.</p>
<p>This article will focus on explaining what message queues are and how to get started with them by building a very simple application. You should be familiar with the basics of Node.js, and you should have Redis installed either locally or on a cloud instance. Learn how to install Redis <a target="_blank" href="https://redis.io/docs/getting-started/installation/">here</a>.</p>
<h2 id="heading-what-is-a-queue">What is a Queue?</h2>
<p>A queue is a data structure that allows you store entities in an order. Queues use a first-in-first-out (FIFO) principle. </p>
<p>The concept of queues in computer science is the same as the concept of queues in everyday life where people line up to get things. You join a queue from the back, wait till it is your turn, then leave the queue from the front after you have been attended to.</p>
<p>In computer science, when a process like an API request is running, and you need to remove a certain task (like sending an email) from the current flow, you push it to a queue and continue the process.</p>
<p>The diagram below illustrates the lifecycle of a queue:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/job-lifecycle.png" alt="Image" width="600" height="400" loading="lazy">
<em>Queue Lifecycle | https://optimalbits.github.io/bull/</em></p>
<h2 id="heading-what-is-a-job">What is a Job?</h2>
<p>A job is any piece of data that is used on a queue, usually a JSON-like object.</p>
<p>As demonstrated in the cover image of this article, you can think of a job as each person on a queue at an airport. Each person holds a briefcase containing specific data, and other instructions (passports and maybe medical papers where required) that will help when it is their turn to be attended to.</p>
<p>New people joining this queue will enter from the back (as the last person), and people will be attended to from the front. That is how jobs are also processed, each job contains data that will be used for its processing. New jobs are added from the back while jobs are taken out from the front.</p>
<h3 id="heading-what-is-a-job-producer">What is a Job Producer?</h3>
<p>A job producer is any piece of code that adds a job to a queue. In real life, this would be the security guard at the airport that gives direction to people, telling them which queue to join for different purposes. </p>
<p>A job producer can exist independently of a job consumer. This means that in a microservice setup, a particular service might just be concerned with adding jobs to a queue, but not how they're processed after.</p>
<h3 id="heading-what-is-a-worker-job-consumer">What is a Worker (Job Consumer)?</h3>
<p>A worker, or job consumer, is a process, or function, that can execute a job. Think of a worker as a bank cashier attending to people on a queue at the bank. When the first person comes in, they join the queue as the only one on the queue. The cashier then calls for them and the queue is emptied. </p>
<p>The cashier requests for specific details to be used to process the transaction from the person. While the cashier attends to that customer, four more customers could have lined up. They will remain on the queue till the cashier is done with the first customer before calling for the next one. This is the same process with queue workers — they pick the first job in the queue, and process it.</p>
<h3 id="heading-what-are-failed-jobs">What are Failed Jobs?</h3>
<p>Often times, some jobs might fail during processing.</p>
<p>Here are some reasons why a job could fail:</p>
<ul>
<li>Invalid or missing input data: When data required for a job to be processed is missing, the job will fail. For example, a job to send an email will fail without the recipient's email address.</li>
<li>Timeout: A job could be failed by the queue mechanism if it is taking longer than usual. This could be due to an issue on a dependency of the job or something else, but usually you don't want a single job running forever.</li>
<li>Network or infrastructure problems: These problems are almost out of your control, but they do happen. A database connection error for example would fail a job.</li>
<li>Dependency issues: Sometimes a job relies on some external resources to function well. Whenever these other resources are unavailable or unsuccessful, the job will fail.</li>
</ul>
<p>When jobs fail, you can configure your queue mechanism to retry them. You can either retry the job immediately, or after a calculated amount of time. You can set a maximum number of attempts, which is recommended. If not, you end up running a job that will always fail infinitely.</p>
<h2 id="heading-why-use-queues">Why Use Queues?</h2>
<p>Queues are useful for creating robust communication channels between microservices. Multiple services can use the same queue. Different services could be tasked with different problems. When a service completes its task, it can push a job to another service that has workers waiting for that job. That service will pick it up and do whatever is needed with the data.</p>
<p>Queues are also useful for offloading heavy tasks from a process. As you'll see in this article, a time consuming task like sending of an email can be put on a queue to avoid slowing down response time.</p>
<p>Queues help avoid single points of failure. A process that has the ability to fail and can be retried is best processed using a queue where it can be retried after a while.</p>
<h2 id="heading-how-to-build-a-simple-application-that-uses-queues">How to Build a Simple Application that Uses Queues</h2>
<p>In this article, we'll build a simple project using Node.js and <a target="_blank" href="https://redis.io/">Redis</a>. We'll use the <a target="_blank" href="https://optimalbits.github.io/bull/">Bull</a> library as it simplifies a lot of the complexities involved in building a queue system. The project will have a single endpoint to send emails.</p>
<h3 id="heading-create-a-new-nodejs-project">Create a New Node.js Project</h3>
<pre><code class="lang-shell">mkdir nodejs-queue-project
cd nodejs-queue-project
npm init -y
</code></pre>
<p>The commands above will create a new folder named <code>nodejs-queue-project</code> and a <code>package.json</code> file in it. The <code>package.json</code> file should look like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"nodejs-queue-project"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.js"</span>,
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"echo \"Error: no test specified\" &amp;&amp; exit 1"</span>
  },
  <span class="hljs-attr">"keywords"</span>: [],
  <span class="hljs-attr">"author"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"license"</span>: <span class="hljs-string">"ISC"</span>
}
</code></pre>
<h3 id="heading-install-the-required-dependencies">Install the Required Dependencies</h3>
<pre><code class="lang-shell">npm i express @types/express @types/node body-parser ts-node ts-lint typescript nodemon nodemailer @types/nodemailer
</code></pre>
<p>The commands above will install the different packages and dependencies required for the project. </p>
<p>After installation, you can update the <code>scripts</code> section of your <code>package.json</code> to have a <code>dev</code> command. Your whole <code>package.json</code> file should look like this now:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"nodejs-queue-project"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.js"</span>,
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"nodemon src/app.ts"</span>
  },
  <span class="hljs-attr">"keywords"</span>: [],
  <span class="hljs-attr">"author"</span>: <span class="hljs-string">""</span>,
  <span class="hljs-attr">"license"</span>: <span class="hljs-string">"ISC"</span>,
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"@types/express"</span>: <span class="hljs-string">"^4.17.17"</span>,
    <span class="hljs-attr">"@types/node"</span>: <span class="hljs-string">"^20.3.3"</span>,
    <span class="hljs-attr">"@types/nodemailer"</span>: <span class="hljs-string">"^6.4.8"</span>,
    <span class="hljs-attr">"body-parser"</span>: <span class="hljs-string">"^1.20.2"</span>,
    <span class="hljs-attr">"express"</span>: <span class="hljs-string">"^4.18.2"</span>,
    <span class="hljs-attr">"nodemailer"</span>: <span class="hljs-string">"^6.9.3"</span>,
    <span class="hljs-attr">"nodemon"</span>: <span class="hljs-string">"^2.0.22"</span>,
    <span class="hljs-attr">"ts-lint"</span>: <span class="hljs-string">"^4.5.1"</span>,
    <span class="hljs-attr">"ts-node"</span>: <span class="hljs-string">"^10.9.1"</span>,
    <span class="hljs-attr">"typescript"</span>: <span class="hljs-string">"^5.1.6"</span>
  }
}
</code></pre>
<p>The file above shows all your installed dependencies. The <code>npm run dev</code> command will run when you use the <code>dev</code> script.</p>
<h2 id="heading-how-to-build-the-endpoint">How to Build the Endpoint</h2>
<p>The first thing to do is to create a new folder named <code>src</code>. This folder will contain all your code files. The first file it will contain is the root file of the application — the <code>app.ts</code> file as defined in the <code>package.json</code> file.</p>
<p>We'll use the <code>app.ts</code> file to import required packages and create a simple server with a single endpoint to send a email as seen below:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
<span class="hljs-keyword">import</span> bodyParser <span class="hljs-keyword">from</span> <span class="hljs-string">"body-parser"</span>;
<span class="hljs-keyword">import</span> nodemailer <span class="hljs-keyword">from</span> <span class="hljs-string">"nodemailer"</span>;

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

app.use(bodyParser.json());

app.post(<span class="hljs-string">"/send-email"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { <span class="hljs-keyword">from</span>, to, subject, text } = req.body;

  <span class="hljs-comment">// Use a test account as this is a tutorial</span>
  <span class="hljs-keyword">const</span> testAccount = <span class="hljs-keyword">await</span> nodemailer.createTestAccount();

  <span class="hljs-keyword">const</span> transporter = nodemailer.createTransport({
    host: <span class="hljs-string">"smtp.ethereal.email"</span>,
    port: <span class="hljs-number">587</span>,
    secure: <span class="hljs-literal">false</span>,
    auth: {
      user: testAccount.user,
      pass: testAccount.pass,
    },
    tls: {
      rejectUnauthorized: <span class="hljs-literal">false</span>,
    },
  });

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Sending mail to %s"</span>, to);

  <span class="hljs-keyword">let</span> info = <span class="hljs-keyword">await</span> transporter.sendMail({
    <span class="hljs-keyword">from</span>,
    to,
    subject,
    text,
    html: <span class="hljs-string">`&lt;strong&gt;<span class="hljs-subst">${text}</span>&lt;/strong&gt;`</span>,
  });

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Message sent: %s"</span>, info.messageId);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Preview URL: %s"</span>, nodemailer.getTestMessageUrl(info));

  res.json({
    message: <span class="hljs-string">"Email Sent"</span>,
  });
});

app.listen(<span class="hljs-number">4300</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Server started at http://localhost:4300"</span>);
});
</code></pre>
<p>Now, you can start your server by running <code>npm run dev</code> in your terminal. You should see a message saying <code>Server started at [http://localhost:4300](http://localhost:4300)</code> in your terminal.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-01-at-17.41.33.png" alt="Image" width="600" height="400" loading="lazy">
<em>npm run dev message</em></p>
<p>You can now test the endpoint using a tool like Postman:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-01-at-17.30.33.png" alt="Image" width="600" height="400" loading="lazy">
<em>Endpoint testing on Postman</em></p>
<p>The request took almost 4 seconds as shown in the screenshot. This is very slow for an endpoint. If you take a look at your terminal, you should also see a URL where you can preview the email that was sent.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-01-at-17.43.01.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Opening the link lets you see how the email looks.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-01-at-17.43.47.png" alt="Image" width="600" height="400" loading="lazy">
<em>Email content</em></p>
<h2 id="heading-how-to-create-the-queue">How to Create the Queue</h2>
<p>To make the process even faster, the email can be queued to be sent later and a response sent to the user immediately.</p>
<p>To do this, install the <code>bull</code> library and its <code>@types</code> library as we'll use it to create a queue. That is:</p>
<pre><code class="lang-shell">npm i bull @types/bull
</code></pre>
<p>Creating a new queue using <code>bull</code> is as easy as instantiating a new <code>Bull</code> object with a name for the queue:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// This goes at the top of your file</span>
<span class="hljs-keyword">import</span> Bull <span class="hljs-keyword">from</span> <span class="hljs-string">'bull'</span>;

<span class="hljs-keyword">const</span> emailQueue = <span class="hljs-keyword">new</span> Bull(<span class="hljs-string">"email"</span>);
</code></pre>
<p>When the queue is created with just the queue name, it tries to use the default Redis connection URL: <code>localhost:6379</code>. If you prefer using a different URL, simply pass in a second object to the <code>Bull</code> class as an options object:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> emailQueue = <span class="hljs-keyword">new</span> Bull(<span class="hljs-string">"email"</span>, {
  redis: <span class="hljs-string">"localhost:6379"</span>,
});
</code></pre>
<p>At this point, you can create a simple function to serve as a job producer and add a job to the queue every time a request comes in.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">type</span> EmailType = {
  <span class="hljs-keyword">from</span>: <span class="hljs-built_in">string</span>;
  to: <span class="hljs-built_in">string</span>;
  subject: <span class="hljs-built_in">string</span>;
  text: <span class="hljs-built_in">string</span>;
};

<span class="hljs-keyword">const</span> sendNewEmail = <span class="hljs-keyword">async</span> (email: EmailType) =&gt; {
  emailQueue.add({ ...email });
};
</code></pre>
<p>This newly created function, <code>sendNewEmail</code>, accepts an object containing details of the new email to be sent of type <code>EmailType</code>. There's sender email address (<code>from</code>), recipient email address (<code>to</code>), <code>subject</code> of the email, and the content of the email (<code>text</code>). Then it pushes a new job to the queue. </p>
<p>You can use this function instead of sending the email during the request now. Modify the endpoint to do this:</p>
<pre><code class="lang-ts">app.post(<span class="hljs-string">"/send-email"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { <span class="hljs-keyword">from</span>, to, subject, text } = req.body;

  <span class="hljs-keyword">await</span> sendNewEmail({ <span class="hljs-keyword">from</span>, to, subject, text });

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Added to queue"</span>);

  res.json({
    message: <span class="hljs-string">"Email Sent"</span>,
  });
});
</code></pre>
<p>At this point, the code is simpler and the process is faster. The request only takes about 40m — about 100x faster than before.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-01-at-18.25.40.png" alt="Image" width="600" height="400" loading="lazy">
<em>Endpoint testing with Postman</em></p>
<p>At this point, the email is added to a queue. It will remain on the queue until processed. The job can be processed by the same application or another service (if in a <a target="_blank" href="https://www.freecodecamp.org/news/microservices-architecture-for-humans/">microservice setup</a>).</p>
<h2 id="heading-how-to-process-the-jobs">How to Process the Jobs</h2>
<p>The cycle is incomplete and useless if the mails never leave the queue. We'll create a job consumer to process the jobs and clear the queue.</p>
<p>We can do this by creating the logic for a function that accepts a <code>Job</code> object and sends the email:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> processEmailQueue = <span class="hljs-keyword">async</span> (job: Job) =&gt; {
  <span class="hljs-comment">// Use a test account as this is a tutorial</span>
  <span class="hljs-keyword">const</span> testAccount = <span class="hljs-keyword">await</span> nodemailer.createTestAccount();

  <span class="hljs-keyword">const</span> transporter = nodemailer.createTransport({
    host: <span class="hljs-string">"smtp.ethereal.email"</span>,
    port: <span class="hljs-number">587</span>,
    secure: <span class="hljs-literal">false</span>,
    auth: {
      user: testAccount.user,
      pass: testAccount.pass,
    },
    tls: {
      rejectUnauthorized: <span class="hljs-literal">false</span>,
    },
  });

  <span class="hljs-keyword">const</span> { <span class="hljs-keyword">from</span>, to, subject, text } = job.data;

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Sending mail to %s"</span>, to);

  <span class="hljs-keyword">let</span> info = <span class="hljs-keyword">await</span> transporter.sendMail({
    <span class="hljs-keyword">from</span>,
    to,
    subject,
    text,
    html: <span class="hljs-string">`&lt;strong&gt;<span class="hljs-subst">${text}</span>&lt;/strong&gt;`</span>,
  });

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Message sent: %s"</span>, info.messageId);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Preview URL: %s"</span>, nodemailer.getTestMessageUrl(info));

  <span class="hljs-keyword">return</span> nodemailer.getTestMessageUrl(info);
};
</code></pre>
<p>The function above accepts a <code>Job</code> object. The object has useful properties that shows the status of and data in a job. Here, we use the <code>data</code> property. </p>
<p>At this point, all we have is a function. It doesn't pick up jobs automatically because it doesn't know which queue to work with.</p>
<p>Before connecting it to the queue, you can go on to add a few jobs to the queue by sending some requests. You can check the email jobs currently queued by running this command in your <code>redis-cli</code>:</p>
<pre><code class="lang-shell">LRANGE bull:email:wait 0 -1
</code></pre>
<p>This checks the email waitlist, and returns the <code>ids</code> of the waiting jobs.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-01-at-18.47.35.png" alt="Image" width="600" height="400" loading="lazy">
<em>Redis CLI</em></p>
<p>I have created a few jobs just to show how workers actually work.</p>
<p>Now, connect the worker to the queue by adding this line of code:</p>
<pre><code class="lang-ts">emailQueue.process(processEmailQueue);
</code></pre>
<p>This is what your <code>app.ts</code> file should now look after that:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
<span class="hljs-keyword">import</span> bodyParser <span class="hljs-keyword">from</span> <span class="hljs-string">"body-parser"</span>;
<span class="hljs-keyword">import</span> nodemailer <span class="hljs-keyword">from</span> <span class="hljs-string">"nodemailer"</span>;
<span class="hljs-keyword">import</span> Bull, { Job } <span class="hljs-keyword">from</span> <span class="hljs-string">"bull"</span>;

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

app.use(bodyParser.json());

<span class="hljs-keyword">const</span> emailQueue = <span class="hljs-keyword">new</span> Bull(<span class="hljs-string">"email"</span>, {
  redis: <span class="hljs-string">"localhost:6379"</span>,
});

<span class="hljs-keyword">type</span> EmailType = {
  <span class="hljs-keyword">from</span>: <span class="hljs-built_in">string</span>;
  to: <span class="hljs-built_in">string</span>;
  subject: <span class="hljs-built_in">string</span>;
  text: <span class="hljs-built_in">string</span>;
};

<span class="hljs-keyword">const</span> sendNewEmail = <span class="hljs-keyword">async</span> (email: EmailType) =&gt; {
  emailQueue.add({ ...email });
};

<span class="hljs-keyword">const</span> processEmailQueue = <span class="hljs-keyword">async</span> (job: Job) =&gt; {
  <span class="hljs-comment">// Use a test account as this is a tutorial</span>
  <span class="hljs-keyword">const</span> testAccount = <span class="hljs-keyword">await</span> nodemailer.createTestAccount();

  <span class="hljs-keyword">const</span> transporter = nodemailer.createTransport({
    host: <span class="hljs-string">"smtp.ethereal.email"</span>,
    port: <span class="hljs-number">587</span>,
    secure: <span class="hljs-literal">false</span>,
    auth: {
      user: testAccount.user,
      pass: testAccount.pass,
    },
    tls: {
      rejectUnauthorized: <span class="hljs-literal">false</span>,
    },
  });

  <span class="hljs-keyword">const</span> { <span class="hljs-keyword">from</span>, to, subject, text } = job.data;

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Sending mail to %s"</span>, to);

  <span class="hljs-keyword">let</span> info = <span class="hljs-keyword">await</span> transporter.sendMail({
    <span class="hljs-keyword">from</span>,
    to,
    subject,
    text,
    html: <span class="hljs-string">`&lt;strong&gt;<span class="hljs-subst">${text}</span>&lt;/strong&gt;`</span>,
  });

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Message sent: %s"</span>, info.messageId);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Preview URL: %s"</span>, nodemailer.getTestMessageUrl(info));
};

emailQueue.process(processEmailQueue);

app.post(<span class="hljs-string">"/send-email"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { <span class="hljs-keyword">from</span>, to, subject, text } = req.body;

  <span class="hljs-keyword">await</span> sendNewEmail({ <span class="hljs-keyword">from</span>, to, subject, text });

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Added to queue"</span>);

  res.json({
    message: <span class="hljs-string">"Email Sent"</span>,
  });
});

app.listen(<span class="hljs-number">4300</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Server started at http://localhost:4300"</span>);
});
</code></pre>
<p>Once you save, you'll notice that the server restarts and immediately starts sending out mails. This is because the worker sees the queue and begins processing immediately.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-01-at-18.51.14.png" alt="Image" width="600" height="400" loading="lazy">
<em>Server sending out queued emails</em></p>
<p>Now, both the producer and the worker are active. Every new API request will be pushed to the queue, and the worker will immediately process it unless there's some pending jobs already.</p>
<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>I hope this article helped you understand what a message queue is, how to add jobs and create processes to run them, and how you can use them to build better web applications. You can find the code files used in this article on <a target="_blank" href="https://github.com/Zubs/php-redis">GitHub</a>.</p>
<p>If you have any questions or relevant advice, please get in touch with me to share them.</p>
<p>To read more of my articles or follow my work, you can connect with me on <a target="_blank" href="https://www.linkedin.com/in/idris-aweda-zubair-5433121a3/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/AwedaIdris">Twitter</a>, and <a target="_blank" href="https://github.com/Zubs">Github</a>. It’s quick, it’s easy, and it’s free!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Redis in Your PHP Apps ]]>
                </title>
                <description>
                    <![CDATA[ Redis is a data store that stores data primarily in memory. It's faster than traditional databases, and has grown quite popular. In this tutorial, you'll learn the basics of how Redis works, when to use it, how to install it on your device, and how t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-redis-with-php/</link>
                <guid isPermaLink="false">66c4c69de7521bfd6862b3ad</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PHP ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Zubair Idris Aweda ]]>
                </dc:creator>
                <pubDate>Wed, 03 May 2023 19:51:55 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/pexels-tom-fisk-3063470.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Redis is a data store that stores data primarily in memory. It's faster than traditional databases, and has grown quite popular.</p>
<p>In this tutorial, you'll learn the basics of how Redis works, when to use it, how to install it on your device, and how to use it as a caching system in a PHP web application.</p>
<h2 id="heading-what-is-redis">What Is Redis?</h2>
<p>Redis is a data store – like a database, but one that stores data primarily in-memory. This makes it much faster than traditional databases where data is stored in disks. Because of this speed, Redis is often used as a caching tool.</p>
<p>Redis can store data in any data type, as it uses a key-value pair system to store data. This is also unlike traditional databases that use documents or rows. </p>
<p>You can think of a Redis database as a big JSON object, where everything in the database is a key-value pair. This means it might not be the best place to store structured data.</p>
<p>You can also use Redis as a database, as it has the ability to write data to disk for persistence. You can configure Redis to persist data either periodically or after every command you issue. When Redis isn't configured to persist data, it is very volatile, and a system crash would result in a loss of data.</p>
<p>Redis is popular in production level applications and it's used by large companies like Twitter, Github, SnapChat and StackOverFlow.</p>
<h2 id="heading-when-to-use-redis">When to Use Redis</h2>
<ul>
<li>For One Time Passwords (OTPs): These are usually generated to be used once, and have short lifespans. With Redis' ability to set an expiry date for data, you can safely store the OTP without worrying about deleting them after a certain period.</li>
<li>For frequently accessed resources: For data that doesn't change too frequently but is accessed a lot, you can use Redis to save time that would have been spent querying the database or making a call to some external service.</li>
<li>For heavy duty queries: For database queries that take time, and also won't change too often, use Redis to reduce this time by storing the results for as long as you like.</li>
</ul>
<h2 id="heading-how-to-install-redis">How to Install Redis</h2>
<p>You can install Redis on any operating system. Here are the instructions for macOS, Windows Subsystem for Linux, and Linux.</p>
<h3 id="heading-macos">macOS</h3>
<p>To install Redis on macOS, run:</p>
<pre><code class="lang-shell">brew install redis
</code></pre>
<p>Then, run this command to start Redis:</p>
<pre><code class="lang-shell">redis-server
</code></pre>
<h3 id="heading-windows-subsystem-for-linux-and-linux">Windows Subsystem for Linux and Linux</h3>
<p>Redis doesn't exactly support the Windows operating system yet, so you can install WSL (Windows Subsystem for Linux) on windows to have a Linux environment.</p>
<p>To install Redis on Linux, run:</p>
<pre><code class="lang-shell">curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list

sudo apt-get update
sudo apt-get install redis
</code></pre>
<p>Then, run this command to start Redis:</p>
<pre><code class="lang-shell">sudo service redis-server start
</code></pre>
<p>Now that Redis is installed, you can test it by running <code>redis-cli ping</code>. This will output <em>"PONG"</em>. Like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-13.36.14.png" alt="Image" width="600" height="400" loading="lazy">
<em>Testing Redis Installation</em></p>
<h2 id="heading-redis-basics">Redis Basics</h2>
<p>To use Redis as a REPL or as a standalone application, run <code>redis-cli</code>. It will open the REPL environment.</p>
<h3 id="heading-how-to-set-data">How to Set Data</h3>
<p>Use the <code>SET</code> keyword to set a key value pair in Redis. To set a <code>username</code> key to the value <code>Zubs</code> , run this: </p>
<pre><code class="lang-redis">SET username Zubs
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-13.41.36.png" alt="Image" width="600" height="400" loading="lazy">
<em>Setting a key-value pair</em></p>
<h3 id="heading-how-to-get-data">How to Get Data</h3>
<p>To get the recently saved <code>username</code> key, use the <code>GET</code> keyword like this: </p>
<pre><code class="lang-redis">GET username
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-13.43.52.png" alt="Image" width="600" height="400" loading="lazy">
<em>Getting a value by key</em></p>
<h3 id="heading-how-to-delete-data">How to Delete Data</h3>
<p>You can also delete a previously stored key using the <code>DEL</code> keyword like this:</p>
<pre><code class="lang-redis">DEL username
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-17.57.26.png" alt="Image" width="600" height="400" loading="lazy">
<em>Deleting a value by key</em></p>
<h3 id="heading-how-to-check-if-a-value-exists">How to Check if a Value Exists</h3>
<p>You can check for the existence of a key by using the <code>EXISTS</code> keyword. It returns <code>0</code> when the key doesn't exist, and <code>1</code> if it does. You can test by checking if the recently deleted <code>username</code> key exists. Like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-18.04.17.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-set-a-time-to-live-for-keys">How to Set a Time to Live for Keys</h3>
<p> Redis lets you specify how long some key should exist for when creating it. This is one really great feature of Redis. To do this, use the <code>SETEX</code> keyword like this:</p>
<pre><code class="lang-redis">SETEX key seconds value
</code></pre>
<p>You can check the time to live for a specific key using the <code>TTL</code> keyword. This returns <code>-1</code> if the key has no set expiration, meaning it will be stored indefinitely. It returns <code>-2</code> if the key doesn't exist. And it returns the time in seconds if the key exists.</p>
<p>You can set an expiration time in seconds for a key previously created without an expiration time using the <code>EXPIRE</code> keyword. For example, create a key to store a variable <code>age</code> with a value of <code>26</code>. </p>
<pre><code class="lang-redis">SET age 26
</code></pre>
<p>Then, set an expiration time of 10 seconds for it.</p>
<pre><code class="lang-redis">EXPIRE age 20
</code></pre>
<p>Check the time left to live repeatedly a couple of times to see how it actually reduces and eventually doesn't exist again.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-18.22.58.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-build-a-simple-application-with-redis">How to Build a Simple Application with Redis</h2>
<p>To help you understand how Redis works, we'll now build a basic web application that uses Redis to cache data to load responses faster. You'll be building a simple application that fetches images data from <a target="_blank" href="https://www.freecodecamp.org/news/p/043f81af-1384-435c-b08a-4f80327a6002/'https://jsonplaceholder.typicode.com/photos'">JSONPlaceholder</a> and returns them.</p>
<h3 id="heading-create-a-new-php-project-using-composer">Create a New PHP Project Using Composer</h3>
<p>Create a new folder for the project, change directory into the newly created folder, and run the following compound to create a new composer project:</p>
<pre><code class="lang-shell">composer init -q
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-19.06.40.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This will create a new <code>composer.json</code> file that should look like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"require"</span>: {}
}
</code></pre>
<p>Next, create a public folder to house your public facing code files. Then create a new <code>index.php</code> file in the folder.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-19.26.54.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Put in some boilerplate content in the PHP file for now and start a server.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">echo</span> <span class="hljs-string">"Hello World!"</span>;
</code></pre>
<pre><code class="lang-shell">php -S localhost:8080
</code></pre>
<h3 id="heading-install-a-simple-router-and-handle-requests">Install a Simple Router and Handle Requests</h3>
<p>To complete the project, install a simple PHP router, <code>Altorouter</code>, and a web client, <code>Guzzlehttp</code>.</p>
<pre><code class="lang-shell">composer require altorouter/altorouter guzzlehttp/guzzle
</code></pre>
<p>Update the <code>index.php</code> to contain this code:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-comment">// Import composer autoload file</span>
<span class="hljs-keyword">require_once</span> <span class="hljs-keyword">__DIR__</span> . <span class="hljs-string">'/../vendor/autoload.php'</span>;

<span class="hljs-comment">// Import GuzzleHttp Client</span>
<span class="hljs-keyword">use</span> <span class="hljs-title">GuzzleHttp</span>\<span class="hljs-title">Client</span>;

<span class="hljs-comment">// Instantiate router and web client</span>
$router = <span class="hljs-keyword">new</span> AltoRouter();
$client = <span class="hljs-keyword">new</span> Client();

<span class="hljs-comment">// Register Sample route</span>
$router-&gt;map(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'/'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Set response Content-Type</span>
    header(<span class="hljs-string">'Content-Type: application/json; charset=utf-8'</span>);

    <span class="hljs-comment">// Return basic response</span>
    <span class="hljs-keyword">echo</span> json_encode([<span class="hljs-string">'data'</span> =&gt; <span class="hljs-string">'Hello World'</span>]);
});

<span class="hljs-comment">/**
 * Route to get all photos
 */</span>
$router-&gt;map(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'/photos'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) <span class="hljs-title">use</span> (<span class="hljs-params">$client</span>) </span>{
    <span class="hljs-comment">// Make request to JSONPlaceholder</span>
    $response = $client-&gt;request(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'https://jsonplaceholder.typicode.com/photos'</span>);

    header(<span class="hljs-string">'Content-Type: application/json; charset=utf-8'</span>);
    <span class="hljs-keyword">echo</span> json_encode([
        <span class="hljs-string">'data'</span> =&gt; json_decode($response-&gt;getBody()-&gt;getContents())
    ]);
});

<span class="hljs-comment">/**
 * Route to get single photo by id
 */</span>
$router-&gt;map(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'/photos/[i:id]'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>) <span class="hljs-title">use</span> (<span class="hljs-params">$client</span>) </span>{
    $response = $client-&gt;request(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'https://jsonplaceholder.typicode.com/photos/'</span> . $id);

    header(<span class="hljs-string">'Content-Type: application/json; charset=utf-8'</span>);
    <span class="hljs-keyword">echo</span> json_encode([
        <span class="hljs-string">'data'</span> =&gt; json_decode($response-&gt;getBody()-&gt;getContents())
    ]);
});

$match = $router-&gt;match();

<span class="hljs-keyword">if</span>( is_array($match) &amp;&amp; is_callable( $match[<span class="hljs-string">'target'</span>] ) ) {
    call_user_func_array( $match[<span class="hljs-string">'target'</span>], $match[<span class="hljs-string">'params'</span>] );
} <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// no route was matched</span>
    header( $_SERVER[<span class="hljs-string">"SERVER_PROTOCOL"</span>] . <span class="hljs-string">' 404 Not Found'</span>);
}
</code></pre>
<p>The code is pretty self explanatory. But, here's a breakdown for clarity. From lines 1-11, the required classes GuzzleHttp and AltoRouter are imported and instantiated.</p>
<p>From lines 14-20, the first route is registered, with a simple closure that returns "Hello World!". Lines 25-45 register two more routes, one to fetch all photos, <code>/photos</code> and another to fetch a single photo, <code>/photos/id</code>. </p>
<p>The final lines are required based on documentation of the router package to actually execute the closures set in the routes declaration.</p>
<p>You can test these routes using Postman.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-20.19.36.png" alt="Image" width="600" height="400" loading="lazy">
<em>Hello World route</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-20.21.01.png" alt="Image" width="600" height="400" loading="lazy">
<em>Get All Photos route</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-20.26.19.png" alt="Image" width="600" height="400" loading="lazy">
<em>Get a Single Photo route</em></p>
<p>The <code>/photos</code> route takes an average of 1400ms per request. The <code>/photos/id</code> takes an average of 900ms per request. </p>
<h3 id="heading-install-and-instantiate-redis">Install and Instantiate Redis</h3>
<p>These times can be reduced by caching the results of the original request to JSONPlaceholder, then returning a response from the cache instead of making a request every time.</p>
<p>To use Redis with PHP, install the <a target="_blank" href="https://github.com/phpredis/phpredis">PhpRedis</a> extension. This extension provides an API for communicating with Redis. You can easily install it using the command:</p>
<pre><code class="lang-shell">pecl install redis
</code></pre>
<p>After installation, you can then use this class in your PHP project. Import the class and instantiate it at the top of your <code>index.php</code> file:</p>
<pre><code class="lang-php">$redis = <span class="hljs-keyword">new</span> Redis();
$redis-&gt;connect(<span class="hljs-string">'127.0.0.1'</span>);
</code></pre>
<p>Having done this, you can now use Redis in your project.</p>
<h3 id="heading-how-to-cache-data-with-redis">How to Cache Data with Redis</h3>
<p>Store the raw JSON response returned from JSONPlaceholder to Redis with an expiry time of 1 hour (3600 seconds).</p>
<pre><code class="lang-php">$response = $client-&gt;request(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'https://jsonplaceholder.typicode.com/photos'</span>);

$redis-&gt;setex(
    <span class="hljs-string">'photos'</span>,
    <span class="hljs-number">3600</span>,
    $response-&gt;getBody()-&gt;getContents()
);
</code></pre>
<p>Here, you create a new key called <code>photos</code>, give it an expiration time of 1 hour, then assign it the raw response gotten from JSONPlaceholder.</p>
<p>But at this point the API still takes a long time to respond. This is because you're only storing this response, you're not using Redis to return the response. </p>
<p>To fix this, when a new request comes in, check if you have some data previosuly stored in-memory. If yes, you return the data in-memory, else, you make a call to JSONPlaceholder.</p>
<p>Update the <code>/photos</code> block to this:</p>
<pre><code class="lang-php"><span class="hljs-comment">/**
 * Route to get all photos
 */</span>
$router-&gt;map(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'/photos'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) <span class="hljs-title">use</span> (<span class="hljs-params">$client, $redis</span>) </span>{
    <span class="hljs-comment">// Check if Redis has the key</span>
    <span class="hljs-keyword">if</span> (!$redis-&gt;exists(<span class="hljs-string">'photos'</span>)) {
        $response = $client-&gt;request(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'https://jsonplaceholder.typicode.com/photos'</span>);

        <span class="hljs-comment">// Store the data for next use</span>
        $redis-&gt;setex(
            <span class="hljs-string">'photos'</span>,
            REDIS_STANDARD_EXPIRY,
            $response-&gt;getBody()-&gt;getContents()
        );
    }

    header(<span class="hljs-string">'Content-Type: application/json; charset=utf-8'</span>);
    <span class="hljs-keyword">echo</span> json_encode([
        <span class="hljs-string">'data'</span> =&gt; json_decode($redis-&gt;get(<span class="hljs-string">'photos'</span>))
    ]);
});
</code></pre>
<p>Testing in Postman to see improvements, you see the average response time after the first call (the original call before it is cached) has dropped to an average of 20ms for the <code>/photos</code> route. This is an improvement of over 50x. Redis saves a lot of processing time and power.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-21.11.34.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Update the <code>/photos/id</code> route to use Redis too:</p>
<pre><code class="lang-php">$router-&gt;map(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'/photos/[i:id]'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>) <span class="hljs-title">use</span> (<span class="hljs-params">$client, $redis</span>) </span>{
    <span class="hljs-keyword">if</span> (!$redis-&gt;exists(<span class="hljs-string">'photos:'</span> . $id)) {
        $response = $client-&gt;request(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'https://jsonplaceholder.typicode.com/photos/'</span> . $id);

        $redis-&gt;setex(
            <span class="hljs-string">'photos:'</span> . $id,
            REDIS_STANDARD_EXPIRY,
            $response-&gt;getBody()-&gt;getContents()
        );
    }

    header(<span class="hljs-string">'Content-Type: application/json; charset=utf-8'</span>);
    <span class="hljs-keyword">echo</span> json_encode([
        <span class="hljs-string">'data'</span> =&gt; json_decode($redis-&gt;get(<span class="hljs-string">'photos:'</span> . $id))
    ]);
});
</code></pre>
<p>The <code>/photos/id</code> route is now also much faster as it takes less than 5ms to get a response, an improvement of over 45x.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-30-at-21.12.31.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-summary">Summary</h2>
<p>I hope you now understand the what Redis is, its basics, and how you can use it to enhance the speed of your PHP web applications. You can find the code files used in this article on <a target="_blank" href="https://github.com/Zubs/php-redis">GitHub</a>.</p>
<p>If you have any questions or relevant advice, please get in touch with me to share them.</p>
<p>To read more of my articles or follow my work, you can connect with me on <a target="_blank" href="https://www.linkedin.com/in/idris-aweda-zubair-5433121a3/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/AwedaIdris">Twitter</a>, and <a target="_blank" href="https://github.com/Zubs">Github</a>. It’s quick, it’s easy, and it’s free!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Pub/Sub in Redis – How to Use the Publish/Subscribe Messaging Pattern ]]>
                </title>
                <description>
                    <![CDATA[ When you're working on an application that needs to be easily maintainable, scalable, and performant, the Publish/Subscribe messaging pattern is a good choice. The idea behind it is simple, but powerful. We have senders called publishers. Their sole ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-pub-sub-in-redis/</link>
                <guid isPermaLink="false">66d460569f2bec37e2da0654</guid>
                
                    <category>
                        <![CDATA[ messaging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mihail Gaberov ]]>
                </dc:creator>
                <pubDate>Fri, 28 Apr 2023 14:33:25 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/04/image-332-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you're working on an application that needs to be easily maintainable, scalable, and performant, the Publish/Subscribe messaging pattern is a good choice.</p>
<p>The idea behind it is simple, but powerful. We have senders called <em>publishers.</em> Their sole role is to send or <em>publish</em> messages. They don’t care about who is going to receive them or if someone will receive them at all. They just shoot and forget the messages. And they do that via <em>channels</em>.</p>
<p>Think of them as, for example, TV channels. We have Sports channels, Weather Forecasting channels, Cooking channels, and so on. Every publisher sends its messages to a certain channel, and whoever is <em>subscribed</em> for this channel will be able to receive these messages.</p>
<p>Here is where the <em>subscribers</em> come in play. They can subscribe to one or more channels and start receiving the messages broadcasted in there.</p>
<p>As we already mentioned, the messages are to be sent and forgotten. This means that if a subscriber subscribes for a certain channel, all the messages that were sent previously in that channel are not going to be available to this subscriber.</p>
<p>Due to the nature of this kind of architecture, we can easily achieve low coupling between the different components and provide a solid foundation for building robust and easy-to-maintain applications.</p>
<p>For example, imagine a situation where we need to replace or improve the publishing part of our system – say add more publishers, more channels or so on. Since the two parts are isolated, meaning publishers don’t care about subscribers and vice versa, we could easily do that without worrying whether we are breaking some other part of the system. We just add the new publishers. Then later, when a subscriber comes to the relevant channels, it just starts using them.</p>
<h2 id="heading-what-is-redis">What is Redis?</h2>
<p>The initial idea behind Redis was to serve as an in-memory cache solution, as an alternative to its ancestor <a target="_blank" href="https://www.memcached.org/">Memcached</a>.</p>
<p>But nowadays it's a many-in-one solution, providing an in-memory data structure store, key-value database, message brokering, and so on. This makes it perfect candidate when building an application that needs a really fast caching solution as well as some of the other features mentioned before. Especially if the performance of the app is crucial for its regular usage.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-333.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Redis performance comparison (source: google)</em></p>
<p>One of the biggest advantages when using Redis is the huge community and technical resources you can find online. A lot of these resources are free, and there are online platforms that have free tier offerings.</p>
<p>Redis includes in its arsenal a cloud solution as well. If you want to try it yourself, you may go <a target="_blank" href="https://redis.com/try-free/">here</a> and register a free account or use their initial coupon offering.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-334.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Redis Enterprise Cloud Sign Up / Sign In page</em></p>
<h2 id="heading-pubsub-in-redis">Pub/Sub in Redis</h2>
<h3 id="heading-what-is-pubsub">What is pub/sub?</h3>
<p><a target="_blank" href="https://redis.io/docs/manual/pubsub/">Publish/Subscribe channels in Redis</a> is one of the features I haven’t mentioned above but it’s included in the last versions of Redis. This is their implementation of the <a target="_blank" href="https://redis.io/docs/manual/pubsub/">pub/sub messaging pattern</a>, where we have publishers and subscribers that exchange messages via channels.</p>
<p>We'll go briefly through it below and then see it in practice in a <a target="_blank" href="https://github.com/mihailgaberov/redis-pub-sub-visualized">small demo app</a> I have prepared for you.</p>
<h3 id="heading-how-does-redis-pubsub-work">How does Redis pub/sub work?</h3>
<p>We have publishers (the producers of messages), channels (that the messages are going through), and subscribers (the receivers of the messages). Who receives what depends solely on who is subscribed to which channel.</p>
<p><strong>Let's see how this works in an example:</strong></p>
<p>If we have created three publishers which will be publishing messages to three different channels. Let’s call them channels 1, 2 and 3. We also have three subscribers, let’s call them subscribers A, B and C.</p>
<p>Now, let’s imagine subscriber A is listening for messages on all three channels, that is, it's subscribed to them. And subscribers B and C are subscribed to channels 2 and 3. This means that when either of the three publishers sends a message, subscriber A will receive it. And subscribers B and C will be receiving messages sent only by publishers 2 and 3, because they are listening only for messages on these channels (2 and 3).</p>
<p>Notice that we have two entities using a channel – one is sending, the other is receiving – but they are totally independent. And the messages being sent are not persisted. Once they are sent by the publisher they are forgotten. The only entities that are subscribed at the moment of sending will get them.</p>
<h3 id="heading-how-to-use-pubsub-in-redis">How to use pub/sub in Redis</h3>
<p>There are a plethora of client libraries that you can use with Redis. There is a <a target="_blank" href="https://redis.io/docs/clients/">dedicated page</a> where everybody can go and pick one, depending on the specific project needs or just on your preferred programming language.</p>
<p>People at Redis also marked some of these repositories as <em>recommended</em> which makes the choice easier, if you are new to all this.</p>
<p>For our demo below, I used <a target="_blank" href="https://github.com/luin/ioredis">ioredis</a>, a full-featured Redis client for Node.js. I chose this because the demo app UI is built with React and Node.js and my server code goes pretty well with it.</p>
<h2 id="heading-redis-pubsub-demo">Redis Pub/Sub Demo</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-336.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Redis Pub/Sub Visualizer app</em></p>
<p>Show time!</p>
<p>The idea behind the <a target="_blank" href="https://github.com/mihailgaberov/redis-pub-sub-visualized">demo application</a> is to show visually how the pattern works.</p>
<p>What you will see when you open it for first the time is three buttons for publishing simple messages (news) in the three imaginary TV channels: Weather, Sports and Music.</p>
<p>The cards below the publish buttons are the subscribers. Once you move your mouse cursor over any of them, it will flip to its back side and you will see three buttons. You may use each of these buttons to subscribe to the relevant channel.</p>
<p>Once a subscriber is signed over a channel and you click on the icon or the publish button for this channel, you will see a sample news appearing on the front side of the card.</p>
<p>Play with different publishers/subscribers combinations and see the result.</p>
<p>I hope this will give you a better understanding of what I explained in the example above.</p>
<h3 id="heading-how-to-run-the-demo-app-locally"><strong>How to run the demo app locally</strong></h3>
<p>In order to install and run the demo application locally, follow the steps below (all commands are considered to be run from the root project directory):</p>
<p><strong>Run frontend:</strong></p>
<p><code>cd client yarn &amp;&amp; yarn dev</code></p>
<p><strong>Run backend:</strong></p>
<p><code>cd server &amp;&amp; yarn yarn start</code></p>
<p>And finally, use your local installation of Docker (if don’t have one, you may get it from <a target="_blank" href="https://docs.docker.com/get-docker/">here</a>) to run this:</p>
<pre><code class="lang-python">docker run -p <span class="hljs-number">6379</span>:<span class="hljs-number">6379</span> redislabs/redismod:preview
</code></pre>
<p>That’s probably the easiest way to have a running copy of Redis locally. The other option would be to use <a target="_blank" href="https://redis.com/try-free/">Redis Cloud</a> directly and deploy the application online. This is an option I am still investigating and if I manage to do it, I will deploy the whole app and will let you know.</p>
<h2 id="heading-closing">Closing</h2>
<p>This article introduced you to the pub/sub messaging pattern subject. It's important to remember that whenever we want to build a highly performant application with a low coupled architecture and real-time like messaging features, consider using the Publish/Subscribe pattern and Redis in particular.</p>
<p>In fact a lot of the real life applications that use Redis are dashboard-based. This means that usually there is a nice dashboard screen, showing different data, often being updated in real time.</p>
<p>Imagine, for example, a system showing traffic in a specific area. This kind of software is a perfect candidate for leveraging the advantages of pub/sub. And in many cases this is achieved by using Redis.</p>
<p>In any case, as developers and engineers, we should always be guided by the specific needs of the project we are working on. Whenever we decide to introduce a new pattern or technology, we should do it carefully and back it up with serious research.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The AI Chatbot Handbook – How to Build an AI Chatbot with Redis, Python, and GPT ]]>
                </title>
                <description>
                    <![CDATA[ By Stephen Sanwo In order to build a working full-stack application, there are so many moving parts to think about. And you'll need to make many decisions that will be critical to the success of your app. For example, what language will you use and w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-an-ai-chatbot-with-redis-python-and-gpt/</link>
                <guid isPermaLink="false">66d4614abd438296f45cd3b8</guid>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #chatbots ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 27 Jul 2022 20:16:44 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/The-AI-Chatbot-Handbook-Cover--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Stephen Sanwo</p>
<p>In order to build a working full-stack application, there are so many moving parts to think about. And you'll need to make many decisions that will be critical to the success of your app.</p>
<p>For example, what language will you use and what platform will you deploy on? Are you going to deploy a containerised software on a server, or make use of serverless functions to handle the backend? Do you plan to use third-party APIs to handle complex parts of your application, like authentication or payments? Where do you store the data? </p>
<p>In addition to all this, you'll also need to think about the user interface, design and usability of your application, and much more. </p>
<p>This is why complex large applications require a multifunctional development team collaborating to build the app.</p>
<p>One of the best ways to learn how to develop full stack applications is to build projects that cover the end-to-end development process. You'll go through designing the architecture, developing the API services, developing the user interface, and finally deploying your application. </p>
<p>So this tutorial will take you through the process of building an AI chatbot to help you learn these concepts in depth.</p>
<p>Some of the topics we will cover include:</p>
<ul>
<li>How to build APIs with Python, FastAPI, and WebSockets</li>
<li>How to build real-time systems with Redis</li>
<li>How to build a chat User Interface with React</li>
</ul>
<p><strong>Important Note:</strong>
This is an intermediate full stack software development project that requires some basic Python and JavaScript knowledge. </p>
<p>I've carefully divided the project into sections to ensure that you can easily select the phase that is important to you in case you do not wish to code the full application.</p>
<p>You can download the full repository on <a target="_blank" href="https://github.com/stephensanwo/fullstack-ai-chatbot">My Github here</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<h3 id="heading-section-1">Section 1</h3>
<ul>
<li><a class="post-section-overview" href="#application-architecture">Application Architecture</a></li>
<li><a class="post-section-overview" href="#how-to-set-up-the-development-environment">How to Set Up the Development Environment</a><h3 id="heading-section-2">Section 2</h3>
</li>
<li><a class="post-section-overview" href="#how-to-build-a-chat-server-with-python-fastapi-and-websockets">How to Build a Chat Server with Python, FastAPI, and WebSockets</a><ul>
<li><a class="post-section-overview" href="#how-to-set-up-the-python-environment">How to Set Up the Python Environment</a></li>
<li><a class="post-section-overview" href="#fastapi-server-setup">FastAPI Server Setup</a></li>
<li><a class="post-section-overview" href="#how-to-add-routes-to-the-api">How to Add Routes to the API</a></li>
<li><a class="post-section-overview" href="#how-to-generate-a-chat-session-token-with-uuid">How to Generate a Chat Session Token with UUID</a></li>
<li><a class="post-section-overview" href="#how-to-test-the-api-with-postman">How to Test the API with Postman</a></li>
<li><a class="post-section-overview" href="#websockets-and-connection-manager">Websockets and Connection Manager</a></li>
<li><a class="post-section-overview" href="#dependency-injection-in-fastapi">Dependency Injection in FastAPI</a><h3 id="heading-section-3">Section 3</h3>
</li>
</ul>
</li>
<li><a class="post-section-overview" href="#how-to-build-real-time-systems-with-redis">How to build Real-Time Systems with Redis</a><ul>
<li><a class="post-section-overview" href="#redis-and-distributed-messaging-queues">Redis and Distributed Messaging Queues</a></li>
<li><a class="post-section-overview" href="#how-to-connect-to-a-redis-cluster-in-python-with-a-redis-client">How to Connect to a Redis Cluster in Python with a Redis Client</a></li>
<li><a class="post-section-overview" href="#how-to-work-with-redis-streams">How to Work with Redis Streams</a></li>
<li><a class="post-section-overview" href="#how-to-model-the-chat-data">How to Model the Chat Data</a></li>
<li><a class="post-section-overview" href="#how-to-work-with-redis-json">How to Work with Redis JSON</a></li>
<li><a class="post-section-overview" href="#how-to-update-the-token-dependency">How to Update the Token Dependency</a><h3 id="heading-section-4">Section 4</h3>
</li>
</ul>
</li>
<li><a class="post-section-overview" href="#how-to-add-intelligence-to-chatbots-with-ai-models">How to Add Intelligence to Chatbots with AI models</a><ul>
<li><a class="post-section-overview" href="#how-to-get-started-with-huggingface">How to Get Started with Huggingface</a></li>
<li><a class="post-section-overview" href="#how-to-interact-with-the-language-model">How to Interact with the Language Model</a></li>
<li><a class="post-section-overview" href="#how-to-simulate-short-term-memory-for-the-ai-model">How to Simulate Short-term Memory for the AI Model</a></li>
<li><a class="post-section-overview" href="#stream-consumer-and-real-time-data-pull-from-the-message-queue">Stream Consumer and Real-timeDdata Pull from the Message Queue</a></li>
<li><a class="post-section-overview" href="#how-to-update-the-chat-client-with-the-ai-response">How to Update the Chat Client with the AI Response</a></li>
<li><a class="post-section-overview" href="#refresh-token">Refresh Token</a></li>
<li><a class="post-section-overview" href="#how-to-test-the-chat-with-multiple-clients-in-postman">How to Test the Chat with Multiple Clients in Postman</a></li>
</ul>
</li>
</ul>
<h2 id="heading-application-architecture">Application Architecture <a></a></h2>
<p>Sketching out a solution architecture gives you a high-level overview of your application, the tools you intend to use, and how the components will communicate with each other. </p>
<p>I have drawn up a simple architecture below using <a target="_blank" href="http://draw.io">draw.io</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/full-stack-chatbot-architecture.drawio.svg" alt="Image" width="600" height="400" loading="lazy">
<em>Fullstack chatbot architecture</em></p>
<p>Let's go over the various parts of the architecture in more detail:</p>
<h3 id="heading-clientuser-interface">Client/User Interface</h3>
<p>We will use React version 18 to build the user interface. The Chat UI will communicate with the backend via WebSockets.</p>
<h3 id="heading-gpt-j-6b-and-huggingface-inference-api">GPT-J-6B and Huggingface Inference API</h3>
<p>GPT-J-6B is a generative language model which was trained with 6 Billion parameters and performs closely with OpenAI's GPT-3 on some tasks. </p>
<p>I have chosen to use GPT-J-6B because it is an open-source model and doesn’t require paid tokens for simple use cases. </p>
<p>Huggingface also provides us with an on-demand API to connect with this model pretty much free of charge. You can read more about <a target="_blank" href="https://huggingface.co/EleutherAI/gpt-j-6B?text=My+name+is+Teven+and+I+am">GPT-J-6B</a> and <a target="_blank" href="https://huggingface.co/inference-api">Hugging Face Inference API</a>.</p>
<h3 id="heading-redis">Redis</h3>
<p>When we send prompts to GPT, we need a way to store the prompts and easily retrieve the response. We will use Redis JSON to store the chat data and also use Redis Streams for handling the real-time communication with the huggingface inference API. </p>
<p>Redis is an in-memory key-value store that enables super-fast fetching and storing of JSON-like data. For this tutorial, we will use a managed free Redis storage provided by <a target="_blank" href="https://redis.info/3NBGJRT">Redis Enterprise</a> for testing purposes.</p>
<h3 id="heading-web-sockets-and-the-chat-api">Web Sockets and the Chat API</h3>
<p>To send messages between the client and server in real-time, we need to open a socket connection. This is because an HTTP connection will not be sufficient to ensure real-time bi-directional communication between the client and the server. </p>
<p>We will be using FastAPI for the chat server, as it provides a fast and modern Python server for our use. <a target="_blank" href="https://fastapi.tiangolo.com/advanced/websockets/?h=web">Check out the FastAPI documentation</a>) to learn more about WebSockets.</p>
<h2 id="heading-how-to-set-up-the-development-environment">How to Set Up the Development Environment <a></a></h2>
<p>You can use your desired OS to build this app – I am currently using MacOS, and Visual Studio Code. Just make sure you have Python and NodeJs installed. </p>
<p>To set up the project structure, create a folder named<code>fullstack-ai-chatbot</code>. Then create two folders within the project called <code>client</code> and <code>server</code>. The server will hold the code for the backend, while the client will hold the code for the frontend.</p>
<p>Next within the project directory, initialize a Git repository within the root of the project folder using the "git init" command. Then create a .gitignore file by using "touch .gitignore":</p>
<pre><code class="lang-bash">git init
touch .gitignore
</code></pre>
<p>In the next section, we will build our chat web server using FastAPI and Python.</p>
<h2 id="heading-how-to-build-a-chat-server-with-python-fastapi-and-websockets">How to Build a Chat Server with Python, FastAPI and WebSockets <a></a></h2>
<p>In this section, we will build the chat server using FastAPI to communicate with the user. We will use WebSockets to ensure bi-directional communication between the client and server so that we can send responses to the user in real-time.</p>
<h3 id="heading-how-to-set-up-the-python-environment">How to Set Up the Python Environment <a></a></h3>
<p>To start our server, we need to set up our Python environment. Open the project folder within VS Code, and open up the terminal.</p>
<p>From the project root, cd into the server directory and run <code>python3.8 -m venv env</code>. This will create a <a target="_blank" href="https://blog.stephensanwo.dev/virtual-environments-in-python"><strong>virtual environment</strong></a> for our Python project, which will be named <code>env</code>. To activate the virtual environment, run <code>source env/bin/activate</code></p>
<p>Next, install a couple of libraries in your Python environment.</p>
<pre><code class="lang-bash">pip install fastapi uuid uvicorn gunicorn WebSockets python-dotenv aioredis
</code></pre>
<p>Next create an environment file by running <code>touch .env</code> in the terminal. We will define our app variables and secret variables within the <code>.env</code> file. </p>
<p>Add your app environment variable and set it to "development" like so: <code>export APP_ENV=development</code>. Next, we will set up a development server with a FastAPI server.</p>
<h3 id="heading-fastapi-server-setup">FastAPI Server Setup <a></a></h3>
<p>At the root of the server directory, create a new file named <code>main.py</code> then paste the code below for the development sever:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI, Request
<span class="hljs-keyword">import</span> uvicorn
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv

load_dotenv()

api = FastAPI()

<span class="hljs-meta">@api.get("/test")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">root</span>():</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"msg"</span>: <span class="hljs-string">"API is Online"</span>}


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    <span class="hljs-keyword">if</span> os.environ.get(<span class="hljs-string">'APP_ENV'</span>) == <span class="hljs-string">"development"</span>:
        uvicorn.run(<span class="hljs-string">"main:api"</span>, host=<span class="hljs-string">"0.0.0.0"</span>, port=<span class="hljs-number">3500</span>,
                    workers=<span class="hljs-number">4</span>, reload=<span class="hljs-literal">True</span>)
    <span class="hljs-keyword">else</span>:
      <span class="hljs-keyword">pass</span>
</code></pre>
<p>First we <code>import FastAPI</code> and initialize it as <code>api</code>. Then we <code>import load_dotenv</code> from the <code>python-dotenv</code> library, and initialize it to load the variables from the <code>.env</code> file,</p>
<p>Then we create a simple test route to test the API. The test route will return a simple JSON response that tells us the API is online. </p>
<p>Lastly, we set up the development server by using <code>uvicorn.run</code> and providing the required arguments. The API will run on port <code>3500</code>.</p>
<p>Finally, run the server in the terminal with <code>python main.py</code>. Once you see <code>Application startup complete</code> in the terminal, navigate to the URL <a target="_blank" href="http://localhost:3500/test">http://localhost:3500/test</a> on your browser, and you should get a web page like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/test-page.png" alt="Image" width="600" height="400" loading="lazy">
<em>API Test Page</em></p>
<h3 id="heading-how-to-add-routes-to-the-api">How to Add Routes to the API <a></a></h3>
<p>In this section, we will add routes to our API. Create a new folder named <code>src</code>. This is the directory where all our API code will live. </p>
<p>Create a subfolder named <code>routes</code>, cd into the folder, create a new file named <code>chat.py</code> and then add the code below:</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> APIRouter, FastAPI, WebSocket,  Request

chat = APIRouter()

<span class="hljs-comment"># @route   POST /token</span>
<span class="hljs-comment"># @desc    Route to generate chat token</span>
<span class="hljs-comment"># @access  Public</span>

<span class="hljs-meta">@chat.post("/token")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">token_generator</span>(<span class="hljs-params">request: Request</span>):</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>


<span class="hljs-comment"># @route   POST /refresh_token</span>
<span class="hljs-comment"># @desc    Route to refresh token</span>
<span class="hljs-comment"># @access  Public</span>

<span class="hljs-meta">@chat.post("/refresh_token")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">refresh_token</span>(<span class="hljs-params">request: Request</span>):</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>


<span class="hljs-comment"># @route   Websocket /chat</span>
<span class="hljs-comment"># @desc    Socket for chatbot</span>
<span class="hljs-comment"># @access  Public</span>

<span class="hljs-meta">@chat.websocket("/chat")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">websocket_endpoint</span>(<span class="hljs-params">websocket: WebSocket = WebSocket</span>):</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
</code></pre>
<p>We created three endpoints:</p>
<ul>
<li><code>/token</code> will issue the user a session token for access to the chat session. Since the chat app will be open publicly, we do not want to worry about authentication and just keep it simple – but we still need a way to identify each unique user session.</li>
<li><code>/refresh_token</code> will get the session history for the user if the connection is lost, as long as the token is still active and not expired.</li>
<li><code>/chat</code> will open a WebSocket to send messages between the client and server.</li>
</ul>
<p>Next, connect the chat route to our main API. First we need to <code>import chat from src.chat</code> within our <code>main.py</code> file. Then we will include the router by literally calling an <code>include_router</code> method on the initialized <code>FastAPI</code> class and passing chat as the argument. </p>
<p>Update your <code>api.py</code> code as shown below:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI, Request
<span class="hljs-keyword">import</span> uvicorn
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
<span class="hljs-keyword">from</span> routes.chat <span class="hljs-keyword">import</span> chat

load_dotenv()

api = FastAPI()
api.include_router(chat)


<span class="hljs-meta">@api.get("/test")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">root</span>():</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"msg"</span>: <span class="hljs-string">"API is Online"</span>}


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    <span class="hljs-keyword">if</span> os.environ.get(<span class="hljs-string">'APP_ENV'</span>) == <span class="hljs-string">"development"</span>:
        uvicorn.run(<span class="hljs-string">"main:api"</span>, host=<span class="hljs-string">"0.0.0.0"</span>, port=<span class="hljs-number">3500</span>,
                    workers=<span class="hljs-number">4</span>, reload=<span class="hljs-literal">True</span>)
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">pass</span>
</code></pre>
<h3 id="heading-how-to-generate-a-chat-session-token-with-uuid">How to Generate a Chat Session Token with UUID <a></a></h3>
<p>To generate a user token we will use <code>uuid4</code> to create dynamic routes for our chat endpoint. Since this is a publicly available endpoint, we won't need to go into details about JWTs and authentication. </p>
<p>If you didn't install <code>uuid</code> initially, run <code>pip install uuid</code>. Next in chat.py, import UUID, and update the <code>/token</code> route with the code below:</p>
<pre><code class="lang-py">
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> APIRouter, FastAPI, WebSocket,  Request, BackgroundTasks, HTTPException
<span class="hljs-keyword">import</span> uuid

<span class="hljs-comment"># @route   POST /token</span>
<span class="hljs-comment"># @desc    Route generating chat token</span>
<span class="hljs-comment"># @access  Public</span>

<span class="hljs-meta">@chat.post("/token")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">token_generator</span>(<span class="hljs-params">name: str, request: Request</span>):</span>

    <span class="hljs-keyword">if</span> name == <span class="hljs-string">""</span>:
        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">400</span>, detail={
            <span class="hljs-string">"loc"</span>: <span class="hljs-string">"name"</span>,  <span class="hljs-string">"msg"</span>: <span class="hljs-string">"Enter a valid name"</span>})

    token = str(uuid.uuid4())

    data = {<span class="hljs-string">"name"</span>: name, <span class="hljs-string">"token"</span>: token}

    <span class="hljs-keyword">return</span> data
</code></pre>
<p>In the code above, the client provides their name, which is required. We do a quick check to ensure that the name field is not empty, then generate a token using uuid4. </p>
<p>The session data is a simple dictionary for the name and token. Ultimately we will need to persist this session data and set a timeout, but for now we just return it to the client.</p>
<h3 id="heading-how-to-test-the-api-with-postman">How to Test the API with Postman <a></a></h3>
<p>Because we will be testing a WebSocket endpoint, we need to use a tool like <a target="_blank" href="https://www.postman.com">Postman</a> that allows this (as the default swagger docs on FastAPI does not support WebSockets). </p>
<p>In Postman, create a collection for your development environment and send a POST request to <code>localhost:3500/token</code> specifying the name as a query parameter and passing it a value. You should get a response as shown below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/token-generator-postman.png" alt="Image" width="600" height="400" loading="lazy">
<em>Token Generator Postman</em></p>
<h3 id="heading-websockets-and-connection-manager">Websockets and Connection Manager <a></a></h3>
<p>In the src root, create a new folder named <code>socket</code> and add a file named <code>connection.py</code>. In this file, we will define the class that controls the connections to our WebSockets, and all the helper methods to connect and disconnect. </p>
<p>In <code>connection.py</code> add the code below:</p>
<pre><code class="lang-py">
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> WebSocket

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConnectionManager</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.active_connections: List[WebSocket] = []

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">connect</span>(<span class="hljs-params">self, websocket: WebSocket</span>):</span>
        <span class="hljs-keyword">await</span> websocket.accept()
        self.active_connections.append(websocket)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">disconnect</span>(<span class="hljs-params">self, websocket: WebSocket</span>):</span>
        self.active_connections.remove(websocket)

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_personal_message</span>(<span class="hljs-params">self, message: str, websocket: WebSocket</span>):</span>
        <span class="hljs-keyword">await</span> websocket.send_text(message)
</code></pre>
<p>The <code>ConnectionManager</code> class is initialized with an <code>active_connections</code> attribute that is a list of active connections. </p>
<p>Then the asynchronous <code>connect</code> method will accept a <code>WebSocket</code> and add it to the list of active connections, while the <code>disconnect</code> method will remove the <code>Websocket</code> from the list of active connections. </p>
<p>Lastly, the <code>send_personal_message</code> method will take in a message and the <code>Websocket</code> we want to send the message to and asynchronously send the message.</p>
<p>WebSockets are a very broad topic and we only scraped the surface here. This should however be sufficient to create multiple connections and handle messages to those connections asynchronously. </p>
<p>You can read more about <a target="_blank" href="https://fastapi.tiangolo.com/advanced/websockets/?h=depends#using-depends-and-others">FastAPI Websockets</a> and <a target="_blank" href="https://realpython.com/python-sockets/">Sockets Programming</a>.</p>
<p>To use the <code>ConnectionManager</code>, import and initialize it within the <code>src.routes.chat.py</code>, and update the <code>/chat</code> WebSocket route with the code below:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> ..socket.connection <span class="hljs-keyword">import</span> ConnectionManager

manager = ConnectionManager()

<span class="hljs-meta">@chat.websocket("/chat")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">websocket_endpoint</span>(<span class="hljs-params">websocket: WebSocket</span>):</span>
    <span class="hljs-keyword">await</span> manager.connect(websocket)
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            data = <span class="hljs-keyword">await</span> websocket.receive_text()
            print(data)
            <span class="hljs-keyword">await</span> manager.send_personal_message(<span class="hljs-string">f"Response: Simulating response from the GPT service"</span>, websocket)

    <span class="hljs-keyword">except</span> WebSocketDisconnect:
        manager.disconnect(websocket)
</code></pre>
<p>In the <code>websocket_endpoint</code> function, which takes a WebSocket, we add the new websocket to the connection manager and run a <code>while True</code> loop, to ensure that the socket stays open. Except when the socket gets disconnected. </p>
<p>While the connection is open, we receive any messages sent by the client with <code>websocket.receive_test()</code> and print them to the terminal for now. </p>
<p>Then we send a hard-coded response back to the client for now. Ultimately the message received from the clients will be sent to the AI Model, and the response sent back to the client will be the response from the AI Model.</p>
<p>In Postman, we can test this endpoint by creating a new WebSocket request, and connecting to the WebSocket endpoint <code>localhost:3500/chat</code>. </p>
<p>When you click connect, the Messages pane will show that the API client is connected to the URL, and a socket is open. </p>
<p>To test this, send a message "Hello Bot" to the chat server, and you should get an immediate test response "Response: Simulating response from the GPT service" as shown below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/postman-chat-test.png" alt="Image" width="600" height="400" loading="lazy">
<em>Postman Chat Test</em></p>
<h3 id="heading-dependency-injection-in-fastapi">Dependency Injection in FastAPI <a></a></h3>
<p>To be able to distinguish between two different client sessions and limit the chat sessions, we will use a timed token, passed as a query parameter to the WebSocket connection. </p>
<p>In the socket folder, create a file named <code>utils.py</code> then add the code below:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> WebSocket, status, Query
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Optional

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_token</span>(<span class="hljs-params">
    websocket: WebSocket,
    token: Optional[str] = Query(<span class="hljs-params">None</span>),
</span>):</span>
    <span class="hljs-keyword">if</span> token <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">or</span> token == <span class="hljs-string">""</span>:
        <span class="hljs-keyword">await</span> websocket.close(code=status.WS_1008_POLICY_VIOLATION)

    <span class="hljs-keyword">return</span> token
</code></pre>
<p>The get_token function receives a WebSocket and token, then checks if the token is None or null. </p>
<p>If this is the case, the function returns a policy violation status and if available, the function just returns the token. We will ultimately extend this function later with additional token validation.</p>
<p>To consume this function, we inject it into the <code>/chat</code> route. FastAPI provides a Depends class to easily inject dependencies, so we don't have to tinker with decorators. </p>
<p>Update the <code>/chat</code> route to the following:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> ..socket.utils <span class="hljs-keyword">import</span> get_token

<span class="hljs-meta">@chat.websocket("/chat")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">websocket_endpoint</span>(<span class="hljs-params">websocket: WebSocket, token: str = Depends(<span class="hljs-params">get_token</span>)</span>):</span>
    <span class="hljs-keyword">await</span> manager.connect(websocket)
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            data = <span class="hljs-keyword">await</span> websocket.receive_text()
            print(data)
            <span class="hljs-keyword">await</span> manager.send_personal_message(<span class="hljs-string">f"Response: Simulating response from the GPT service"</span>, websocket)

    <span class="hljs-keyword">except</span> WebSocketDisconnect:
        manager.disconnect(websocket)
</code></pre>
<p>Now when you try to connect to the <code>/chat</code> endpoint in Postman, you will get a 403 error. Provide a token as query parameter and provide any value to the token, for now. Then you should be able to connect like before, only now the connection requires a token.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/postman-chat-test-token.png" alt="Image" width="600" height="400" loading="lazy">
<em>Postman Chat Test with Token</em></p>
<p>Congratulations on getting this far! Your <code>chat.py</code> file should now look like this:</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> APIRouter, FastAPI, WebSocket, WebSocketDisconnect, Request, Depends, HTTPException
<span class="hljs-keyword">import</span> uuid
<span class="hljs-keyword">from</span> ..socket.connection <span class="hljs-keyword">import</span> ConnectionManager
<span class="hljs-keyword">from</span> ..socket.utils <span class="hljs-keyword">import</span> get_token


chat = APIRouter()

manager = ConnectionManager()

<span class="hljs-comment"># @route   POST /token</span>
<span class="hljs-comment"># @desc    Route to generate chat token</span>
<span class="hljs-comment"># @access  Public</span>


<span class="hljs-meta">@chat.post("/token")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">token_generator</span>(<span class="hljs-params">name: str, request: Request</span>):</span>
    token = str(uuid.uuid4())

    <span class="hljs-keyword">if</span> name == <span class="hljs-string">""</span>:
        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">400</span>, detail={
            <span class="hljs-string">"loc"</span>: <span class="hljs-string">"name"</span>,  <span class="hljs-string">"msg"</span>: <span class="hljs-string">"Enter a valid name"</span>})

    data = {<span class="hljs-string">"name"</span>: name, <span class="hljs-string">"token"</span>: token}

    <span class="hljs-keyword">return</span> data


<span class="hljs-comment"># @route   POST /refresh_token</span>
<span class="hljs-comment"># @desc    Route to refresh token</span>
<span class="hljs-comment"># @access  Public</span>


<span class="hljs-meta">@chat.post("/refresh_token")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">refresh_token</span>(<span class="hljs-params">request: Request</span>):</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>


<span class="hljs-comment"># @route   Websocket /chat</span>
<span class="hljs-comment"># @desc    Socket for chatbot</span>
<span class="hljs-comment"># @access  Public</span>

<span class="hljs-meta">@chat.websocket("/chat")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">websocket_endpoint</span>(<span class="hljs-params">websocket: WebSocket, token: str = Depends(<span class="hljs-params">get_token</span>)</span>):</span>
    <span class="hljs-keyword">await</span> manager.connect(websocket)
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            data = <span class="hljs-keyword">await</span> websocket.receive_text()
            print(data)
            <span class="hljs-keyword">await</span> manager.send_personal_message(<span class="hljs-string">f"Response: Simulating response from the GPT service"</span>, websocket)

    <span class="hljs-keyword">except</span> WebSocketDisconnect:
        manager.disconnect(websocket)
</code></pre>
<p>In the next part of this tutorial, we will focus on handling the state of our application and passing data between client and server.</p>
<h2 id="heading-how-to-build-real-time-systems-with-redis">How to Build Real-Time Systems with Redis <a></a></h2>
<p>Our application currently does not store any state, and there is no way to identify users or store and retrieve chat data. We are also returning a hard-coded response to the client during chat sessions. </p>
<p>In this part of the tutorial, we will cover the following:</p>
<ul>
<li>How to connect to a <strong>Redis Cluster</strong> in Python and set up a <strong>Redis Client</strong></li>
<li>How to store and retrieve data with <strong>Redis JSON</strong></li>
<li>How to set up <strong>Redis Streams</strong> as message queues between a web server and worker environment</li>
</ul>
<h3 id="heading-redis-and-distributed-messaging-queues">Redis and Distributed Messaging Queues <a></a></h3>
<p>Redis is an open source in-memory data store that you can use as a database, cache, message broker, and streaming engine. It supports a number of data structures and is a perfect solution for distributed applications with real-time capabilities. </p>
<p><strong>Redis Enterprise Cloud</strong> is a fully managed cloud service provided by Redis that helps us deploy Redis clusters at an infinite scale without worrying about infrastructure. </p>
<p>We will be using a free Redis Enterprise Cloud instance for this tutorial. You can <a target="_blank" href="https://redis.com/try-free/?utm_campaign=write_for_redis">Get started with Redis Cloud for free here</a> and follow <a target="_blank" href="https://developer.redis.com/create/rediscloud/">This tutorial to set up a Redis database and Redis Insight, a GUI to interact with Redis</a>.</p>
<p>Once you have set up your Redis database, create a new folder in the project root (outside the server folder) named <code>worker</code>. </p>
<p>We will isolate our worker environment from the web server so that when the client sends a message to our WebSocket, the web server does not have to handle the request to the third-party service. Also, resources can be freed up for other users. </p>
<p>The background communication with the inference API is handled by this worker service, through Redis.</p>
<p>Requests from all the connected clients are appended to the message queue (producer), while the worker consumes the messages, sends off the requests to the inference API, and appends the response to a response queue. </p>
<p>Once the API receives a response, it sends it back to the client. </p>
<p>During the trip between the producer and the consumer, the client can send multiple messages, and these messages will be queued up and responded to in order. </p>
<p>Ideally, we could have this worker running on a completely different server, in its own environment, but for now, we will create its own Python environment on our local machine.</p>
<p>You might be wondering – <strong>why do we need a worker?</strong> Imagine a scenario where the web server also creates the request to the third-party service. This means that while waiting for the response from the third party service during a socket connection, the server is blocked and resources are tied up till the response is obtained from the API. </p>
<p>You can try this out by creating a random sleep <code>time.sleep(10)</code> before sending the hard-coded response, and sending a new message. Then try to connect with a different token in a new postman session. </p>
<p>You will notice that the chat session will not connect until the random sleep times out.</p>
<p>While we can use asynchronous techniques and worker pools in a more production-focused server set-up, that also won't be enough as the number of simultaneous users grow. </p>
<p>Ultimately, we want to avoid tying up the web server resources by using Redis to broker the communication between our chat API and the third-party API.</p>
<p>Next open up a new terminal, cd into the worker folder, and create and activate a new Python virtual environment similar to what we did in part 1. </p>
<p>Next, install the following dependencies:</p>
<pre><code class="lang-bash">pip install aiohttp aioredis python-dotenv
</code></pre>
<h3 id="heading-how-to-connect-to-a-redis-cluster-in-python-with-a-redis-client">How to Connect to a Redis Cluster in Python with a Redis Client <a></a></h3>
<p>We will use the aioredis client to connect with the Redis database. We'll also use the requests library to send requests to the Huggingface inference API. </p>
<p>Create two files <code>.env</code>, and <code>main.py</code>. Then create a folder named <code>src</code>. Also, create a folder named <code>redis</code> and add a new file named <code>config.py</code>. </p>
<p>In the <code>.env</code> file, add the following code – and make sure you update the fields with the credentials provided in your Redis Cluster.</p>
<pre><code class="lang-txt">export REDIS_URL=&lt;REDIS URL PROVIDED IN REDIS CLOUD&gt;
export REDIS_USER=&lt;REDIS USER IN REDIS CLOUD&gt;
export REDIS_PASSWORD=&lt;DATABASE PASSWORD IN REDIS CLOUD&gt;
export REDIS_HOST=&lt;REDIS HOST IN REDIS CLOUD&gt;
export REDIS_PORT=&lt;REDIS PORT IN REDIS CLOUD&gt;
</code></pre>
<p>In config.py add the Redis Class below:</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
<span class="hljs-keyword">import</span> aioredis

load_dotenv()

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Redis</span>():</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""initialize  connection """</span>
        self.REDIS_URL = os.environ[<span class="hljs-string">'REDIS_URL'</span>]
        self.REDIS_PASSWORD = os.environ[<span class="hljs-string">'REDIS_PASSWORD'</span>]
        self.REDIS_USER = os.environ[<span class="hljs-string">'REDIS_USER'</span>]
        self.connection_url = <span class="hljs-string">f"redis://<span class="hljs-subst">{self.REDIS_USER}</span>:<span class="hljs-subst">{self.REDIS_PASSWORD}</span>@<span class="hljs-subst">{self.REDIS_URL}</span>"</span>

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_connection</span>(<span class="hljs-params">self</span>):</span>
        self.connection = aioredis.from_url(
            self.connection_url, db=<span class="hljs-number">0</span>)

        <span class="hljs-keyword">return</span> self.connection
</code></pre>
<p>We create a Redis object and initialize the required parameters from the environment variables. Then we create an asynchronous method <code>create_connection</code> to create a Redis connection and return the connection pool obtained from the <code>aioredis</code> method <code>from_url</code>.</p>
<p>Next, we test the Redis connection in main.py by running the code below. This will create a new Redis connection pool, set a simple key "key", and assign a string "value" to it.</p>
<pre><code class="lang-py">
<span class="hljs-keyword">from</span> src.redis.config <span class="hljs-keyword">import</span> Redis
<span class="hljs-keyword">import</span> asyncio

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    redis = Redis()
    redis = <span class="hljs-keyword">await</span> redis.create_connection()
    print(redis)
    <span class="hljs-keyword">await</span> redis.set(<span class="hljs-string">"key"</span>, <span class="hljs-string">"value"</span>)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    asyncio.run(main())
</code></pre>
<p>Now open Redis Insight (if you followed the tutorial to download and install it) You should see something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/redis-insight-test.png" alt="Image" width="600" height="400" loading="lazy">
<em>Redis Insight Test</em></p>
<h3 id="heading-how-to-work-with-redis-streams">How to Work with Redis Streams <a></a></h3>
<p>Now that we have our worker environment setup, we can create a producer on the web server and a consumer on the worker. </p>
<p>First, let's create our Redis class again on the server. In <code>server.src</code> create a folder named <code>redis</code> and add two files, <code>config.py</code> and <code>producer.py</code>. </p>
<p>In <code>config.py</code>, add the code below as we did for the worker environment:</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
<span class="hljs-keyword">import</span> aioredis

load_dotenv()

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Redis</span>():</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""initialize  connection """</span>
        self.REDIS_URL = os.environ[<span class="hljs-string">'REDIS_URL'</span>]
        self.REDIS_PASSWORD = os.environ[<span class="hljs-string">'REDIS_PASSWORD'</span>]
        self.REDIS_USER = os.environ[<span class="hljs-string">'REDIS_USER'</span>]
        self.connection_url = <span class="hljs-string">f"redis://<span class="hljs-subst">{self.REDIS_USER}</span>:<span class="hljs-subst">{self.REDIS_PASSWORD}</span>@<span class="hljs-subst">{self.REDIS_URL}</span>"</span>

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_connection</span>(<span class="hljs-params">self</span>):</span>
        self.connection = aioredis.from_url(
            self.connection_url, db=<span class="hljs-number">0</span>)

        <span class="hljs-keyword">return</span> self.connection
</code></pre>
<p>In the .env file, also add the Redis credentials:</p>
<pre><code class="lang-txt">export REDIS_URL=&lt;REDIS URL PROVIDED IN REDIS CLOUD&gt;
export REDIS_USER=&lt;REDIS USER IN REDIS CLOUD&gt;
export REDIS_PASSWORD=&lt;DATABASE PASSWORD IN REDIS CLOUD&gt;
export REDIS_HOST=&lt;REDIS HOST IN REDIS CLOUD&gt;
export REDIS_PORT=&lt;REDIS PORT IN REDIS CLOUD&gt;
</code></pre>
<p>Finally, in <code>server.src.redis.producer.py</code> add the following code:</p>
<pre><code class="lang-py">
<span class="hljs-keyword">from</span> .config <span class="hljs-keyword">import</span> Redis

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Producer</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, redis_client</span>):</span>
        self.redis_client = redis_client

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_to_stream</span>(<span class="hljs-params">self,  data: dict, stream_channel</span>):</span>
        <span class="hljs-keyword">try</span>:
            msg_id = <span class="hljs-keyword">await</span> self.redis_client.xadd(name=stream_channel, id=<span class="hljs-string">"*"</span>, fields=data)
            print(<span class="hljs-string">f"Message id <span class="hljs-subst">{msg_id}</span> added to <span class="hljs-subst">{stream_channel}</span> stream"</span>)
            <span class="hljs-keyword">return</span> msg_id

        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            print(<span class="hljs-string">f"Error sending msg to stream =&gt; <span class="hljs-subst">{e}</span>"</span>)
</code></pre>
<p>We created a Producer class that is initialized with a Redis client. We use this client to add data to the stream with the <code>add_to_stream</code> method, which takes the data and the Redis channel name. </p>
<p>The Redis command for adding data to a stream channel is <code>xadd</code> and it has both high-level and low-level functions in aioredis.</p>
<p>Next, to run our newly created Producer, update <code>chat.py</code> and the WebSocket <code>/chat</code> endpoint like below. Notice the updated channel name <code>message_channel</code>.</p>
<pre><code class="lang-py">
<span class="hljs-keyword">from</span> ..redis.producer <span class="hljs-keyword">import</span> Producer
<span class="hljs-keyword">from</span> ..redis.config <span class="hljs-keyword">import</span> Redis

chat = APIRouter()
manager = ConnectionManager()
redis = Redis()


<span class="hljs-meta">@chat.websocket("/chat")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">websocket_endpoint</span>(<span class="hljs-params">websocket: WebSocket, token: str = Depends(<span class="hljs-params">get_token</span>)</span>):</span>
    <span class="hljs-keyword">await</span> manager.connect(websocket)
    redis_client = <span class="hljs-keyword">await</span> redis.create_connection()
    producer = Producer(redis_client)

    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            data = <span class="hljs-keyword">await</span> websocket.receive_text()
            print(data)
            stream_data = {}
            stream_data[token] = data
            <span class="hljs-keyword">await</span> producer.add_to_stream(stream_data, <span class="hljs-string">"message_channel"</span>)
            <span class="hljs-keyword">await</span> manager.send_personal_message(<span class="hljs-string">f"Response: Simulating response from the GPT service"</span>, websocket)

    <span class="hljs-keyword">except</span> WebSocketDisconnect:
        manager.disconnect(websocket)
</code></pre>
<p>Next, in Postman, create a connection and send any number of messages that say <code>Hello</code>. You should have the stream messages printed to the terminal like below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/terminal-channel-messages-test.png" alt="Image" width="600" height="400" loading="lazy">
<em>Terminal Channel Messages Test</em></p>
<p>In Redis Insight, you will see a new <code>mesage_channel</code> created and a time-stamped queue filled with the messages sent from the client. This timestamped queue is important to preserve the order of the messages.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/redis-insight-channel.png" alt="Image" width="600" height="400" loading="lazy">
<em>Redis Insight Channel</em></p>
<h3 id="heading-how-to-model-the-chat-data">How to Model the Chat Data <a></a></h3>
<p>Next, we'll create a model for our chat messages. Recall that we are sending text data over WebSockets, but our chat data needs to hold more information than just the text. We need to timestamp when the chat was sent, create an ID for each message, and collect data about the chat session, then store this data in a JSON format. </p>
<p>We can store this JSON data in Redis so we don't lose the chat history once the connection is lost, because our WebSocket does not store state.</p>
<p>In <code>server.src</code> create a new folder named <code>schema</code>. Then create a file named <code>chat.py</code> in <code>server.src.schema</code> add the following code:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> List, Optional
<span class="hljs-keyword">import</span> uuid


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Message</span>(<span class="hljs-params">BaseModel</span>):</span>
    id = uuid.uuid4()
    msg: str
    timestamp = str(datetime.now())


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Chat</span>(<span class="hljs-params">BaseModel</span>):</span>
    token: str
    messages: List[Message]
    name: str
    session_start = str(datetime.now())
</code></pre>
<p>We are using Pydantic's <code>BaseModel</code> class to model the chat data. The <code>Chat</code> class will hold data about a single Chat session. It will store the token, name of the user, and an automatically generated timestamp for the chat session start time using <code>datetime.now()</code>. </p>
<p>The messages sent and received within this chat session are stored with a <code>Message</code> class which creates a chat id on the fly using <code>uuid4</code>. The only data we need to provide when initializing this <code>Message</code> class is the message text.</p>
<h3 id="heading-how-to-work-with-redis-json">How to Work with Redis JSON <a></a></h3>
<p>In order to use Redis JSON's ability to store our chat history, we need to install <a target="_blank" href="https://github.com/RedisJSON/redisjson-py">rejson</a> provided by Redis labs. </p>
<p>In the terminal, cd into <code>server</code> and install rejson with <code>pip install rejson</code>. Then update your <code>Redis</code> class in <code>server.src.redis.config.py</code> to include the <code>create_rejson_connection</code> method:</p>
<pre><code class="lang-py">
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
<span class="hljs-keyword">import</span> aioredis
<span class="hljs-keyword">from</span> rejson <span class="hljs-keyword">import</span> Client

load_dotenv()

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Redis</span>():</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""initialize  connection """</span>
        self.REDIS_URL = os.environ[<span class="hljs-string">'REDIS_URL'</span>]
        self.REDIS_PASSWORD = os.environ[<span class="hljs-string">'REDIS_PASSWORD'</span>]
        self.REDIS_USER = os.environ[<span class="hljs-string">'REDIS_USER'</span>]
        self.connection_url = <span class="hljs-string">f"redis://<span class="hljs-subst">{self.REDIS_USER}</span>:<span class="hljs-subst">{self.REDIS_PASSWORD}</span>@<span class="hljs-subst">{self.REDIS_URL}</span>"</span>
        self.REDIS_HOST = os.environ[<span class="hljs-string">'REDIS_HOST'</span>]
        self.REDIS_PORT = os.environ[<span class="hljs-string">'REDIS_PORT'</span>]

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_connection</span>(<span class="hljs-params">self</span>):</span>
        self.connection = aioredis.from_url(
            self.connection_url, db=<span class="hljs-number">0</span>)

        <span class="hljs-keyword">return</span> self.connection

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_rejson_connection</span>(<span class="hljs-params">self</span>):</span>
        self.redisJson = Client(host=self.REDIS_HOST,
                                port=self.REDIS_PORT, decode_responses=<span class="hljs-literal">True</span>, username=self.REDIS_USER, password=self.REDIS_PASSWORD)

        <span class="hljs-keyword">return</span> self.redisJson
</code></pre>
<p>We are adding the <code>create_rejson_connection</code> method to connect to Redis with the rejson <code>Client</code>. This gives us the methods to create and manipulate JSON data in Redis, which are not available with aioredis.</p>
<p>Next, in <code>server.src.routes.chat.py</code> we can update the <code>/token</code> endpoint to create a new <code>Chat</code> instance and store the session data in Redis JSON like so:</p>
<pre><code class="lang-py"><span class="hljs-meta">@chat.post("/token")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">token_generator</span>(<span class="hljs-params">name: str, request: Request</span>):</span>
    token = str(uuid.uuid4())

    <span class="hljs-keyword">if</span> name == <span class="hljs-string">""</span>:
        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">400</span>, detail={
            <span class="hljs-string">"loc"</span>: <span class="hljs-string">"name"</span>,  <span class="hljs-string">"msg"</span>: <span class="hljs-string">"Enter a valid name"</span>})

    <span class="hljs-comment"># Create new chat session</span>
    json_client = redis.create_rejson_connection()

    chat_session = Chat(
        token=token,
        messages=[],
        name=name
    )

    <span class="hljs-comment"># Store chat session in redis JSON with the token as key</span>
    json_client.jsonset(str(token), Path.rootPath(), chat_session.dict())

    <span class="hljs-comment"># Set a timeout for redis data</span>
    redis_client = <span class="hljs-keyword">await</span> redis.create_connection()
    <span class="hljs-keyword">await</span> redis_client.expire(str(token), <span class="hljs-number">3600</span>)


    <span class="hljs-keyword">return</span> chat_session.dict()
</code></pre>
<p>NOTE: Because this is a demo app, I do not want to store the chat data in Redis for too long. So I have added a 60-minute time out on the token using the aioredis client (rejson does not implement timeouts). This means that after 60 minutes, the chat session data will be lost. </p>
<p>This is necessary because we are not authenticating users, and we want to dump the chat data after a defined period. This step is optional, and you don't have to include it.</p>
<p>Next, in Postman, when you send a POST request to create a new token, you will get a structured response like the one below. You can also check Redis Insight to see your chat data stored with the token as a JSON key and the data as a value.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/token-generator-updated.png" alt="Image" width="600" height="400" loading="lazy">
<em>Token Generator Updated</em></p>
<h3 id="heading-how-to-update-the-token-dependency">How to Update the Token Dependency <a></a></h3>
<p>Now that we have a token being generated and stored, this is a good time to update the <code>get_token</code> dependency in our <code>/chat</code> WebSocket. We do this to check for a valid token before starting the chat session. </p>
<p>In <code>server.src.socket.utils.py</code> update the <code>get_token</code> function to check if the token exists in the Redis instance. If it does then we return the token, which means that the socket connection is valid. If it doesn't exist, we close the connection. </p>
<p>The token created by <code>/token</code> will cease to exist after 60 minutes. So we can have some simple logic on the frontend to redirect the user to generate a new token if an error response is generated while trying to start a chat.</p>
<pre><code class="lang-py">
<span class="hljs-keyword">from</span> ..redis.config <span class="hljs-keyword">import</span> Redis

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_token</span>(<span class="hljs-params">
    websocket: WebSocket,
    token: Optional[str] = Query(<span class="hljs-params">None</span>),
</span>):</span>

    <span class="hljs-keyword">if</span> token <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">or</span> token == <span class="hljs-string">""</span>:
        <span class="hljs-keyword">await</span> websocket.close(code=status.WS_1008_POLICY_VIOLATION)

    redis_client = <span class="hljs-keyword">await</span> redis.create_connection()
    isexists = <span class="hljs-keyword">await</span> redis_client.exists(token)

    <span class="hljs-keyword">if</span> isexists == <span class="hljs-number">1</span>:
        <span class="hljs-keyword">return</span> token
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">await</span> websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason=<span class="hljs-string">"Session not authenticated or expired token"</span>)
</code></pre>
<p>To test the dependency, connect to the chat session with the random token we have been using, and you should get a 403 error. (Note that you have to manually delete the token in Redis Insight.) </p>
<p>Now copy the token generated when you sent the post request to the <code>/token</code> endpoint (or create a new request) and paste it as the value to the token query parameter required by the <code>/chat</code> WebSocket. Then connect. You should get a successful connection.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/chat-session-with-token.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chat Session with Token</em></p>
<p>Bringing it all together, your chat.py should look like the below.</p>
<pre><code class="lang-py">
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> APIRouter, FastAPI, WebSocket, WebSocketDisconnect, Request, Depends
<span class="hljs-keyword">import</span> uuid
<span class="hljs-keyword">from</span> ..socket.connection <span class="hljs-keyword">import</span> ConnectionManager
<span class="hljs-keyword">from</span> ..socket.utils <span class="hljs-keyword">import</span> get_token
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">from</span> ..redis.producer <span class="hljs-keyword">import</span> Producer
<span class="hljs-keyword">from</span> ..redis.config <span class="hljs-keyword">import</span> Redis
<span class="hljs-keyword">from</span> ..schema.chat <span class="hljs-keyword">import</span> Chat
<span class="hljs-keyword">from</span> rejson <span class="hljs-keyword">import</span> Path

chat = APIRouter()
manager = ConnectionManager()
redis = Redis()


<span class="hljs-comment"># @route   POST /token</span>
<span class="hljs-comment"># @desc    Route to generate chat token</span>
<span class="hljs-comment"># @access  Public</span>


<span class="hljs-meta">@chat.post("/token")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">token_generator</span>(<span class="hljs-params">name: str, request: Request</span>):</span>
    token = str(uuid.uuid4())

    <span class="hljs-keyword">if</span> name == <span class="hljs-string">""</span>:
        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">400</span>, detail={
            <span class="hljs-string">"loc"</span>: <span class="hljs-string">"name"</span>,  <span class="hljs-string">"msg"</span>: <span class="hljs-string">"Enter a valid name"</span>})

    <span class="hljs-comment"># Create nee chat session</span>
    json_client = redis.create_rejson_connection()
    chat_session = Chat(
        token=token,
        messages=[],
        name=name
    )

    print(chat_session.dict())

    <span class="hljs-comment"># Store chat session in redis JSON with the token as key</span>
    json_client.jsonset(str(token), Path.rootPath(), chat_session.dict())

    <span class="hljs-comment"># Set a timeout for redis data</span>
    redis_client = <span class="hljs-keyword">await</span> redis.create_connection()
    <span class="hljs-keyword">await</span> redis_client.expire(str(token), <span class="hljs-number">3600</span>)

    <span class="hljs-keyword">return</span> chat_session.dict()


<span class="hljs-comment"># @route   POST /refresh_token</span>
<span class="hljs-comment"># @desc    Route to refresh token</span>
<span class="hljs-comment"># @access  Public</span>


<span class="hljs-meta">@chat.post("/refresh_token")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">refresh_token</span>(<span class="hljs-params">request: Request</span>):</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>


<span class="hljs-comment"># @route   Websocket /chat</span>
<span class="hljs-comment"># @desc    Socket for chat bot</span>
<span class="hljs-comment"># @access  Public</span>

<span class="hljs-meta">@chat.websocket("/chat")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">websocket_endpoint</span>(<span class="hljs-params">websocket: WebSocket, token: str = Depends(<span class="hljs-params">get_token</span>)</span>):</span>
    <span class="hljs-keyword">await</span> manager.connect(websocket)
    redis_client = <span class="hljs-keyword">await</span> redis.create_connection()
    producer = Producer(redis_client)
    json_client = redis.create_rejson_connection()

    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            data = <span class="hljs-keyword">await</span> websocket.receive_text()
            stream_data = {}
            stream_data[token] = data
            <span class="hljs-keyword">await</span> producer.add_to_stream(stream_data, <span class="hljs-string">"message_channel"</span>)
            <span class="hljs-keyword">await</span> manager.send_personal_message(<span class="hljs-string">f"Response: Simulating response from the GPT service"</span>, websocket)

    <span class="hljs-keyword">except</span> WebSocketDisconnect:
        manager.disconnect(websocket)
</code></pre>
<p>Well done on reaching it this far! In the next section, we will focus on communicating with the AI model and handling the data transfer between client, server, worker, and the external API.</p>
<h2 id="heading-how-to-add-intelligence-to-chatbots-with-ai-models">How to Add Intelligence to Chatbots with AI Models <a></a></h2>
<p>In this section, we will focus on building a wrapper to communicate with the transformer model, send prompts from a user to the API in a conversational format, and receive and transform responses for our chat application.</p>
<h3 id="heading-how-to-get-started-with-huggingface">How to Get Started with Huggingface <a></a></h3>
<p>We will not be building or deploying any language models on Hugginface. Instead, we'll focus on using Huggingface's accelerated inference API to connect to pre-trained models. </p>
<p>The model we will be using is the <a target="_blank" href="https://huggingface.co/EleutherAI/gpt-j-6B">GPT-J-6B Model provided by EleutherAI</a>. It's a generative language model which was trained with 6 Billion parameters. </p>
<p>Huggingface provides us with an on-demand limited API to connect with this model pretty much free of charge.</p>
<p>To get started with Huggingface, <a target="_blank" href="https://huggingface.co/pricing">Create a free account</a>. In your settings, <a target="_blank" href="https://huggingface.co/settings/tokens">generate a new access token</a>. For up to 30k tokens, Huggingface provides access to the inference API for free. </p>
<p>You can <a target="_blank" href="https://api-inference.huggingface.co/dashboard/usage">Monitor your API usage here</a>. Make sure you keep this token safe and don't expose it publicly.</p>
<p>Note: We will use HTTP connections to communicate with the API because we are using a free account. But the PRO Huggingface account supports streaming with WebSockets <a target="_blank" href="https://huggingface.co/docs/api-inference/parallelism">see parallelism and batch jobs</a>. </p>
<p>This can help significantly improve response times between the model and our chat application, and I'll hopefully cover this method in a follow-up article.</p>
<h3 id="heading-how-to-interact-with-the-language-model">How to Interact with the Language Model <a></a></h3>
<p>First, we add the Huggingface connection credentials to the .env file within our worker directory.</p>
<pre><code class="lang-txt">export HUGGINFACE_INFERENCE_TOKEN=&lt;HUGGINGFACE ACCESS TOKEN&gt;
export MODEL_URL=https://api-inference.huggingface.co/models/EleutherAI/gpt-j-6B
</code></pre>
<p>Next, in <code>worker.src</code> create a folder named <code>model</code> then add a file <code>gptj.py</code>. Then add the GPT class below:</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
<span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">import</span> json

load_dotenv()

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GPT</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.url = os.environ.get(<span class="hljs-string">'MODEL_URL'</span>)
        self.headers = {
            <span class="hljs-string">"Authorization"</span>: <span class="hljs-string">f"Bearer <span class="hljs-subst">{os.environ.get(<span class="hljs-string">'HUGGINFACE_INFERENCE_TOKEN'</span>)}</span>"</span>}
        self.payload = {
            <span class="hljs-string">"inputs"</span>: <span class="hljs-string">""</span>,
            <span class="hljs-string">"parameters"</span>: {
                <span class="hljs-string">"return_full_text"</span>: <span class="hljs-literal">False</span>,
                <span class="hljs-string">"use_cache"</span>: <span class="hljs-literal">True</span>,
                <span class="hljs-string">"max_new_tokens"</span>: <span class="hljs-number">25</span>
            }

        }

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">query</span>(<span class="hljs-params">self, input: str</span>) -&gt; list:</span>
        self.payload[<span class="hljs-string">"inputs"</span>] = input
        data = json.dumps(self.payload)
        response = requests.request(
            <span class="hljs-string">"POST"</span>, self.url, headers=self.headers, data=data)
        print(json.loads(response.content.decode(<span class="hljs-string">"utf-8"</span>)))
        <span class="hljs-keyword">return</span> json.loads(response.content.decode(<span class="hljs-string">"utf-8"</span>))

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    GPT().query(<span class="hljs-string">"Will artificial intelligence help humanity conquer the universe?"</span>)
</code></pre>
<p>The <code>GPT</code> class is initialized with the Huggingface model <code>url</code>, authentication <code>header</code>, and predefined <code>payload</code>. But the payload input is a dynamic field that is provided by the <code>query</code> method and updated before we send a request to the Huggingface endpoint.</p>
<p>Finally, we test this by running the query method on an instance of the GPT class directly. In the terminal, run <code>python src/model/gptj.py</code>, and you should get a response like this (just keep in mind that your response will certainly be different from this):</p>
<pre><code class="lang-bash">[{<span class="hljs-string">'generated_text'</span>: <span class="hljs-string">' (AI) could solve all the problems on this planet? I am of the opinion that in the short term artificial intelligence is much better than human beings, but in the long and distant future human beings will surpass artificial intelligence.\n\nIn the distant'</span>}]
</code></pre>
<p>Next, we add some tweaking to the input to make the interaction with the model more conversational by changing the format of the input. </p>
<p>Update the <code>GPT</code> class like so:</p>
<pre><code class="lang-py">
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GPT</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.url = os.environ.get(<span class="hljs-string">'MODEL_URL'</span>)
        self.headers = {
            <span class="hljs-string">"Authorization"</span>: <span class="hljs-string">f"Bearer <span class="hljs-subst">{os.environ.get(<span class="hljs-string">'HUGGINFACE_INFERENCE_TOKEN'</span>)}</span>"</span>}
        self.payload = {
            <span class="hljs-string">"inputs"</span>: <span class="hljs-string">""</span>,
            <span class="hljs-string">"parameters"</span>: {
                <span class="hljs-string">"return_full_text"</span>: <span class="hljs-literal">False</span>,
                <span class="hljs-string">"use_cache"</span>: <span class="hljs-literal">False</span>,
                <span class="hljs-string">"max_new_tokens"</span>: <span class="hljs-number">25</span>
            }

        }

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">query</span>(<span class="hljs-params">self, input: str</span>) -&gt; list:</span>
        self.payload[<span class="hljs-string">"inputs"</span>] = <span class="hljs-string">f"Human: <span class="hljs-subst">{input}</span> Bot:"</span>
        data = json.dumps(self.payload)
        response = requests.request(
            <span class="hljs-string">"POST"</span>, self.url, headers=self.headers, data=data)
        data = json.loads(response.content.decode(<span class="hljs-string">"utf-8"</span>))
        text = data[<span class="hljs-number">0</span>][<span class="hljs-string">'generated_text'</span>]
        res = str(text.split(<span class="hljs-string">"Human:"</span>)[<span class="hljs-number">0</span>]).strip(<span class="hljs-string">"\n"</span>).strip()
        <span class="hljs-keyword">return</span> res


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    GPT().query(<span class="hljs-string">"Will artificial intelligence help humanity conquer the universe?"</span>)
</code></pre>
<p>We updated the input with a string literal <code>f"Human: {input} Bot:"</code>. The human input is placed in the string and the Bot provides a response. This input format turns the GPT-J6B into a conversational model. Other changes you may notice include</p>
<ul>
<li>use_cache: you can make this False if you want the model to create a new response when the input is the same. I suggest leaving this as True in production to prevent exhausting your free tokens if a user just keeps spamming the bot with the same message. Using cache does not actually load a new response from the model.</li>
<li>return_full_text: is False, as we do not need to return the input – we already have it. When we get a response, we strip the "Bot:" and leading/trailing spaces from the response and return just the response text.</li>
</ul>
<h3 id="heading-how-to-simulate-short-term-memory-for-the-ai-model">How to Simulate Short-term Memory for the AI Model <a></a></h3>
<p>For every new input we send to the model, there is no way for the model to remember the conversation history. This is important if we want to hold context in the conversation. </p>
<p>But remember that as the number of tokens we send to the model increases, the processing gets more expensive, and the response time is also longer. </p>
<p>So we will need to find a way to retrieve short-term history and send it to the model. We will also need to figure out a sweet spot - how much historical data do we want to retrieve and send to the model?</p>
<p>To handle chat history, we need to fall back to our JSON database. We'll use the <code>token</code> to get the last chat data, and then when we get the response, append the response to the JSON database.</p>
<p>Update <code>worker.src.redis.config.py</code> to include the <code>create_rejson_connection</code> method. Also, update the .env file with the authentication data, and ensure rejson is installed.</p>
<p>Your <code>worker.src.redis.config.py</code> should look like this:</p>
<pre><code class="lang-py">
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
<span class="hljs-keyword">import</span> aioredis
<span class="hljs-keyword">from</span> rejson <span class="hljs-keyword">import</span> Client


load_dotenv()


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Redis</span>():</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""initialize  connection """</span>
        self.REDIS_URL = os.environ[<span class="hljs-string">'REDIS_URL'</span>]
        self.REDIS_PASSWORD = os.environ[<span class="hljs-string">'REDIS_PASSWORD'</span>]
        self.REDIS_USER = os.environ[<span class="hljs-string">'REDIS_USER'</span>]
        self.connection_url = <span class="hljs-string">f"redis://<span class="hljs-subst">{self.REDIS_USER}</span>:<span class="hljs-subst">{self.REDIS_PASSWORD}</span>@<span class="hljs-subst">{self.REDIS_URL}</span>"</span>
        self.REDIS_HOST = os.environ[<span class="hljs-string">'REDIS_HOST'</span>]
        self.REDIS_PORT = os.environ[<span class="hljs-string">'REDIS_PORT'</span>]

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_connection</span>(<span class="hljs-params">self</span>):</span>
        self.connection = aioredis.from_url(
            self.connection_url, db=<span class="hljs-number">0</span>)

        <span class="hljs-keyword">return</span> self.connection

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_rejson_connection</span>(<span class="hljs-params">self</span>):</span>
        self.redisJson = Client(host=self.REDIS_HOST,
                                port=self.REDIS_PORT, decode_responses=<span class="hljs-literal">True</span>, username=self.REDIS_USER, password=self.REDIS_PASSWORD)

        <span class="hljs-keyword">return</span> self.redisJson
</code></pre>
<p>While your .env file should look like this:</p>
<pre><code class="lang-txt">export REDIS_URL=&lt;REDIS URL PROVIDED IN REDIS CLOUD&gt;
export REDIS_USER=&lt;REDIS USER IN REDIS CLOUD&gt;
export REDIS_PASSWORD=&lt;DATABASE PASSWORD IN REDIS CLOUD&gt;
export REDIS_HOST=&lt;REDIS HOST IN REDIS CLOUD&gt;
export REDIS_PORT=&lt;REDIS PORT IN REDIS CLOUD&gt;
export HUGGINFACE_INFERENCE_TOKEN=&lt;HUGGINGFACE ACCESS TOKEN&gt;
export MODEL_URL=https://api-inference.huggingface.co/models/EleutherAI/gpt-j-6B
</code></pre>
<p>Next, in <code>worker.src.redis</code> create a new file named <code>cache.py</code> and add the code below:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> .config <span class="hljs-keyword">import</span> Redis
<span class="hljs-keyword">from</span> rejson <span class="hljs-keyword">import</span> Path

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cache</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, json_client</span>):</span>
        self.json_client = json_client

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_chat_history</span>(<span class="hljs-params">self, token: str</span>):</span>
        data = self.json_client.jsonget(
            str(token), Path.rootPath())

        <span class="hljs-keyword">return</span> data
</code></pre>
<p>The cache is initialized with a rejson client, and the method <code>get_chat_history</code> takes in a token to get the chat history for that token, from Redis. Make sure you import the Path object from rejson.</p>
<p>Next, update the <code>worker.main.py</code> with the code below:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> src.redis.config <span class="hljs-keyword">import</span> Redis
<span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">from</span> src.model.gptj <span class="hljs-keyword">import</span> GPT
<span class="hljs-keyword">from</span> src.redis.cache <span class="hljs-keyword">import</span> Cache

redis = Redis()

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    json_client = redis.create_rejson_connection()
    data = <span class="hljs-keyword">await</span> Cache(json_client).get_chat_history(token=<span class="hljs-string">"18196e23-763b-4808-ae84-064348a0daff"</span>)
    print(data)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    asyncio.run(main())
</code></pre>
<p>I have hard-coded a sample token created from previous tests in Postman. If you don't have a token created, just send a new request to <code>/token</code> and copy the token, then run <code>python main.py</code> in the terminal. You should see the data in the terminal like so:</p>
<pre><code class="lang-bash">{<span class="hljs-string">'token'</span>: <span class="hljs-string">'18196e23-763b-4808-ae84-064348a0daff'</span>, <span class="hljs-string">'messages'</span>: [], <span class="hljs-string">'name'</span>: <span class="hljs-string">'Stephen'</span>, <span class="hljs-string">'session_start'</span>: <span class="hljs-string">'2022-07-16 13:20:01.092109'</span>}
</code></pre>
<p>Next, we need to add an <code>add_message_to_cache</code> method to our <code>Cache</code> class that adds messages to Redis for a specific token.</p>
<pre><code class="lang-py">
  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_message_to_cache</span>(<span class="hljs-params">self, token: str, message_data: dict</span>):</span>
      self.json_client.jsonarrappend(
          str(token), Path(<span class="hljs-string">'.messages'</span>), message_data)
</code></pre>
<p>The <code>jsonarrappend</code> method provided by rejson appends the new message to the message array. </p>
<p>Note that to access the message array, we need to provide <code>.messages</code> as an argument to the Path. If your message data has a different/nested structure, just provide the path to the array you want to append the new data to.</p>
<p>To test this method, update the main function in the main.py file with the code below:</p>
<pre><code class="lang-py"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    json_client = redis.create_rejson_connection()

    <span class="hljs-keyword">await</span> Cache(json_client).add_message_to_cache(token=<span class="hljs-string">"18196e23-763b-4808-ae84-064348a0daff"</span>, message_data={
        <span class="hljs-string">"id"</span>: <span class="hljs-string">"1"</span>,
        <span class="hljs-string">"msg"</span>: <span class="hljs-string">"Hello"</span>,
        <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2022-07-16 13:20:01.092109"</span>
    })

    data = <span class="hljs-keyword">await</span> Cache(json_client).get_chat_history(token=<span class="hljs-string">"18196e23-763b-4808-ae84-064348a0daff"</span>)
    print(data)
</code></pre>
<p>We are sending a hard-coded message to the cache, and getting the chat history from the cache. When you run <code>python main.py</code> in the terminal within the worker directory, you should get something like this printed in the terminal, with the message added to the message array.</p>
<pre><code class="lang-bash">{<span class="hljs-string">'token'</span>: <span class="hljs-string">'18196e23-763b-4808-ae84-064348a0daff'</span>, <span class="hljs-string">'messages'</span>: [{<span class="hljs-string">'id'</span>: <span class="hljs-string">'1'</span>, <span class="hljs-string">'msg'</span>: <span class="hljs-string">'Hello'</span>, <span class="hljs-string">'timestamp'</span>: <span class="hljs-string">'2022-07-16 13:20:01.092109'</span>}], <span class="hljs-string">'name'</span>: <span class="hljs-string">'Stephen'</span>, <span class="hljs-string">'session_start'</span>: <span class="hljs-string">'2022-07-16 13:20:01.092109'</span>}
</code></pre>
<p>Finally, we need to update the main function to send the message data to the GPT model, and update the input with the <strong>last 4</strong> messages sent between the client and the model. </p>
<p>First let's update our <code>add_message_to_cache</code> function with a new argument "source" that will tell us if the message is a human or bot. We can then use this arg to add the "Human:" or "Bot:" tags to the data before storing it in the cache.</p>
<p>Update the <code>add_message_to_cache</code> method in the Cache class like so:</p>
<pre><code class="lang-py">  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_message_to_cache</span>(<span class="hljs-params">self, token: str, source: str, message_data: dict</span>):</span>
      <span class="hljs-keyword">if</span> source == <span class="hljs-string">"human"</span>:
          message_data[<span class="hljs-string">'msg'</span>] = <span class="hljs-string">"Human: "</span> + (message_data[<span class="hljs-string">'msg'</span>])
      <span class="hljs-keyword">elif</span> source == <span class="hljs-string">"bot"</span>:
          message_data[<span class="hljs-string">'msg'</span>] = <span class="hljs-string">"Bot: "</span> + (message_data[<span class="hljs-string">'msg'</span>])

      self.json_client.jsonarrappend(
          str(token), Path(<span class="hljs-string">'.messages'</span>), message_data)
</code></pre>
<p>Then update the main function in main.py in the worker directory, and run <code>python main.py</code> to see the new results in the Redis database.</p>
<pre><code class="lang-py"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    json_client = redis.create_rejson_connection()

    <span class="hljs-keyword">await</span> Cache(json_client).add_message_to_cache(token=<span class="hljs-string">"18196e23-763b-4808-ae84-064348a0daff"</span>, source=<span class="hljs-string">"human"</span>, message_data={
        <span class="hljs-string">"id"</span>: <span class="hljs-string">"1"</span>,
        <span class="hljs-string">"msg"</span>: <span class="hljs-string">"Hello"</span>,
        <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2022-07-16 13:20:01.092109"</span>
    })

    data = <span class="hljs-keyword">await</span> Cache(json_client).get_chat_history(token=<span class="hljs-string">"18196e23-763b-4808-ae84-064348a0daff"</span>)
    print(data)
</code></pre>
<p>Next, we need to update the main function to add new messages to the cache, read the previous 4 messages from the cache, and then make an API call to the model using the query method. It'll have a payload consisting of a composite string of the last 4 messages.</p>
<p>You can always tune the number of messages in the history you want to extract, but I think 4 messages is a pretty good number for a demo.</p>
<p>In <code>worker.src</code>, create a new folder schema. Then create a new file named <code>chat.py</code> and paste our message schema in chat.py like so:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> List, Optional
<span class="hljs-keyword">import</span> uuid


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Message</span>(<span class="hljs-params">BaseModel</span>):</span>
    id = str(uuid.uuid4())
    msg: str
    timestamp = str(datetime.now())
</code></pre>
<p>Next, update the main.py file like below:</p>
<pre><code class="lang-py"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>

    json_client = redis.create_rejson_connection()

    <span class="hljs-keyword">await</span> Cache(json_client).add_message_to_cache(token=<span class="hljs-string">"18196e23-763b-4808-ae84-064348a0daff"</span>, source=<span class="hljs-string">"human"</span>, message_data={
        <span class="hljs-string">"id"</span>: <span class="hljs-string">"3"</span>,
        <span class="hljs-string">"msg"</span>: <span class="hljs-string">"I would like to go to the moon to, would you take me?"</span>,
        <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2022-07-16 13:20:01.092109"</span>
    })

    data = <span class="hljs-keyword">await</span> Cache(json_client).get_chat_history(token=<span class="hljs-string">"18196e23-763b-4808-ae84-064348a0daff"</span>)

    print(data)

    message_data = data[<span class="hljs-string">'messages'</span>][<span class="hljs-number">-4</span>:]

    input = [<span class="hljs-string">""</span> + i[<span class="hljs-string">'msg'</span>] <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> message_data]
    input = <span class="hljs-string">" "</span>.join(input)

    res = GPT().query(input=input)

    msg = Message(
        msg=res
    )

    print(msg)
    <span class="hljs-keyword">await</span> Cache(json_client).add_message_to_cache(token=<span class="hljs-string">"18196e23-763b-4808-ae84-064348a0daff"</span>, source=<span class="hljs-string">"bot"</span>, message_data=msg.dict())
</code></pre>
<p>In the code above, we add new message data to the cache. This message will ultimately come from the message queue. Next we get the chat history from the cache, which will now include the most recent data we added. </p>
<p>Note that we are using the same hard-coded token to add to the cache and get from the cache, temporarily just to test this out. </p>
<p>Next, we trim off the cache data and extract only the last 4 items. Then we consolidate the input data by extracting the msg in a list and join it to an empty string. </p>
<p>Finally, we create a new Message instance for the bot response and add the response to the cache specifying the source as "bot"</p>
<p>Next, run <code>python main.py</code> a couple of times, changing the human message and id as desired with each run. You should have a full conversation input and output with the model. </p>
<p>Open Redis Insight and you should have something similar to the below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/conversation-chat.png" alt="Image" width="600" height="400" loading="lazy">
<em>Conversational Chat</em></p>
<h3 id="heading-stream-consumer-and-real-time-data-pull-from-the-message-queue">Stream Consumer and Real-time Data Pull from the Message Queue <a></a></h3>
<p>Next, we want to create a consumer and update our <code>worker.main.py</code> to connect to the message queue. We want it to pull the token data in real-time, as we are currently hard-coding the tokens and message inputs.</p>
<p>In <code>worker.src.redis</code> create a new file named <code>stream.py</code>. Add a <code>StreamConsumer</code> class with the code below:</p>
<pre><code class="lang-py"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StreamConsumer</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, redis_client</span>):</span>
        self.redis_client = redis_client

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">consume_stream</span>(<span class="hljs-params">self, count: int, block: int,  stream_channel</span>):</span>

        response = <span class="hljs-keyword">await</span> self.redis_client.xread(
            streams={stream_channel:  <span class="hljs-string">'0-0'</span>}, count=count, block=block)

        <span class="hljs-keyword">return</span> response

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">delete_message</span>(<span class="hljs-params">self, stream_channel, message_id</span>):</span>
        <span class="hljs-keyword">await</span> self.redis_client.xdel(stream_channel, message_id)
</code></pre>
<p>The <code>StreamConsumer</code> class is initialized with a Redis client. The <code>consume_stream</code> method pulls a new message from the queue from the message channel, using the <code>xread</code> method provided by aioredis.</p>
<p>Next, update the <code>worker.main.py</code> file with a while loop to keep the connection to the message channel alive, like so:</p>
<pre><code class="lang-py">
<span class="hljs-keyword">from</span> src.redis.config <span class="hljs-keyword">import</span> Redis
<span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">from</span> src.model.gptj <span class="hljs-keyword">import</span> GPT
<span class="hljs-keyword">from</span> src.redis.cache <span class="hljs-keyword">import</span> Cache
<span class="hljs-keyword">from</span> src.redis.config <span class="hljs-keyword">import</span> Redis
<span class="hljs-keyword">from</span> src.redis.stream <span class="hljs-keyword">import</span> StreamConsumer
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> src.schema.chat <span class="hljs-keyword">import</span> Message


redis = Redis()


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    json_client = redis.create_rejson_connection()
    redis_client = <span class="hljs-keyword">await</span> redis.create_connection()
    consumer = StreamConsumer(redis_client)
    cache = Cache(json_client)

    print(<span class="hljs-string">"Stream consumer started"</span>)
    print(<span class="hljs-string">"Stream waiting for new messages"</span>)

    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
        response = <span class="hljs-keyword">await</span> consumer.consume_stream(stream_channel=<span class="hljs-string">"message_channel"</span>, count=<span class="hljs-number">1</span>, block=<span class="hljs-number">0</span>)

        <span class="hljs-keyword">if</span> response:
            <span class="hljs-keyword">for</span> stream, messages <span class="hljs-keyword">in</span> response:
                <span class="hljs-comment"># Get message from stream, and extract token, message data and message id</span>
                <span class="hljs-keyword">for</span> message <span class="hljs-keyword">in</span> messages:
                    message_id = message[<span class="hljs-number">0</span>]
                    token = [k.decode(<span class="hljs-string">'utf-8'</span>)
                             <span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> message[<span class="hljs-number">1</span>].items()][<span class="hljs-number">0</span>]
                    message = [v.decode(<span class="hljs-string">'utf-8'</span>)
                               <span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> message[<span class="hljs-number">1</span>].items()][<span class="hljs-number">0</span>]
                    print(token)

                    <span class="hljs-comment"># Create a new message instance and add to cache, specifying the source as human</span>
                    msg = Message(msg=message)

                    <span class="hljs-keyword">await</span> cache.add_message_to_cache(token=token, source=<span class="hljs-string">"human"</span>, message_data=msg.dict())

                    <span class="hljs-comment"># Get chat history from cache</span>
                    data = <span class="hljs-keyword">await</span> cache.get_chat_history(token=token)

                    <span class="hljs-comment"># Clean message input and send to query</span>
                    message_data = data[<span class="hljs-string">'messages'</span>][<span class="hljs-number">-4</span>:]

                    input = [<span class="hljs-string">""</span> + i[<span class="hljs-string">'msg'</span>] <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> message_data]
                    input = <span class="hljs-string">" "</span>.join(input)

                    res = GPT().query(input=input)

                    msg = Message(
                        msg=res
                    )

                    print(msg)

                    <span class="hljs-keyword">await</span> cache.add_message_to_cache(token=token, source=<span class="hljs-string">"bot"</span>, message_data=msg.dict())

                <span class="hljs-comment"># Delete messaage from queue after it has been processed</span>

                <span class="hljs-keyword">await</span> consumer.delete_message(stream_channel=<span class="hljs-string">"message_channel"</span>, message_id=message_id)


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    asyncio.run(main())
</code></pre>
<p>This is quite the update, so let's take it step by step:</p>
<p>We use a <code>while True</code> loop so that the worker can be online listening to messages from the queue. </p>
<p>Next, we await new messages from the message_channel by calling our <code>consume_stream</code> method. If we have a message in the queue, we extract the message_id, token, and message. Then we create a new instance of the Message class, add the message to the cache, and then get the last 4 messages. We set it as input to the GPT model <code>query</code> method. </p>
<p>Once we get a response, we then add the response to the cache using the <code>add_message_to_cache</code> method, then delete the message from the queue.</p>
<h3 id="heading-how-to-update-the-chat-client-with-the-ai-response">How to Update the Chat Client with the AI Response <a></a></h3>
<p>So far, we are sending a chat message from the client to the message_channel (which is received by the worker that queries the AI model) to get a response. </p>
<p>Next, we need to send this response to the client. As long as the socket connection is still open, the client should be able to receive the response. </p>
<p>If the connection is closed, the client can always get a response from the chat history using the <code>refresh_token</code> endpoint.</p>
<p>In <code>worker.src.redis</code> create a new file named <code>producer.py</code>, and add a <code>Producer</code> class similar to what we had on the chat web server:</p>
<pre><code class="lang-py">
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Producer</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, redis_client</span>):</span>
        self.redis_client = redis_client

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_to_stream</span>(<span class="hljs-params">self,  data: dict, stream_channel</span>) -&gt; bool:</span>
        msg_id = <span class="hljs-keyword">await</span> self.redis_client.xadd(name=stream_channel, id=<span class="hljs-string">"*"</span>, fields=data)
        print(<span class="hljs-string">f"Message id <span class="hljs-subst">{msg_id}</span> added to <span class="hljs-subst">{stream_channel}</span> stream"</span>)
        <span class="hljs-keyword">return</span> msg_id
</code></pre>
<p>Next, in the <code>main.py</code> file, update the main function to initialize the producer, create a stream data, and send the response to a <code>response_channel</code> using the <code>add_to_stream</code> method:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> src.redis.config <span class="hljs-keyword">import</span> Redis
<span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">from</span> src.model.gptj <span class="hljs-keyword">import</span> GPT
<span class="hljs-keyword">from</span> src.redis.cache <span class="hljs-keyword">import</span> Cache
<span class="hljs-keyword">from</span> src.redis.config <span class="hljs-keyword">import</span> Redis
<span class="hljs-keyword">from</span> src.redis.stream <span class="hljs-keyword">import</span> StreamConsumer
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> src.schema.chat <span class="hljs-keyword">import</span> Message
<span class="hljs-keyword">from</span> src.redis.producer <span class="hljs-keyword">import</span> Producer


redis = Redis()


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    json_client = redis.create_rejson_connection()
    redis_client = <span class="hljs-keyword">await</span> redis.create_connection()
    consumer = StreamConsumer(redis_client)
    cache = Cache(json_client)
    producer = Producer(redis_client)

    print(<span class="hljs-string">"Stream consumer started"</span>)
    print(<span class="hljs-string">"Stream waiting for new messages"</span>)

    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
        response = <span class="hljs-keyword">await</span> consumer.consume_stream(stream_channel=<span class="hljs-string">"message_channel"</span>, count=<span class="hljs-number">1</span>, block=<span class="hljs-number">0</span>)

        <span class="hljs-keyword">if</span> response:
            <span class="hljs-keyword">for</span> stream, messages <span class="hljs-keyword">in</span> response:
                <span class="hljs-comment"># Get message from stream, and extract token, message data and message id</span>
                <span class="hljs-keyword">for</span> message <span class="hljs-keyword">in</span> messages:
                    message_id = message[<span class="hljs-number">0</span>]
                    token = [k.decode(<span class="hljs-string">'utf-8'</span>)
                             <span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> message[<span class="hljs-number">1</span>].items()][<span class="hljs-number">0</span>]
                    message = [v.decode(<span class="hljs-string">'utf-8'</span>)
                               <span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> message[<span class="hljs-number">1</span>].items()][<span class="hljs-number">0</span>]

                    <span class="hljs-comment"># Create a new message instance and add to cache, specifying the source as human</span>
                    msg = Message(msg=message)

                    <span class="hljs-keyword">await</span> cache.add_message_to_cache(token=token, source=<span class="hljs-string">"human"</span>, message_data=msg.dict())

                    <span class="hljs-comment"># Get chat history from cache</span>
                    data = <span class="hljs-keyword">await</span> cache.get_chat_history(token=token)

                    <span class="hljs-comment"># Clean message input and send to query</span>
                    message_data = data[<span class="hljs-string">'messages'</span>][<span class="hljs-number">-4</span>:]

                    input = [<span class="hljs-string">""</span> + i[<span class="hljs-string">'msg'</span>] <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> message_data]
                    input = <span class="hljs-string">" "</span>.join(input)

                    res = GPT().query(input=input)

                    msg = Message(
                        msg=res
                    )

                    stream_data = {}
                    stream_data[str(token)] = str(msg.dict())

                    <span class="hljs-keyword">await</span> producer.add_to_stream(stream_data, <span class="hljs-string">"response_channel"</span>)

                    <span class="hljs-keyword">await</span> cache.add_message_to_cache(token=token, source=<span class="hljs-string">"bot"</span>, message_data=msg.dict())

                <span class="hljs-comment"># Delete messaage from queue after it has been processed</span>
                <span class="hljs-keyword">await</span> consumer.delete_message(stream_channel=<span class="hljs-string">"message_channel"</span>, message_id=message_id)


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    asyncio.run(main())
</code></pre>
<p>Next, we need to let the client know when we receive responses from the worker in the <code>/chat</code> socket endpoint. We do this by listening to the response stream. We do not need to include a while loop here as the socket will be listening as long as the connection is open.</p>
<p>Note that we also need to check which client the response is for by adding logic to check if the token connected is equal to the token in the response. Then we delete the message in the response queue once it's been read.</p>
<p>In <code>server.src.redis</code> create a new file named stream.py and add our <code>StreamConsumer</code> class like this:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> .config <span class="hljs-keyword">import</span> Redis

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StreamConsumer</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, redis_client</span>):</span>
        self.redis_client = redis_client

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">consume_stream</span>(<span class="hljs-params">self, count: int, block: int,  stream_channel</span>):</span>
        response = <span class="hljs-keyword">await</span> self.redis_client.xread(
            streams={stream_channel:  <span class="hljs-string">'0-0'</span>}, count=count, block=block)

        <span class="hljs-keyword">return</span> response

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">delete_message</span>(<span class="hljs-params">self, stream_channel, message_id</span>):</span>
        <span class="hljs-keyword">await</span> self.redis_client.xdel(stream_channel, message_id)
</code></pre>
<p>Next, update the <code>/chat</code> socket endpoint like so:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> ..redis.stream <span class="hljs-keyword">import</span> StreamConsumer

<span class="hljs-meta">@chat.websocket("/chat")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">websocket_endpoint</span>(<span class="hljs-params">websocket: WebSocket, token: str = Depends(<span class="hljs-params">get_token</span>)</span>):</span>
    <span class="hljs-keyword">await</span> manager.connect(websocket)
    redis_client = <span class="hljs-keyword">await</span> redis.create_connection()
    producer = Producer(redis_client)
    json_client = redis.create_rejson_connection()
    consumer = StreamConsumer(redis_client)

    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
            data = <span class="hljs-keyword">await</span> websocket.receive_text()
            stream_data = {}
            stream_data[str(token)] = str(data)
            <span class="hljs-keyword">await</span> producer.add_to_stream(stream_data, <span class="hljs-string">"message_channel"</span>)
            response = <span class="hljs-keyword">await</span> consumer.consume_stream(stream_channel=<span class="hljs-string">"response_channel"</span>, block=<span class="hljs-number">0</span>)

            print(response)
            <span class="hljs-keyword">for</span> stream, messages <span class="hljs-keyword">in</span> response:
                <span class="hljs-keyword">for</span> message <span class="hljs-keyword">in</span> messages:
                    response_token = [k.decode(<span class="hljs-string">'utf-8'</span>)
                                      <span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> message[<span class="hljs-number">1</span>].items()][<span class="hljs-number">0</span>]

                    <span class="hljs-keyword">if</span> token == response_token:
                        response_message = [v.decode(<span class="hljs-string">'utf-8'</span>)
                                            <span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> message[<span class="hljs-number">1</span>].items()][<span class="hljs-number">0</span>]

                        print(message[<span class="hljs-number">0</span>].decode(<span class="hljs-string">'utf-8'</span>))
                        print(token)
                        print(response_token)

                        <span class="hljs-keyword">await</span> manager.send_personal_message(response_message, websocket)

                    <span class="hljs-keyword">await</span> consumer.delete_message(stream_channel=<span class="hljs-string">"response_channel"</span>, message_id=message[<span class="hljs-number">0</span>].decode(<span class="hljs-string">'utf-8'</span>))

    <span class="hljs-keyword">except</span> WebSocketDisconnect:
        manager.disconnect(websocket)
</code></pre>
<h3 id="heading-refresh-token">Refresh Token <a></a></h3>
<p>Finally, we need to update the <code>/refresh_token</code> endpoint to get the chat history from the Redis database using our <code>Cache</code> class. </p>
<p>In <code>server.src.redis</code>, add a <code>cache.py</code> file and add the code below:</p>
<pre><code class="lang-py">
<span class="hljs-keyword">from</span> rejson <span class="hljs-keyword">import</span> Path

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cache</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, json_client</span>):</span>
        self.json_client = json_client

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_chat_history</span>(<span class="hljs-params">self, token: str</span>):</span>
        data = self.json_client.jsonget(
            str(token), Path.rootPath())

        <span class="hljs-keyword">return</span> data
</code></pre>
<p>Next, in <code>server.src.routes.chat.py</code> import the <code>Cache</code> class and update the <code>/token</code> endpoint to the below:</p>
<pre><code class="lang-py">
<span class="hljs-keyword">from</span> ..redis.cache <span class="hljs-keyword">import</span> Cache

<span class="hljs-meta">@chat.get("/refresh_token")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">refresh_token</span>(<span class="hljs-params">request: Request, token: str</span>):</span>
    json_client = redis.create_rejson_connection()
    cache = Cache(json_client)
    data = <span class="hljs-keyword">await</span> cache.get_chat_history(token)

    <span class="hljs-keyword">if</span> data == <span class="hljs-literal">None</span>:
        <span class="hljs-keyword">raise</span> HTTPException(
            status_code=<span class="hljs-number">400</span>, detail=<span class="hljs-string">"Session expired or does not exist"</span>)
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> data
</code></pre>
<p>Now, when we send a GET request to the <code>/refresh_token</code> endpoint with any token, the endpoint will fetch the data from the Redis database. </p>
<p>If the token has not timed out, the data will be sent to the user. Or it'll send a 400 response if the token is not found.</p>
<h3 id="heading-how-to-test-the-chat-with-multiple-clients-in-postman">How to Test the Chat with multiple Clients in Postman <a></a></h3>
<p>Finally, we will test the chat system by creating multiple chat sessions in Postman, connecting multiple clients in Postman, and chatting with the bot on the clients. </p>
<p>Lastly, we will try to get the chat history for the clients and hopefully get a proper response.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/chat-static.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-recap">Recap <a></a></h2>
<p>Let's have a quick recap as to what we have achieved with our chat system. The chat client creates a token for each chat session with a client. This token is used to identify each client, and each message sent by clients connected to or web server is queued in a Redis channel (message_chanel), identified by the token.</p>
<p>Our worker environment reads from this channel. It does not have any clue who the client is (except that it's a unique token) and uses the message in the queue to send requests to the Huggingface inference API.</p>
<p>When it gets a response, the response is added to a response channel and the chat history is updated. The client listening to the response_channel immediately sends the response to the client once it receives a response with its token.</p>
<p>If the socket is still open, this response is sent. If the socket is closed, we are certain that the response is preserved because the response is added to the chat history. The client can get the history, even if a page refresh happens or in the event of a lost connection.</p>
<p>Congratulations on getting this far! You have been able to build a working chat system. </p>
<p>In follow-up articles, I will focus on building a chat user interface for the client, creating unit and functional tests, fine-tuning our worker environment for faster response time with WebSockets and asynchronous requests, and ultimately deploying the chat application on AWS.</p>
<p>This Article is part of a series on building full-stack intelligent chatbots with tools like Python, React, Huggingface, Redis, and so on. You can follow the full series on my blog: <a target="_blank" href="https://blog.stephensanwo.dev/series/build-ai-chatbot">blog.stephensanwo.dev - AI ChatBot Series</a>**</p>
<p><strong>You can download the full repository on <a target="_blank" href="https://github.com/stephensanwo/fullstack-ai-chatbot">My Github Repository</a></strong></p>
<p>I wrote this tutorial in collaboration with Redis. Need help getting started with Redis? Try the following resources:</p>
<ul>
<li><a target="_blank" href="https://redis.info/3NBGJRT">Try Redis Cloud free of charge</a></li>
<li><a target="_blank" href="https://redis.info/3Ga9YII">Watch this video on the benefits of Redis Cloud over other Redis providers</a></li>
<li><a target="_blank" href="https://redis.info/3LC4GqB">Redis Developer Hub - tools, guides, and tutorials about Redis</a></li>
<li><a target="_blank" href="https://redis.info/3wMR7PR">RedisInsight Desktop GUI</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Rate Limiter using Bucket4J and Redis ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial we will learn how to implement rate limiting in a scaled service.We will use the Bucket4J library to implement it and we will use Redis as a distributed cache. Why Use Rate Limiting? Let's get started with some basics to make sure we... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/rate-limiting-with-bucket4j-and-redis/</link>
                <guid isPermaLink="false">66c37625f278f15f931a342a</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abhinav Pandey ]]>
                </dc:creator>
                <pubDate>Fri, 01 Apr 2022 19:05:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/Rate-Limiter-with-Bucket4J-and-Redis.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial we will learn how to implement rate limiting in a scaled service.<br>We will use the <a target="_blank" href="https://github.com/vladimir-bukhtoyarov/bucket4j">Bucket4J</a> library to implement it and we will use <a target="_blank" href="https://redis.io/">Redis</a> as a distributed cache.</p>
<h2 id="heading-why-use-rate-limiting">Why Use Rate Limiting?</h2>
<p>Let's get started with some basics to make sure we understand the need for rate limiting and introduce the tools we'll be using in this tutorial.</p>
<h3 id="heading-problem-with-unlimited-rates">Problem with Unlimited Rates</h3>
<p>If a public API like the Twitter API allowed its users to make an unlimited number of requests per hour, it could lead to:</p>
<ul>
<li>resource exhaustion</li>
<li>decreasing quality of the service</li>
<li>denial of service attacks</li>
</ul>
<p>This might result in a situation where the <strong>service is unavailable or slow</strong>. It could also lead to more <strong>unexpected costs</strong> being incurred by the service.</p>
<h3 id="heading-how-rate-limiting-helps">How Rate Limiting Helps</h3>
<p>Firstly, rate-limiting can prevent denial of service attacks. When coupled with a deduplication mechanism or API keys, rate limiting can also help prevent distributed denial of service attacks.</p>
<p>Secondly, it helps in estimating traffic. This is very important for public APIs. This can also be coupled with automated scripts to monitor and scale the service.</p>
<p>And thirdly, you can use it to implement tier-based pricing. This type of pricing model means that users can pay for a higher rate of requests. The Twitter API is an example of this.</p>
<h3 id="heading-the-token-bucket-algorithm">The Token Bucket Algorithm</h3>
<p>Token Bucket is an algorithm that you can use to implement rate limiting. In short, it works as follows:</p>
<ol>
<li>A bucket is created with a certain capacity (number of tokens).</li>
<li>When a request comes in, the bucket is checked. If there is enough capacity, the request is allowed to proceed. Otherwise, the request is denied.</li>
<li>When a request is allowed, the capacity is reduced.</li>
<li>After a certain amount of time, the capacity is replenished.</li>
</ol>
<h3 id="heading-how-to-implement-token-bucket-in-a-distributed-system">How to Implement Token Bucket in a Distributed System</h3>
<p>To implement the token bucket algorithm in a distributed system, we need to use a <strong>distributed cache</strong>.</p>
<p>The cache is a <strong>key-value store</strong> to store the bucket information. We will use a Redis cache to implement this.</p>
<p>Internally, Bucket4j allows us to plug in any implementation of the Java JCache API. The <a target="_blank" href="https://redisson.org/">Redisson</a> client of Redis is the implementation we will use.</p>
<h2 id="heading-project-implementation">Project Implementation</h2>
<p>We will use the <a target="_blank" href="https://spring.io/projects/spring-boot">Spring Boot</a> framework to build our service.</p>
<p>Our service will contain the below components:</p>
<ol>
<li>A simple REST API.</li>
<li>A Redis cache connected to the service – using the Redisson client.</li>
<li>The Bucket4J library wrapped around the REST API.</li>
<li>We'll connect Bucket4J to the JCache interface which will use the Redisson client as the implementation in the background.</li>
</ol>
<p>First, we will learn to rate limit the API for all requests. Then we will learn to implement a more complex rate limiting mechanism per user or per pricing tier.</p>
<p>Let's start with the project setup.</p>
<h3 id="heading-install-dependencies">Install Dependencies</h3>
<p>Let's add the below dependencies to our <em>pom.xml</em> (or <em>build.gradle</em>) file.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- To build the Rest API --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Redisson Starter = Spring Data Redis starter(excluding other clients) and Redisson client --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.redisson<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>redisson-spring-boot-starter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.17.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Bucket4J starter = Bucket4J + JCache --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.giffing.bucket4j.spring.boot.starter<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>bucket4j-spring-boot-starter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.5.2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>
</code></pre>
<h3 id="heading-cache-configuration">Cache Configuration</h3>
<p>Firstly, we need to start our Redis server. Let's say we have a Redis server running on port 6379 on our local machine.</p>
<p>We need to perform two steps:</p>
<ol>
<li>Create a connection to this server from our application.</li>
<li>Set up JCache to use the Redisson client as the implementation.</li>
</ol>
<p><a target="_blank" href="https://github.com/redisson/redisson/wiki/14.-Integration-with-frameworks/#144-jcache-api-jsr-107-implementation">Redisson's documentation</a> provides concise steps to implement this in a regular Java application. We're going to implement the same steps, but in Spring Boot.</p>
<p>Let's look at the code first. We need to create a Configuration class to create the required beans.</p>
<pre><code class="lang-java"><span class="hljs-meta">@Configuration</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RedisConfig</span>  </span>{

    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Config <span class="hljs-title">config</span><span class="hljs-params">()</span> </span>{
        Config config = <span class="hljs-keyword">new</span> Config();
        config.useSingleServer().setAddress(<span class="hljs-string">"redis://localhost:6379"</span>);
        <span class="hljs-keyword">return</span> config;
    }

    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> CacheManager <span class="hljs-title">cacheManager</span><span class="hljs-params">(Config config)</span> </span>{
        CacheManager manager = Caching.getCachingProvider().getCacheManager();
        cacheManager.createCache(<span class="hljs-string">"cache"</span>, RedissonConfiguration.fromConfig(config));
        <span class="hljs-keyword">return</span> cacheManager;
    }

    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function">ProxyManager&lt;String&gt; <span class="hljs-title">proxyManager</span><span class="hljs-params">(CacheManager cacheManager)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JCacheProxyManager&lt;&gt;(cacheManager.getCache(<span class="hljs-string">"cache"</span>));
    }
}
</code></pre>
<p><strong>What does this do?</strong></p>
<ol>
<li>Creates a configuration object that we can use to create a connection.</li>
<li>Creates a cache manager using the configuration object. This will internally create a connection to the Redis instance and create a hash called "cache" on it.</li>
<li>Creates a proxy manager that will be used to access the cache. Whatever our application tries to cache using the JCache API, it will be cached on the Redis instance inside the hash named "cache".</li>
</ol>
<h3 id="heading-build-the-api">Build the API</h3>
<p>Let's create a simple REST API.</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RateLimitController</span> </span>{
    <span class="hljs-meta">@GetMapping("/user/{id}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getInfo</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable("id")</span> String id)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello "</span> + id;
    }
}
</code></pre>
<p>If I hit the API with the URL <code>http://localhost:8080/user/1</code>, I will get the response <code>Hello 1</code>.</p>
<h3 id="heading-bucket4j-configuration">Bucket4J Configuration</h3>
<p>To implement the rate limiting, we need to configure Bucket4J. Thankfully, we do not need to write any boilerplate code due to the starter library.</p>
<p>It also <strong>automatically detects the ProxyManager bean</strong> we created in the previous step and uses it to cache the buckets.</p>
<p>What we do need to do is configure this library around the API we created.<br>Again there are multiple ways to do this.</p>
<p>We can go for <a target="_blank" href="https://github.com/MarcGiffing/bucket4j-spring-boot-starter#configuration-via-properties">property-based configuration</a> which is defined in the starter library.<br>This is the most convenient way for simple cases like rate-limiting for all users or all guest users.</p>
<p>However, if we want to implement something more complex like a rate limit for each user, it's better to write custom code for it.</p>
<p>We are going to implement rate limiting per user. Let's assume we have the rate limit for each user stored in a database, and we can query it using the user id.</p>
<p>Let's write the code for it step by step.</p>
<h4 id="heading-create-a-bucket">Create a Bucket</h4>
<p>Before we start, let's look at how a bucket is created.</p>
<pre><code class="lang-java">Refill refill = Refill.intervally(<span class="hljs-number">10</span>, Duration.ofMinutes(<span class="hljs-number">1</span>));
Bandwidth limit = Bandwidth.classic(<span class="hljs-number">10</span>, refill);
Bucket bucket = Bucket4j.builder()
        .addLimit(limit)
        .build();
</code></pre>
<ul>
<li><strong>Refill</strong> – After how much time the bucket will be refilled.</li>
<li><strong>Bandwidth</strong> – How much bandwidth the bucket has. Basically, requests per refill period.</li>
<li><strong>Bucket</strong> – An object configured using these two parameters. Additionally, it maintains a token counter to keep track of how many tokens are available in the bucket.</li>
</ul>
<p>Using this as the building block, let's change a few things to make it suitable to our use case.</p>
<h4 id="heading-create-and-cache-buckets-using-proxymanager">Create and Cache Buckets using ProxyManager</h4>
<p>We created the proxy manager for the purpose of storing buckets on Redis. Once a bucket is created, it needs to be cached on Redis and does not need to be created again.</p>
<p>To make this happen, we will replace the <code>Bucket4j.builder()</code> with <code>proxyManager.builder()</code>. ProxyManager will take care of caching the buckets and not creating them again.</p>
<p>ProxyManager's builder takes two parameters – a <strong>key</strong> against which the bucket will be cached and a <strong>configuration object</strong> that it will use to create the bucket.</p>
<p>Let's see how we can implement it:</p>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RateLimiter</span> </span>{
    <span class="hljs-comment">//autowiring dependencies</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> Bucket <span class="hljs-title">resolveBucket</span><span class="hljs-params">(String key)</span> </span>{
        Supplier&lt;BucketConfiguration&gt; configSupplier = getConfigSupplierForUser(key);

        <span class="hljs-comment">// Does not always create a new bucket, but instead returns the existing one if it exists.</span>
        <span class="hljs-keyword">return</span> buckets.builder().build(key, configSupplier);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Supplier&lt;BucketConfiguration&gt; <span class="hljs-title">getConfigSupplierForUser</span><span class="hljs-params">(String key)</span> </span>{
        User user = userRepository.findById(userId);
        Refill refill = Refill.intervally(user.getLimit(), Duration.ofMinutes(<span class="hljs-number">1</span>));
        Bandwidth limit = Bandwidth.classic(user.getLimit(), refill);
        <span class="hljs-keyword">return</span> () -&gt; (BucketConfiguration.builder()
                .addLimit(limit)
                .build());
    }
}
</code></pre>
<p>We have created a method which returns a bucket for a key provided. In the next step, we will see how to use this.</p>
<h4 id="heading-how-to-consume-tokens-and-set-up-rate-limiting">How to Consume Tokens and Set Up Rate Limiting</h4>
<p>When a request comes in, we will try to consume a token from the relevant bucket.<br>We will use the <code>tryConsume()</code> method of the bucket to do this.</p>
<pre><code class="lang-java"><span class="hljs-meta">@GetMapping("/user/{id}")</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getInfo</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable("id")</span> String id)</span> </span>{
    <span class="hljs-comment">// gets the bucket for the user</span>
    Bucket bucket = rateLimiter.resolveBucket(id);

    <span class="hljs-comment">// tries to consume a token from the bucket</span>
    <span class="hljs-keyword">if</span> (bucket.tryConsume(<span class="hljs-number">1</span>)) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello "</span> + id;
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Rate limit exceeded"</span>;
    }
}
</code></pre>
<p>The <code>tryConsume()</code> method returns <code>true</code> if the token was consumed successfully or <code>false</code> if the token was not consumed.</p>
<h2 id="heading-how-to-test-our-service">How to Test our Service</h2>
<p>We can test this using any automated testing technique. For example, we can use <a target="_blank" href="https://junit.org/">JUnit</a>. Let's write a test case that calls the <code>getInfo()</code> method multiple times and verifies that the response is correct.</p>
<p>Let's assume we have a user with id <code>1</code> and a limit of <code>10</code> requests per minute. Let's assume we also have a user with id <code>2</code> and a limit of <code>20</code> requests per minute.</p>
<p>We will hit 11 requests for both users and verify that the request fails for the user with id <code>1</code> but succeeds for the user with id <code>2</code>.</p>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">testGetInfo</span><span class="hljs-params">()</span> </span>{

    <span class="hljs-comment">// calls the method 10 times for user 1</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) {
        rateLimiter.getInfo(<span class="hljs-number">1</span>));
        rateLimiter.getInfo(<span class="hljs-number">2</span>));
    }

    <span class="hljs-comment">// verifies that the response is rate limited for user 1</span>
    assertEquals(<span class="hljs-string">"Rate limit exceeded"</span>, rateLimiter.getInfo(<span class="hljs-number">1</span>));

    <span class="hljs-comment">// verifies that the response is successful for user 2</span>
    assertEquals(<span class="hljs-string">"Hello 2"</span>, rateLimiter.getInfo(<span class="hljs-number">2</span>));
}
</code></pre>
<p>When we run the test, we will see that the test passes.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, we have covered how to create a rate limiter using Bucket4j and Redis in a Spring Boot application.We also looked at how to set up a Redisson client with JCache and how to use it to cache buckets.</p>
<p>At the end, we implemented a simple rate limiter which can be used to rate limit requests for specific users.</p>
<p>Hope you enjoyed this tutorial. Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Scale a System With Process Splitting and Redis ]]>
                </title>
                <description>
                    <![CDATA[ By Pramono Winata Have you ever gotten into trouble trying to handle a single process that's really huge or heavy? If so, I can help you figure out how to better manage it. In this article I will be sharing how I'm currently managing a single message... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/scale-system-split-processes/</link>
                <guid isPermaLink="false">66d4609cc7632f8bfbf1e485</guid>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ scalability ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 20 Jul 2021 19:26:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/07/photo-1508624217470-5ef0f947d8be.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Pramono Winata</p>
<p>Have you ever gotten into trouble trying to handle a single process that's really huge or heavy? If so, I can help you figure out how to better manage it.</p>
<p>In this article I will be sharing how I'm currently managing a single message that is too big to be processed on a single process. I've split it into different chunks, which results in separate processes.</p>
<p>I won't go into much technical detail, but more of the architectural process.<br>I'll discuss some bits about caching usage and pubsub, but I will not go into details on the implementation. Instead, I'll focus on the pattern itself.</p>
<h2 id="heading-the-problem">The Problem</h2>
<p><img src="https://images.unsplash.com/photo-1489533119213-66a5cd877091?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDh8fHN0YXJ0fGVufDB8fHx8MTYyNjYzMDgzMQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Begin." width="2000" height="1333" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@dsmacinnes?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"&gt;Danielle MacInnes / &lt;a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm<em>campaign=api-credit)</em></p>
<p>Perhaps the first question that comes to your mind is why we need to split a single process into several concurrent processes?</p>
<p>There might be several reasons to do this. In my case, though, I did it because the message was just too big.</p>
<p>To give you some idea about my situation, let me brief you a bit with this simple diagram:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/sad.png" alt="Image" width="600" height="400" loading="lazy">
<em>I'm not a great diagram maker, but this should get the point across</em></p>
<p>To simplify it with words, imagine two separate services, service A and service B, with a pubsub service in the middle of it. </p>
<p>If you are not sure what a pubsub service is, just imagine it as a broker that helps the message from one service reach the other service.</p>
<p>Service A will then publish a message and through the pubsub, service B will then process it. After it has finished the process, it will do another process to mark that the message has been processed.</p>
<p>Simple enough right?</p>
<p>It's just that in some cases when the message is too big, it will not successfully publish the message because of the pubsub service's limitations. </p>
<p>Alright, this should give you an overview of the issues that I've encountered. So how did I fix this problem? In the next section, I will run you through my solution.</p>
<h2 id="heading-my-first-approach">My First Approach</h2>
<p><img src="https://images.unsplash.com/photo-1513348355499-5bdba1597a80?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDg2fHx0aGlua3xlbnwwfHx8fDE2MjY2MzA4OTY&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Think" width="2000" height="1333" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@dose?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"&gt;Dose Media / &lt;a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm<em>campaign=api-credit)</em></p>
<p>The first thing that came to my mind was increasing the size that the pubsub service can handle, which is doable with a single config change.</p>
<p>But life won't be too interesting if it's that easy right? What happens if the message just keeps getting bigger? Do we keep increasing the size of the pubsub?</p>
<p>Turns out that doing so can result in a lot of scalability issues. Not good for a long term solution.</p>
<p>Then I came up with another solution that I thought might solve it: I split that message into several messages and tried to process those parts separately.</p>
<p>Now, the system looked like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/asda.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>My updated system/process</em></p>
<p>As you can see from the diagram, the message gets split into several smaller messages. How it's split and which part of the message needs to be split might differ for each case and flow. </p>
<p>In my case, though, my message actually contains a list of items so I can split it by each item.</p>
<p>Let's say that I have 10 items. Previously, it would publish all 10 items in one message. But now after splitting the message up, it will turn that message into 10 messages.</p>
<p>This results in a single process becoming several processes all together. A single publish will turn into 10 publishes which in turn will turn that single process into 10 processes. </p>
<p>This might not look ideal when you look at it that way, but this is the best solution I came up with and it sure does work.</p>
<p>So is that all, only splitting it up?</p>
<p>Not really – remember that final part where it marks the process as finished?<br>If so, you might wonder why that part is missing from my new diagram. </p>
<p>Don't worry – it's not that I forgot about it. I intentionally left it out for the next part.</p>
<p>The thing is, when you split the message and break it into several processes, your system might not know if the whole process is actually finished. This is another major issue that we need to tackle, and thankfully I managed to find a solution for that, too.</p>
<h2 id="heading-how-to-handle-finishing-processes">How to Handle Finishing Processes</h2>
<p><img src="https://images.unsplash.com/photo-1516434233442-0c69c369b66d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fHN3YXJtfGVufDB8fHx8MTYyNjYzMDk3Mg&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="A whale in the sky( Starling roost at Otmoor UK )" width="2000" height="1333" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@tumbao1949?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"&gt;James Wainscoat / &lt;a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm<em>campaign=api-credit)</em></p>
<p>So how exactly do I know if the process has finished, since those processes are happening concurrently?</p>
<p>The solution that I came up with is storing the number of processes that need to be done and decrementing it each time a process finishes. This way we will be able to know if the last process has finished.</p>
<p>It sure sounds simple enough, as long as we have a reliable place to store that data.<br>And actually there quite a lot of options for that. One of them is called <a target="_blank" href="https://redis.io/">Redis</a>, and I am using that to deal with my issue here. </p>
<p>If you are not familiar with Redis, it is a service that is generally used as a cache.</p>
<p>We will manage our Redis mechanism like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/saddd.png" alt="Image" width="600" height="400" loading="lazy">
<em>Adding Redis to mark our process</em></p>
<p>The process looks exactly the same as before, but with the addition of Redis in the middle. You need to make sure you have a valid initial count for this case.</p>
<p>In my case, since I'm publishing a list, I can easily put the length of my list as my initial counter. And for the counter, I can just decrease it by one each time a process has finished. Then I will be able to know if I have finished all my processes simply by referring to my Redis counter. If it has reached 0, it means that I can safely mark that all of my processes are done.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>To sum it all up, I split the message into several messages which will be processed all together in several processes. To manage the message processes, I use Redis caching.</p>
<p>The solution that I have described above will not be a silver bullet every time you have a problem processing a very big message. There are other ways like streaming your message, but that will be a story for another day.</p>
<p>Thanks for reading my article through to the end! I sincerely hope that you enjoyed and found my article interesting and, most importantly, that it was useful.  </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Redis Database Basics – How the Redis CLI Works, Common Commands, and Sample Projects ]]>
                </title>
                <description>
                    <![CDATA[ By Mehul Mohan Redis is a popular in-memory database used for a variety of projects, like caching and rate limiting. In this blog post, we will see how you can use Redis as an in-memory database, why you'd want to use Redis, and finally we'll discuss... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-learn-redis/</link>
                <guid isPermaLink="false">66d46059e39d8b5612bc0dd7</guid>
                
                    <category>
                        <![CDATA[ caching ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 14 Apr 2021 15:55:10 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/04/image-46.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Mehul Mohan</p>
<p>Redis is a popular in-memory database used for a variety of projects, like caching and rate limiting.</p>
<p>In this blog post, we will see how you can use Redis as an in-memory database, why you'd want to use Redis, and finally we'll discuss a few important features of the database. Let's start.</p>
<h2 id="heading-what-is-an-in-memory-database">What is an in-memory database?</h2>
<p>Traditional databases keep part of the database (usually the "hot" or often-accessed indices) in memory for faster access, and the rest of the database on disk. </p>
<p>Redis, on the other hand, focuses a lot on latency and the fast retrieval and storage of data. So it operates completely on memory (RAM) instead of storage devices (SSD/HDD). Speed is important!</p>
<p>Redis is a key-value database. But don't let it fool you into thinking it's a simple database. You have a lot of ways to store and retrieve those keys and values.</p>
<h2 id="heading-why-do-you-need-redis">Why do you need Redis?</h2>
<p>You can use Redis in a lot of ways. But there are two main reasons I can think of:</p>
<ol>
<li>You are creating an application where you want to make your code layer stateless. Why? - Because if your code is stateless, it is horizontally scalable. Therefore, you can use Redis as a central storage system and let your code handle just the logic.</li>
<li>You are creating an application where multiple apps might need to share data. For example, what if somebody is trying to bruteforce your site at <code>payments.codedamn.com</code>, and once you detect it, you'd also like to block them at <code>login.codedamn.com</code>? Redis lets your multiple disconnected/loosely connected services share a common memory space.</li>
</ol>
<h2 id="heading-redis-basics">Redis Basics</h2>
<p>Redis is relatively simple to learn as there are only a handful of commands you'll need to know. In the next couple sections, we'll cover a few main Redis concepts and some useful common commands.</p>
<h3 id="heading-the-redis-cli">The Redis CLI</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/ezgif.com-gif-maker.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Redis has a CLI which is a REPL version of the command line. Whatever you write will be evaluated. </p>
<p>The above image shows you how to do a simple <code>PING</code> or hello world in Redis in one of my codedamn Redis course exercises (the course is linked at the end if you want to check it out).</p>
<p>This Redis REPL is very useful when you're working with the database in an application and quickly need to get a peek into a few keys or the state of Redis.</p>
<h2 id="heading-common-redis-commands">Common Redis commands</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/ezgif.com-gif-maker--1-.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Trying out common commands on Redis CLI in codedamn course</em></p>
<p>Here are a few very commonly used commands in Redis to help you learn more about how it works:</p>
<h3 id="heading-set">SET</h3>
<p>SET allows you to set a key to a value in Redis.</p>
<p>Here's an example of how it works:</p>
<pre><code>SET mehul <span class="hljs-string">"developer from india"</span>
</code></pre><p>This sets the key <code>mehul</code> to the value <code>developer from india</code>.</p>
<h3 id="heading-get">GET</h3>
<p>GET allows you to get the keys you've set.</p>
<p>Here's the syntax:</p>
<pre><code>GET mehul
</code></pre><p>This will return the string "developer from india" as we set above.</p>
<h3 id="heading-setnx">SETNX</h3>
<p>This key will set a value only if the key does not exist. This command has a number of use cases, including not accidentally overwriting the value of a key which might already be present.</p>
<p>Here's how it works:</p>
<pre><code>SET key1 value1
SETNX key1 value2
SETNX key2 value2
</code></pre><p>After running this example, your <code>key1</code> will have the value <code>value1</code> and <code>key2</code> as <code>value2</code>. This is because the second command will have no effect as <code>key1</code> was already present.</p>
<h3 id="heading-mset">MSET</h3>
<p>MSET is like SET, but you can set multiple keys together in one command. Here's how it works:</p>
<pre><code>MSET key1 <span class="hljs-string">"value1"</span> key2 <span class="hljs-string">"value2"</span> key3 <span class="hljs-string">"value3"</span>
</code></pre><p>Right now we are using <code>key</code> and <code>value</code> as the prefix for keys and values. But in reality when you write such code it's easy to lose track of what is a key and what is a value in such a long command. </p>
<p>So one thing you can do is always quote your value using double quotes, and leave your keys without quotes (if they are valid keynames without quotes).</p>
<h3 id="heading-mget">MGET</h3>
<p>MGET is similar to GET, but it can return multiple values at once, like this:</p>
<pre><code>MGET key1 key2 key3 key4
</code></pre><p>This will return four values as an array: <code>value1</code>, <code>value2</code>, <code>value3</code> and <code>null</code>. We got <code>key4</code> as null because we never set it.</p>
<h3 id="heading-del">DEL</h3>
<p>This command deletes a key – simple enough, right?</p>
<p>Here's an example:</p>
<pre><code>SET key value
GET key # gives you <span class="hljs-string">"value"</span>
DEL key 
GET key # <span class="hljs-literal">null</span>
</code></pre><h3 id="heading-incr-and-decr">INCR and DECR</h3>
<p>You can use these two commands to increment or decrement a key which is a number. They are very useful and you'll use them a lot, because Redis can perform two operations in one – GET key and SET key to key + 1. </p>
<p>This avoids roundtrips to your parent application, and makes the operation also safe to perform without using transactions (more on this later)</p>
<p>Here's how they work:</p>
<pre><code>SET favNum <span class="hljs-number">10</span>
INCR favNum # <span class="hljs-number">11</span>
INCR favNum # <span class="hljs-number">12</span>
DECR favNum # <span class="hljs-number">11</span>
</code></pre><h3 id="heading-expire">EXPIRE</h3>
<p>The EXPIRE command is used to set an expiration timer to a key. Technically it's not a timer, but a kill timestamp beyond which the key will always return null unless it's set again.</p>
<pre><code>SET bitcoin <span class="hljs-number">100</span>
EXPIRE bitcoin <span class="hljs-number">10</span>

GET bitcoin # <span class="hljs-number">100</span>
# after <span class="hljs-number">10</span> seconds
GET bitcoin # <span class="hljs-literal">null</span>
</code></pre><p><code>EXPIRE</code> uses a little bit more memory to store that key as a whole (because now you have to also store when that key should expire). But you probably won't ever care about that overhead.</p>
<h3 id="heading-ttl">TTL</h3>
<p>This command can be used to learn how much time the key has to live.</p>
<p>Example:</p>
<pre><code>SET bitcoin <span class="hljs-number">100</span>
TTL bitcoin # <span class="hljs-number">-1</span>
TTL somethingelse # <span class="hljs-number">-2</span>

EXPIRE bitcoin <span class="hljs-number">5</span>
# wait <span class="hljs-number">2</span> seconds
TTL bitcoin # returns <span class="hljs-number">3</span>
# after <span class="hljs-number">1</span> second
GET bitcoin # <span class="hljs-literal">null</span>
TTL bitcoin # <span class="hljs-number">-2</span>
</code></pre><p>So what can we learn from this code?</p>
<ol>
<li>TTL will return <code>-1</code> if the key exists but doesn't have an expiration</li>
<li>TTL will return <code>-2</code> if the key doesn't exist</li>
<li>TTL will return time to live in seconds if the key exists and will expire</li>
</ol>
<h3 id="heading-setex">SETEX</h3>
<p>You can perform <strong>SET</strong> and <strong>EXPIRE</strong> together with <code>SETEX</code>.</p>
<p>Like this:</p>
<pre><code>SETEX key <span class="hljs-number">10</span> value
</code></pre><p>Here, the key is "key", the value is "value", and the time to live (TTL) is 10. This key will get unset after 10 seconds.</p>
<p>Now that you have fundamental knowledge of basic Redis commands and how the CLI works, let's build a couple of projects and use those tools in real life.</p>
<h2 id="heading-project-1-build-an-api-caching-system-with-redis">Project 1 – Build an API Caching System with Redis</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/Screenshot-2021-04-13-at-4.20.13-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Preview of API caching system building lab on codedamn</em></p>
<p>This project involves setting up an API caching system with Redis, where you cache results from a 3rd party server and use it for some time. </p>
<p>This is useful so that you are not rate limited by that third party. Also, caching improves your site's speed, so if you implement it correctly it's a win-win for everyone.</p>
<p>You can build this project interactively on codedamn inside the browser using Node.js. If you're interested, you can <a target="_blank" href="https://codedamn.com/learn/redis-caching-concepts-nodejs/2--CvP86ikcFeFUB4MTty">try the API caching lab for free</a>.</p>
<p>If you're only interested in the solution (and not building it yourself) here's how the core logic will work in Node.js:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">'/data'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">const</span> repo = req.body.repo

    <span class="hljs-keyword">const</span> value = <span class="hljs-keyword">await</span> redis.get(repo)

    <span class="hljs-keyword">if</span> (value) {
        <span class="hljs-comment">// means we got a cache hit</span>
        res.json({
            <span class="hljs-attr">status</span>: <span class="hljs-string">'ok'</span>,
            <span class="hljs-attr">stars</span>: value
        })

        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`https://api.github.com/repos/<span class="hljs-subst">${repo}</span>`</span>).then(<span class="hljs-function">(<span class="hljs-params">t</span>) =&gt;</span> t.json())

    <span class="hljs-keyword">if</span> (response.stargazers_count != <span class="hljs-literal">undefined</span>) {
        <span class="hljs-keyword">await</span> redis.setex(repo, <span class="hljs-number">60</span>, response.stargazers_count)
    }

    res.json({
        <span class="hljs-attr">status</span>: <span class="hljs-string">'ok'</span>,
        <span class="hljs-attr">stars</span>: response.stargazers_count
    })
})
</code></pre>
<p>Let's see what's happening here:</p>
<ul>
<li>We try to get the <code>repo</code> (which is the passed repo format - <code>facebook/react</code>) from our Redis cache. If present, great! We return the star count from our redis cache, saving us a roundtrip to GitHub's servers.</li>
<li>If we don't find it in cache, we do a request to GitHub's servers, and get the star count. We check if the star count is not undefined (in case a repo doesn't exist/is private). If it has a value, we <code>setex</code> the value with a timeout of 60 seconds.</li>
<li>We set a timeout because we don't want to serve stale values over time. This helps us refresh our star count at least once a minute.</li>
</ul>
<p>Here's the full source code:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/codedamn-classrooms/redis-nodejs-classroom/tree/lab5sol">https://github.com/codedamn-classrooms/redis-nodejs-classroom/tree/lab5sol</a></div>
<h2 id="heading-project-2-rate-limiting-api-with-redis">Project 2 - Rate limiting API with Redis</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/Screenshot-2021-04-13-at-4.21.31-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Preview of rate limiting API with Redis</em></p>
<p>This project involves rate limiting a certain endpoint to protect it from bad actors, and then blocking them from accessing that particular API. </p>
<p>This is very useful for login and sensitive API endpoints, where you don't want a single person to hit your endpoint with thousands of requests.</p>
<p>We perform rate limiting by IP address in this lab. If you want to attempt this codelab, you can try <a target="_blank" href="https://codedamn.com/learn/redis-caching-concepts-nodejs/RapJaltQbkvVtm4_b5oYl">it for free</a> on codedamn.</p>
<p>If you're only interested in the solution (and not building it yourself) here's how the core logic will work in Node.js:</p>
<pre><code>app.post(<span class="hljs-string">'/api/route'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-comment">// add data here</span>
    <span class="hljs-keyword">const</span> ip = req.headers[<span class="hljs-string">'x-forwarded-for'</span>] || req.ip

    <span class="hljs-keyword">const</span> reqs = <span class="hljs-keyword">await</span> redis.incr(ip)
    <span class="hljs-keyword">await</span> redis.expire(ip, <span class="hljs-number">2</span>)

    <span class="hljs-keyword">if</span> (reqs &gt; <span class="hljs-number">15</span>) {
        <span class="hljs-keyword">return</span> res.json({
            <span class="hljs-attr">status</span>: <span class="hljs-string">'rate-limited'</span>
        })
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (reqs &gt; <span class="hljs-number">10</span>) {
        <span class="hljs-keyword">return</span> res.json({
            <span class="hljs-attr">status</span>: <span class="hljs-string">'about-to-rate-limit'</span>
        })
    } <span class="hljs-keyword">else</span> {
        res.json({
            <span class="hljs-attr">status</span>: <span class="hljs-string">'ok'</span>
        })
    }
})
</code></pre><p>Let's understand this code block:</p>
<ul>
<li>We try to extract the IP from the <code>x-forwarded-for</code> header (or you can use <code>req.ip</code> as we are using express)</li>
<li>We <code>INCR</code> the IP address field. If our key in Redis never existed, INCR would automatically set it to 0 and increment, that is finally set it to 1.</li>
<li>We set the key to expire in 2 seconds. Ideally you'd want a larger value - but this is what the codedamn challenge specified above, so there we have it.</li>
<li>Finally we check the request counts, if they are greater than a certain threshold, we block the request from reaching the main function body.</li>
</ul>
<p>Here's the full solution:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/codedamn-classrooms/redis-nodejs-classroom/tree/lab6sol">https://github.com/codedamn-classrooms/redis-nodejs-classroom/tree/lab6sol</a></div>
<h2 id="heading-more-on-redis">More on Redis</h2>
<p>Redis is much more than what we have learned so far. But the good thing is that we have learned enough to start working with it already! </p>
<p>In this section, let's cover a few more Redis fundamentals.</p>
<h3 id="heading-redis-is-single-threaded">Redis is single threaded</h3>
<p>Redis runs as a single threaded process, even on a multiple core system supporting multi threading. This is not a performance nightmare, but a safety measure against inconsistent read/writes in a multi threaded environment. </p>
<p>If Redis were multi threaded, to ensure thread safety when accessing a single key, you'd eventually have resolved to some locking mechanism, which probably would perform worse than single threaded/sequential access anyway.</p>
<h3 id="heading-redis-transactions">Redis Transactions</h3>
<p>Of course, you cannot do everything in Redis in a single command. But you can surely ask it to do a block of commands in a single go (that is, nobody else talks to Redis while it is executing that block). You can do that using the <code>MULTI</code> command.</p>
<p>Here's how that works:</p>
<pre><code>MULTI
SET hello world
SET yo lo
SET number <span class="hljs-number">1</span>
INCR number
EXPIRE hello <span class="hljs-number">10</span>
EXPIRE yo <span class="hljs-number">5</span>
EXEC
</code></pre><p>This will perform all these operations in one go, that is it will <strong>not</strong> run anything at all after <code>MULTI</code>, and will run everything at once the moment it sees the <code>EXEC</code> keyword.</p>
<p>Redis includes support for lists and sets for more advanced use cases. You can also use Redis as a broadcasting service where you <strong>publish</strong> to a channel and others who have <strong>subscribed</strong> to the channel receive a notification. This is very useful in multi-client architecture.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you liked this introduction to Redis. This blog post is a part of codedamn's <a target="_blank" href="https://codedamn.com/learn/redis-caching-concepts-nodejs">new interactive course: Redis + Node.js caching</a>, where you not only learn about these concepts, but practice them within your browser on the go. </p>
<p>Feel free to give the course a try and let me know what you think. You can find me on <a target="_blank" href="https://twitter.com/mehulmpt">twitter</a> to send any feedback :)</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Redis to Supercharge Your Web APIs ]]>
                </title>
                <description>
                    <![CDATA[ By Tarique Ejaz Performance is an essential parameter to consider when you're designing any piece of software. It is particularly important when it comes to what happens behind-the-scenes.  We, as developers and technologists, adopt multiple tweaks a... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/redis-caching-essentials-with-node-and-mongoose/</link>
                <guid isPermaLink="false">66d46150d14641365a050977</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mongoose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Redis ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 13 May 2020 03:50:58 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/05/adobe-spark-post--1--1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Tarique Ejaz</p>
<p>Performance is an essential parameter to consider when you're designing any piece of software. It is particularly important when it comes to what happens behind-the-scenes. </p>
<p>We, as developers and technologists, adopt multiple tweaks and implementations in order to improve performance. This is where caching comes into play. </p>
<blockquote>
<p>Caching is defined as a mechanism to store data or files in a temporary storage location from where it can be instantly accessed whenever required. </p>
</blockquote>
<p>Caching has become a must have in web applications nowadays. We can use Redis to supercharge our web APIs - which are built using Node.js and MongoDB.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/redis.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>"Caching would apparently still play a super important role 100 to 200 years down the line."</em></p>
<h2 id="heading-redis-a-laymans-overview">Redis: A Layman's Overview</h2>
<p><a target="_blank" href="https://redis.io/">Redis</a>, according to the official documentation, is defined as an in-memory data structure store which is used as a database, message broker, or cache storage. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. </p>
<p>Okay, that is quite a lot of data structures right there. Just to make it simple, almost all the data structures supported can be condensed into one form of string or the other. You will get more clarity as we run through the implementation.</p>
<p>But one thing is clear. Redis is powerful, and when used properly can make our applications not only faster but amazingly efficient. Enough talk. Let's get our hands dirty.</p>
<h2 id="heading-lets-talk-code">Let's Talk Code</h2>
<p>Before we start off, you will need to get redis setup in your local system. You can follow this <a target="_blank" href="https://redis.io/topics/quickstart">quick setup</a> process to get redis up and running. </p>
<p>Done? Cool. Let's start. We have a simple application created in Express which makes use of an instance in MongoDB Atlas to read and write data from. </p>
<p>We have two major APIs created in the <code>/blogs</code> route file.</p>
<pre><code class="lang-js">...

<span class="hljs-comment">// GET - Fetches all blog posts for required user</span>
blogsRouter.route(<span class="hljs-string">'/:user'</span>)
    .get(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
        <span class="hljs-keyword">const</span> blogs = <span class="hljs-keyword">await</span> Blog.find({ <span class="hljs-attr">user</span>: req.params.user });

        res.status(<span class="hljs-number">200</span>).json({
            blogs,
        });
    });

<span class="hljs-comment">// POST - Creates a new blog post</span>
blogsRouter.route(<span class="hljs-string">'/'</span>)
    .post(<span class="hljs-keyword">async</span> (req, res, next) =&gt; {
        <span class="hljs-keyword">const</span> existingBlog = <span class="hljs-keyword">await</span> Blog.findOne({ <span class="hljs-attr">title</span>: req.body.title });

        <span class="hljs-keyword">if</span> (!existingBlog) {
            <span class="hljs-keyword">let</span> newBlog = <span class="hljs-keyword">new</span> Blog(req.body);

            <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> newBlog.save();

            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).json({
                <span class="hljs-attr">message</span>: <span class="hljs-string">`Blog <span class="hljs-subst">${result.id}</span> is successfully created`</span>,
                result,
            });
        }

        res.status(<span class="hljs-number">200</span>).json({
            <span class="hljs-attr">message</span>: <span class="hljs-string">'Blog with same title exists'</span>,
        });
    });

...
</code></pre>
<h3 id="heading-sprinkling-some-redis-goodness">Sprinkling Some Redis Goodness</h3>
<p>We start off by downloading the npm package <a target="_blank" href="https://www.npmjs.com/package/redis"><code>redis</code></a> to connect to the local redis server. </p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</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> util = <span class="hljs-built_in">require</span>(<span class="hljs-string">'util'</span>);

<span class="hljs-keyword">const</span> redisUrl = <span class="hljs-string">'redis://127.0.0.1:6379'</span>;
<span class="hljs-keyword">const</span> client = redis.createClient(redisUrl);
client.hget = util.promisify(client.hget);

...
</code></pre>
<p>We make use of the <code>utils.promisify</code> function to transform the <code>client.hget</code> function to return a promise instead of a callback. You can read more about <code>promisification</code> <a target="_blank" href="https://javascript.info/promisify">here</a>.</p>
<p>The Redis connection is in place. Before we start writing any more caching code, let us take a step back and try to understand what are the requirements we need to fulfill and the likely challenges we might face. </p>
<p>Our caching strategy should be able to address the following points.</p>
<ul>
<li>Cache the request for all blog posts for a particular user</li>
<li><p>Clear cache every time a new blog post is created</p>
<p>The likely challenges we should be careful of as we go about our strategy are:</p>
</li>
<li><p>The right way to handle key creation for storing cache data</p>
</li>
<li>Cache expiration logic and forced expiration for maintaining cache freshness</li>
<li>Reusable implementation of caching logic</li>
</ul>
<p>All right. We have our points jotted down and redis connected. On to the next step.</p>
<h3 id="heading-overriding-the-default-mongoose-exec-function">Overriding the Default Mongoose Exec Function</h3>
<p>We want our caching logic to be reusable. And not only reusable, we also want it to be the first checkpoint before we make any query to the database. This can easily be done by using a simple hack of piggy-backing onto the mongoose exec function.</p>
<pre><code class="lang-js">...

const exec = mongoose.Query.prototype.exec;

...

mongoose.Query.prototype.exec = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    ...

     const result = <span class="hljs-keyword">await</span> exec.apply(<span class="hljs-built_in">this</span>, <span class="hljs-built_in">arguments</span>);

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Data Source: Database'</span>);
    <span class="hljs-keyword">return</span> result;
}

...
</code></pre>
<p>We make use of the prototype object of mongoose to add our caching logic code as the first execution in the query.</p>
<h3 id="heading-adding-cache-as-a-query">Adding Cache as a Query</h3>
<p>In order to denote which queries should be up for caching, we create a mongoose query. We provide the ability to pass the <code>user</code> to be used as a hash-key through the <code>options</code> object. </p>
<blockquote>
<p><strong>Note:</strong> Hashkey serves as an identifier for a hash data structure which, in layman terms, can be stated as the parent key to a set of key-value pairs. Thereby, enabling caching of a larger number of query-value set. You can read more about hashes in redis <a target="_blank" href="https://redislabs.com/ebook/part-1-getting-started/chapter-1-getting-to-know-redis/1-2-what-redis-data-structures-look-like/1-2-4-hashes-in-redis/">here</a>.</p>
</blockquote>
<pre><code class="lang-js">...

mongoose.Query.prototype.cache = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">options = {}</span>) </span>{
    <span class="hljs-built_in">this</span>.enableCache = <span class="hljs-literal">true</span>;
    <span class="hljs-built_in">this</span>.hashKey = <span class="hljs-built_in">JSON</span>.stringify(options.key || <span class="hljs-string">'default'</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
};

...
</code></pre>
<p>Having done so, we can easily use the <code>cache(&lt;options argument&gt;)</code> query along with the queries we want to cache in the following manner.</p>
<pre><code class="lang-js">...

const blogs = <span class="hljs-keyword">await</span> Blog
                    .find({ <span class="hljs-attr">user</span>: req.params.user })
                    .cache({ <span class="hljs-attr">key</span>: req.params.user });

...
</code></pre>
<h3 id="heading-crafting-the-cache-logic">Crafting The Cache Logic</h3>
<p>We have set up a common reusable query to denote which queries need to be cached. Let's go ahead and write the central caching logic.</p>
<pre><code class="lang-js">...

mongoose.Query.prototype.exec = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.enableCache) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Data Source: Database'</span>);
        <span class="hljs-keyword">return</span> exec.apply(<span class="hljs-built_in">this</span>, <span class="hljs-built_in">arguments</span>);
    }

    <span class="hljs-keyword">const</span> key = <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-built_in">Object</span>.assign({}, <span class="hljs-built_in">this</span>.getQuery(), {
        <span class="hljs-attr">collection</span>: <span class="hljs-built_in">this</span>.mongooseCollection.name,
    }));

    <span class="hljs-keyword">const</span> cachedValue = <span class="hljs-keyword">await</span> client.hget(<span class="hljs-built_in">this</span>.hashKey, key);

    <span class="hljs-keyword">if</span> (cachedValue) {
        <span class="hljs-keyword">const</span> parsedCache = <span class="hljs-built_in">JSON</span>.parse(cachedValue);

        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Data Source: Cache'</span>);

        <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.isArray(parsedCache) 
                ?  parsedCache.map(<span class="hljs-function"><span class="hljs-params">doc</span> =&gt;</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">this</span>.model(doc)) 
                :  <span class="hljs-keyword">new</span> <span class="hljs-built_in">this</span>.model(parsedCache);
    }

    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> exec.apply(<span class="hljs-built_in">this</span>, <span class="hljs-built_in">arguments</span>);

    client.hmset(<span class="hljs-built_in">this</span>.hashKey, key, <span class="hljs-built_in">JSON</span>.stringify(result), <span class="hljs-string">'EX'</span>, <span class="hljs-number">300</span>);

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Data Source: Database'</span>);
    <span class="hljs-keyword">return</span> result;
};

...
</code></pre>
<p>Whenever we use the <code>cache()</code> query along with our main query, we set the <code>enableCache</code> key to be true. </p>
<p>If the key is false, we return the main <code>exec</code> query as default. If not, we first form the key for fetching and storing/refreshing the cache data. </p>
<p>We use the <code>collection</code> name along with the default query as the key name for the sake of uniqueness. The hash-key used is the name of the <code>user</code> which we have already set earlier in the <code>cache()</code> function definition. </p>
<p>The cached data is fetched using the <code>client.hget()</code> function which requires the hash-key and the consequent key as parameters. </p>
<blockquote>
<p><strong>Note:</strong> We always use <code>JSON.parse()</code> while fetching any data from redis. And similarly, we use <code>JSON.stringify()</code> on the key and data before storing anything into redis. This is done since redis does not support JSON data structures.</p>
</blockquote>
<p>Once we have obtained the cached data, we have to transform each of the cached objects into a mongoose model. This can be done by simply using <code>new this.model(&lt;object&gt;)</code>. </p>
<p>If the cache does not contain the required data, we make a query to the database. Then, having returned the data to the API, we refresh the cache using <code>client.hmset()</code>. We also set a default cache expiration time of 300 seconds. This is customizable based on your caching strategy.</p>
<p>The caching logic is in place. We have also set a default expiration time. Next up, we look at forcing cache expiration whenever a new blog post is created.</p>
<h3 id="heading-forced-cache-expiration">Forced Cache Expiration</h3>
<p>In certain cases, such as when a user creates a new blog post, the user expects that the new post should be available when they fetche all the posts. </p>
<p>In order to do so, we have to clear the cache related to that user and update it with new data. So we have to force expiration. We can do that by invoking the <code>del()</code> function provided by redis. </p>
<pre><code class="lang-js">...

module.exports = {
    clearCache(hashKey) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Cache cleaned'</span>);
        client.del(<span class="hljs-built_in">JSON</span>.stringify(hashKey));
    }
}

...
</code></pre>
<p>We also have to keep in mind that we will be forcing expiration on multiple routes. One extensible way is to use this <code>clearCache()</code> as a middleware and call it once any query related to a route has finished execution. </p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { clearCache } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../services/cache'</span>);

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-comment">// wait for route handler to finish running</span>
    <span class="hljs-keyword">await</span> next(); 

    clearCache(req.body.user);
}
</code></pre>
<p>This middleware can be easily called on a particular route in the following way.</p>
<pre><code class="lang-js">...

blogsRouter.route(<span class="hljs-string">'/'</span>)
    .post(cleanCache, <span class="hljs-keyword">async</span> (req, res, next) =&gt; {

    ...

    }

...
</code></pre>
<p>And we are done. I agree that was a quite a lot of code. But with that last part, we have set up redis with our application and taken care of almost all the likely challenges. It is time to see our caching strategy in action.</p>
<h2 id="heading-redis-in-action">Redis in Action</h2>
<p>We make use of <a target="_blank" href="https://www.postman.com/">Postman</a> as the API client to see our caching strategy in action. Here we go. Let's run through the API operations, one by one.</p>
<ol>
<li>We create a new blog post using the <code>/blogs</code> route</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot--50-.png" alt="Image" width="600" height="400" loading="lazy">
<em>New Blog Post Creation</em></p>
<ol start="2">
<li>We then fetch all the blog posts related to user <code>tejaz</code></li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot--51-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Fetching all Blog Posts for User tejaz</em></p>
<ol start="3">
<li>We fetch all the blog posts for user <code>tejaz</code> once more.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot--52-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Fetch all Blog Posts for User tejaz Once More</em></p>
<p>You can clearly see that when we fetch from the cache, the time taken has gone down from <strong>409ms</strong> to <strong>24ms</strong>. This supercharges your API by decreasing the time taken by almost <strong>95%.</strong> </p>
<p>Plus, we can clearly see that cache expiration and update operations work as expected.</p>
<p>You can find the complete source code in the <code>redis-express</code> folder here.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/tarique93102/article-snippets/tree/master/redis-express">https://github.com/tarique93102/article-snippets/tree/master/redis-express</a></div>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Caching is a mandatory step for any performance-efficient and data-intensive application. Redis helps you easily achieve this in your web applications. It is a super powerful tool, and if used properly it can definitely provide an excellent experience to developers as well as users all around.</p>
<p>You can find the complete set of redis commands <a target="_blank" href="https://redis.io/commands">here</a>. You can use it with <code>redis-cli</code> to monitor your cache data and application processes. </p>
<p>The possibilities offered by any particular technology is truly endless. If you have any queries, you can reach out to me on <code>[LinkedIn](https://www.linkedin.com/in/tarique-ejaz/)</code>. </p>
<p>In the mean time, keep coding.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
