<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ankita Budhia</title>
    <description>The latest articles on DEV Community by Ankita Budhia (@ankita_budhia).</description>
    <link>https://dev.to/ankita_budhia</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2192246%2Fcfa64919-52a8-4935-a5c2-749f933fe9cd.png</url>
      <title>DEV Community: Ankita Budhia</title>
      <link>https://dev.to/ankita_budhia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ankita_budhia"/>
    <language>en</language>
    <item>
      <title>How I Built a Netflix-Inspired Movie Explorer with TanStack Query and Framer Motion</title>
      <dc:creator>Ankita Budhia</dc:creator>
      <pubDate>Thu, 25 Jun 2026 11:31:43 +0000</pubDate>
      <link>https://dev.to/ankita_budhia/how-i-built-a-netflix-inspired-movie-explorer-with-tanstack-query-and-framer-motion-cf1</link>
      <guid>https://dev.to/ankita_budhia/how-i-built-a-netflix-inspired-movie-explorer-with-tanstack-query-and-framer-motion-cf1</guid>
      <description>&lt;p&gt;After spending a lot of time building full-stack and backend-heavy projects, I wanted to work on something that was unapologetically frontend-focused, something that pushed me to think more about user experience, loading states, performance, and how to make an application feel polished rather than just functional.&lt;/p&gt;

&lt;p&gt;That’s how &lt;strong&gt;Movix&lt;/strong&gt; came about.&lt;/p&gt;

&lt;p&gt;Movix is a &lt;strong&gt;Netflix-inspired movie discovery app&lt;/strong&gt; built with &lt;strong&gt;React, TypeScript, React Query, Tailwind CSS, and Framer Motion&lt;/strong&gt;. The goal wasn’t just to fetch movie data from the TMDB API and render cards. I wanted to build an experience that felt smooth to use, with fast search, responsive interactions, useful details, and small touches like prefetching and animated hover states that make the app feel more intentional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://movix-tmdb.netlify.app" rel="noopener noreferrer"&gt;https://movix-tmdb.netlify.app&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ankita007-coder/TMDB-Movie-Explorer" rel="noopener noreferrer"&gt;https://github.com/ankita007-coder/TMDB-Movie-Explorer&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What Movix does
&lt;/h2&gt;

&lt;p&gt;Movix lets users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browse trending, popular, and top-rated movies&lt;/li&gt;
&lt;li&gt;search for movies and actors&lt;/li&gt;
&lt;li&gt;open detailed movie pages with cast information&lt;/li&gt;
&lt;li&gt;view actor details in a modal&lt;/li&gt;
&lt;li&gt;save movies to a personal watchlist&lt;/li&gt;
&lt;li&gt;navigate a UI inspired by modern streaming platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the hood, it also became a good playground for exploring &lt;strong&gt;React Query caching&lt;/strong&gt;, &lt;strong&gt;prefetching strategies&lt;/strong&gt;, &lt;strong&gt;Context-based state management&lt;/strong&gt;, and &lt;strong&gt;performance-focused frontend architecture&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The idea behind the project
&lt;/h2&gt;

&lt;p&gt;A lot of movie apps are basically the same pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetch a list&lt;/li&gt;
&lt;li&gt;render some cards&lt;/li&gt;
&lt;li&gt;show a details page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s nothing wrong with that, but I wanted this project to focus on &lt;strong&gt;how the app feels&lt;/strong&gt; as much as what it does.&lt;/p&gt;

&lt;p&gt;That meant thinking about questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I reduce loading friction when a user clicks into a movie?&lt;/li&gt;
&lt;li&gt;How should search behave so it doesn’t feel clunky?&lt;/li&gt;
&lt;li&gt;What’s the best way to handle watchlist state without overengineering it?&lt;/li&gt;
&lt;li&gt;How can animations add polish without becoming distracting?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of treating Movix like “just another React project,” I used it to explore the frontend decisions that make an application feel more complete.&lt;/p&gt;


&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vite&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Styling and UI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tailwind CSS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Framer Motion&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Data fetching and server state
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TanStack Query&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  State management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React Context API&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Auth0&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TMDB (The Movie Database)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Building the home page around discovery
&lt;/h2&gt;

&lt;p&gt;The home page is designed around movie discovery rather than just raw data display.&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;hero carousel&lt;/strong&gt; featuring trending movies&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;multiple horizontal sections for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Trending&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Popular&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Top Rated&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This structure let me build a reusable section-based layout, where each category uses the same UI pattern but fetches different data.&lt;/p&gt;

&lt;p&gt;That might sound small, but I wanted the project structure to feel scalable from the start. Instead of writing one-off code for every section, I built reusable pieces like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HorizontalMovieSection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MovieCard&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;shared loading skeletons&lt;/li&gt;
&lt;li&gt;reusable API hooks for different categories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That kept the UI layer much cleaner as the project grew.&lt;/p&gt;


&lt;h2&gt;
  
  
  Designing movie cards that reveal more context
&lt;/h2&gt;

&lt;p&gt;A movie card usually starts as “poster + title,” but that felt too static.&lt;/p&gt;

&lt;p&gt;So I added &lt;strong&gt;hover interactions&lt;/strong&gt; where cards reveal additional information like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runtime&lt;/li&gt;
&lt;li&gt;genres&lt;/li&gt;
&lt;li&gt;movie title overlay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These interactions were built using &lt;strong&gt;Framer Motion&lt;/strong&gt;, which made it easy to add animations without turning the code into a mess of CSS transitions and state toggles.&lt;/p&gt;

&lt;p&gt;The goal here wasn’t to make the UI flashy for the sake of it, it was to make browsing feel more interactive and less like scanning a spreadsheet of posters.&lt;/p&gt;


&lt;h2&gt;
  
  
  Using React Query to make navigation feel faster
&lt;/h2&gt;

&lt;p&gt;One of the parts I enjoyed most in this project was using &lt;strong&gt;React Query prefetching&lt;/strong&gt; to improve perceived performance.&lt;/p&gt;

&lt;p&gt;When a user hovers over a movie card, the app &lt;strong&gt;prefetches the movie details query&lt;/strong&gt; in the background. So if they click into that movie a second later, there’s a good chance the details are already cached and ready.&lt;/p&gt;

&lt;p&gt;That small decision makes the app feel much faster than if every detail page waited for a fresh fetch after navigation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why React Query worked well here
&lt;/h3&gt;

&lt;p&gt;Movix has a lot of server-driven data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trending movies&lt;/li&gt;
&lt;li&gt;popular movies&lt;/li&gt;
&lt;li&gt;top-rated movies&lt;/li&gt;
&lt;li&gt;search results&lt;/li&gt;
&lt;li&gt;movie details&lt;/li&gt;
&lt;li&gt;cast information&lt;/li&gt;
&lt;li&gt;actor details&lt;/li&gt;
&lt;li&gt;known-for credits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Managing all of that with manual loading flags and &lt;code&gt;useEffect&lt;/code&gt; chains would get messy pretty quickly.&lt;/p&gt;

&lt;p&gt;React Query gave me a much cleaner model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;built-in caching&lt;/li&gt;
&lt;li&gt;stale time control&lt;/li&gt;
&lt;li&gt;query deduplication&lt;/li&gt;
&lt;li&gt;background refetching&lt;/li&gt;
&lt;li&gt;easier loading/error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this project, I configured queries with a &lt;strong&gt;5-minute stale time&lt;/strong&gt; and disabled unnecessary refetches on window focus to keep the UX smoother.&lt;/p&gt;


&lt;h2&gt;
  
  
  Search: simple feature, lots of UX details
&lt;/h2&gt;

&lt;p&gt;Search ended up being one of those features that seems straightforward until you start thinking about edge cases.&lt;/p&gt;

&lt;p&gt;Movix supports searching for both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;movies&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;actors&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make it feel usable, I added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;debounced input&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;loading skeletons&lt;/li&gt;
&lt;li&gt;empty states&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without debouncing, every keystroke would trigger a new request and the whole experience would feel noisy. Debouncing gave the search a much calmer, more deliberate feel while also reducing unnecessary API calls.&lt;/p&gt;


&lt;h2&gt;
  
  
  Building the movie details page
&lt;/h2&gt;

&lt;p&gt;The movie details page pulls together a lot of information in one place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backdrop and poster&lt;/li&gt;
&lt;li&gt;title and tagline&lt;/li&gt;
&lt;li&gt;runtime&lt;/li&gt;
&lt;li&gt;genres&lt;/li&gt;
&lt;li&gt;release date&lt;/li&gt;
&lt;li&gt;overview&lt;/li&gt;
&lt;li&gt;rating&lt;/li&gt;
&lt;li&gt;cast details&lt;/li&gt;
&lt;li&gt;streaming providers (India)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This page became a good exercise in component composition because there’s a lot of data, but I still wanted the UI to feel structured rather than overwhelming.&lt;/p&gt;

&lt;p&gt;So instead of building one huge component, I split the page into smaller pieces like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;movie hero/details section&lt;/li&gt;
&lt;li&gt;cast section&lt;/li&gt;
&lt;li&gt;reusable person cards&lt;/li&gt;
&lt;li&gt;modal-based actor details&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made the page much easier to reason about and update.&lt;/p&gt;


&lt;h2&gt;
  
  
  The cast modal and “Known For” section
&lt;/h2&gt;

&lt;p&gt;One of my favorite features in Movix is the &lt;strong&gt;actor modal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Clicking on a cast member opens a modal that shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;actor photo&lt;/li&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;li&gt;department&lt;/li&gt;
&lt;li&gt;short biography&lt;/li&gt;
&lt;li&gt;movies they’re known for&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “Known For” section is built using TMDB’s combined credits endpoint and then filtered/sorted to show the most relevant movie work.&lt;/p&gt;

&lt;p&gt;I liked this feature because it made the movie details page feel less static. Instead of cast members being dead-end data, they became entry points into another layer of discovery.&lt;/p&gt;


&lt;h2&gt;
  
  
  Watchlist without overengineering state
&lt;/h2&gt;

&lt;p&gt;For the watchlist, I deliberately kept the state management lightweight.&lt;/p&gt;

&lt;p&gt;I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React Context&lt;/strong&gt; for shared watchlist state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localStorage&lt;/strong&gt; for persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each authenticated user gets a unique storage key, which means their watchlist remains separate.&lt;/p&gt;

&lt;p&gt;This was one of those places where I didn’t want to overcomplicate the solution. A global state library would have been unnecessary here. Context + localStorage was enough, and it kept the feature simple.&lt;/p&gt;


&lt;h2&gt;
  
  
  Performance optimizations I cared about
&lt;/h2&gt;

&lt;p&gt;Movix wasn’t just about UI, I also wanted it to be reasonably efficient.&lt;/p&gt;

&lt;p&gt;A few things I focused on:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Prefetching movie details
&lt;/h3&gt;

&lt;p&gt;Hovering over a movie card triggers a prefetch, which reduces perceived load time on the details page.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Query caching
&lt;/h3&gt;

&lt;p&gt;React Query handles caching and deduplication, which avoids unnecessary repeated requests.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Lazy loading images
&lt;/h3&gt;

&lt;p&gt;Movie posters and other media are loaded lazily where appropriate.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Route-based code splitting
&lt;/h3&gt;

&lt;p&gt;Pages are lazy-loaded using &lt;code&gt;React.lazy()&lt;/code&gt; to reduce the initial bundle size.&lt;/p&gt;

&lt;p&gt;None of these optimizations are groundbreaking individually, but together they make the app feel noticeably smoother.&lt;/p&gt;


&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;As the project grew, I wanted to avoid dumping everything into a flat &lt;code&gt;components&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;So I split the code into a more structured layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src
├── api/
│   └── movieService.ts
├── components/
│   ├── movie/
│   │   ├── MovieCard
│   │   ├── HorizontalMovieSection
│   │   ├── CastSection
│   │   └── PersonCard
│   └── reusable/
│       ├── Container
│       ├── Skeleton
│       └── Modal
├── context/
│   └── WatchlistContext
├── hooks/
│   ├── useTrending
│   ├── useTopRated
│   ├── usePopular
│   ├── useMovieDetails
│   ├── usePersonCredits
│   └── usePersonDetails
├── pages/
│   ├── Home
│   ├── MovieDetailsPage
│   ├── SearchPage
│   └── WatchlistPage
├── routes/
│   └── AppRoutes
├── types/
│   └── movie.ts
└── utils/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation helped keep concerns clearer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API logic stays in one place&lt;/li&gt;
&lt;li&gt;data-fetching hooks stay reusable&lt;/li&gt;
&lt;li&gt;components stay focused on UI&lt;/li&gt;
&lt;li&gt;pages assemble the bigger experience&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What this project taught me
&lt;/h2&gt;

&lt;p&gt;Movix taught me a lot about the difference between &lt;strong&gt;building a feature&lt;/strong&gt; and &lt;strong&gt;building an experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Technically, the project helped me get more comfortable with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Query for server-state management&lt;/li&gt;
&lt;li&gt;prefetching strategies&lt;/li&gt;
&lt;li&gt;organizing frontend code for scale&lt;/li&gt;
&lt;li&gt;balancing animation with usability&lt;/li&gt;
&lt;li&gt;choosing simpler state management when it’s enough&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But more importantly, it reminded me that frontend work isn’t just about rendering data. It’s about making the product feel intuitive, responsive, and enjoyable to use.&lt;/p&gt;

&lt;p&gt;A movie app is a pretty familiar project idea, so the interesting part wasn’t the domain itself, it was the opportunity to think carefully about performance, interaction design, and how small decisions add up to a better experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I’d improve next
&lt;/h2&gt;

&lt;p&gt;If I continue working on Movix, I’d like to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;infinite scrolling for search results&lt;/li&gt;
&lt;li&gt;more advanced filters by genre/year/language&lt;/li&gt;
&lt;li&gt;a dark/light theme toggle&lt;/li&gt;
&lt;li&gt;better recommendations based on watchlist history&lt;/li&gt;
&lt;li&gt;stronger accessibility improvements across interactive components&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Movix started as a movie discovery app, but it turned into a really useful frontend playground for experimenting with React Query, performance optimization, reusable architecture, and UI polish.&lt;/p&gt;

&lt;p&gt;It was also a good reminder that “frontend project” doesn’t have to mean “just styling things.” There’s a lot of engineering depth in figuring out how to fetch, cache, structure, and present data in a way that feels seamless to the user.&lt;/p&gt;

&lt;p&gt;If you’ve built something similar, or if you’d approach React Query caching/prefetching differently, I’d love to hear how you think about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://movix-tmdb.netlify.app" rel="noopener noreferrer"&gt;https://movix-tmdb.netlify.app&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ankita007-coder/TMDB-Movie-Explorer" rel="noopener noreferrer"&gt;https://github.com/ankita007-coder/TMDB-Movie-Explorer&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>react</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Built a Distributed Rate Limiter with Redis, Lua, and Docker</title>
      <dc:creator>Ankita Budhia</dc:creator>
      <pubDate>Mon, 22 Jun 2026 19:14:35 +0000</pubDate>
      <link>https://dev.to/ankita_budhia/how-i-built-a-distributed-rate-limiter-with-redis-lua-and-docker-5736</link>
      <guid>https://dev.to/ankita_budhia/how-i-built-a-distributed-rate-limiter-with-redis-lua-and-docker-5736</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2bfmeonq3zgjjet6ttw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2bfmeonq3zgjjet6ttw.png" alt=" " width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rate limiting sounds simple at first, until you try to make it work across multiple server instances without breaking consistency.&lt;/p&gt;

&lt;p&gt;I wanted to build something beyond the usual “store a counter in memory and block after 5 requests” demo. So I built &lt;strong&gt;APIShield&lt;/strong&gt;, a distributed rate limiting system that works across multiple backend instances while enforcing limits consistently using &lt;strong&gt;Redis&lt;/strong&gt;, &lt;strong&gt;Lua scripting&lt;/strong&gt;, and &lt;strong&gt;MongoDB-backed dynamic rules&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This project ended up being a lot more interesting than I expected. It pushed me into questions around atomic operations, shared state, rule prioritization, and how to make a system feel closer to a production service than a middleware experiment.&lt;/p&gt;

&lt;p&gt;In this post, I’ll walk through the architecture, the rate limiting strategies I implemented, and why I ended up moving the sliding window logic into Redis using Lua.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/ankita007-coder/API_Shield" rel="noopener noreferrer"&gt;APIShield&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What is APIShield?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;APIShield&lt;/strong&gt; is a distributed rate limiting system that supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fixed Window&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sliding Window&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token Bucket&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic rule management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Violation tracking&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Admin dashboard for rule configuration&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main goal was to build a rate limiter that could run across &lt;strong&gt;multiple backend instances&lt;/strong&gt; while still applying limits consistently, even when requests hit different servers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The system looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   │
   ▼
Backend Instance 1  ─┐
Backend Instance 2  ─┼──&amp;gt; Redis (Centralized Enforcement)
                     │
                     └──&amp;gt; MongoDB (Rule Storage)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this setup?
&lt;/h3&gt;

&lt;p&gt;If each backend instance keeps its own counters in memory, the rate limit breaks the moment traffic is distributed across servers.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend 1 sees 3 requests&lt;/li&gt;
&lt;li&gt;Backend 2 sees 3 requests&lt;/li&gt;
&lt;li&gt;Limit is 5 requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If both servers count independently, the client effectively gets &lt;strong&gt;6 requests instead of 5&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s why &lt;strong&gt;Redis acts as the centralized enforcement layer&lt;/strong&gt;. Every backend instance checks and updates the same counters, so the limit stays consistent regardless of which instance receives the request.&lt;/p&gt;

&lt;p&gt;MongoDB stores dynamic rate limit rules, which means limits can be configured without redeploying the backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  The algorithms I implemented
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Fixed Window
&lt;/h3&gt;

&lt;p&gt;This is the simplest strategy.&lt;/p&gt;

&lt;p&gt;A counter is maintained for a fixed time window, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 requests per minute&lt;/li&gt;
&lt;li&gt;1000 requests per hour&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the window resets, the counter resets too.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Easy to implement&lt;/li&gt;
&lt;li&gt;Low memory overhead&lt;/li&gt;
&lt;li&gt;Fast&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;p&gt;It has the classic &lt;strong&gt;boundary burst problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A client could send:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 requests at &lt;code&gt;12:00:59&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;another 100 at &lt;code&gt;12:01:01&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and effectively bypass the intended smoothness of the limit.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Sliding Window
&lt;/h3&gt;

&lt;p&gt;To make enforcement more accurate, I implemented &lt;strong&gt;Sliding Window&lt;/strong&gt; using &lt;strong&gt;Redis Sorted Sets&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of counting requests in fixed buckets, the system stores request timestamps and continuously checks how many requests happened within the last &lt;code&gt;N&lt;/code&gt; seconds.&lt;/p&gt;

&lt;p&gt;At a high level, the logic is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove timestamps older than the active window&lt;/li&gt;
&lt;li&gt;Add the current request timestamp&lt;/li&gt;
&lt;li&gt;Count the remaining timestamps&lt;/li&gt;
&lt;li&gt;Block the request if the count exceeds the limit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives much more accurate rate limiting than fixed windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem with a naive implementation
&lt;/h3&gt;

&lt;p&gt;My first approach was the straightforward one: perform the sliding window logic using multiple Redis commands from Node.js.&lt;/p&gt;

&lt;p&gt;That meant every request required a sequence like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remove expired entries&lt;/li&gt;
&lt;li&gt;add current request&lt;/li&gt;
&lt;li&gt;count requests&lt;/li&gt;
&lt;li&gt;set expiry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It works, but it also means &lt;strong&gt;multiple Redis round trips per request&lt;/strong&gt;. At small scale that’s acceptable. At higher traffic volumes, it starts to feel wasteful — especially because the whole operation really needs to be treated as one unit.&lt;/p&gt;

&lt;p&gt;That’s what led me to Lua.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I used Lua for Sliding Window
&lt;/h2&gt;

&lt;p&gt;To reduce latency and make the sliding window logic atomic, I moved it into a &lt;strong&gt;Lua script&lt;/strong&gt; executed directly inside Redis.&lt;/p&gt;

&lt;p&gt;Instead of making multiple Redis calls from Node.js, the backend sends a single script to Redis, and Redis performs the entire rate-limiting workflow in one go.&lt;/p&gt;

&lt;p&gt;That solved three problems at once:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Atomicity
&lt;/h3&gt;

&lt;p&gt;There’s no gap between removing old requests, inserting the new one, and checking the count. That avoids race conditions when multiple requests arrive close together.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fewer network round trips
&lt;/h3&gt;

&lt;p&gt;The logic runs directly inside Redis, so the backend doesn’t need to orchestrate multiple calls for every request.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. A more production-friendly implementation
&lt;/h3&gt;

&lt;p&gt;It made the sliding window approach feel much less like a demo and much more like something I’d be comfortable using in a real distributed service.&lt;/p&gt;

&lt;p&gt;This was probably my favorite part of the project because it changed the sliding window implementation from “technically correct” to “something that actually scales more gracefully.”&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Token Bucket
&lt;/h3&gt;

&lt;p&gt;I also implemented &lt;strong&gt;Token Bucket&lt;/strong&gt; to support smoother rate limiting behavior.&lt;/p&gt;

&lt;p&gt;In this model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a bucket has a maximum token capacity&lt;/li&gt;
&lt;li&gt;tokens refill over time&lt;/li&gt;
&lt;li&gt;every request consumes one token&lt;/li&gt;
&lt;li&gt;if no tokens remain, the request is blocked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is useful when you want to allow short bursts of traffic without removing limits entirely.&lt;/p&gt;

&lt;p&gt;Compared to fixed windows, it feels much more natural for APIs where occasional bursts are acceptable but sustained abuse is not.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dynamic Rule Engine
&lt;/h2&gt;

&lt;p&gt;One of the things I didn’t want was a hardcoded rate limiter with a single global limit.&lt;/p&gt;

&lt;p&gt;So I built a &lt;strong&gt;dynamic rule engine&lt;/strong&gt; where rules are stored in &lt;strong&gt;MongoDB&lt;/strong&gt; and managed through an admin dashboard.&lt;/p&gt;

&lt;p&gt;Each rule can define things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;target&lt;/strong&gt; → &lt;code&gt;ip&lt;/code&gt; or &lt;code&gt;user&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;scope&lt;/strong&gt; → &lt;code&gt;global&lt;/code&gt; or &lt;code&gt;endpoint&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;algorithm&lt;/strong&gt; → fixed window / sliding window / token bucket&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;limit and window configuration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;active / inactive state&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes the system much more flexible because different limits can be applied to different scenarios instead of forcing one blanket rule across everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rule Resolution Priority
&lt;/h2&gt;

&lt;p&gt;When multiple rules could match the same request, I added a priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Endpoint + User&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Endpoint + IP&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global + User&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global + IP&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fallback Rule&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This allows the system to support more realistic cases.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a premium user can have a higher limit on a specific endpoint&lt;/li&gt;
&lt;li&gt;anonymous traffic can be limited by IP&lt;/li&gt;
&lt;li&gt;everything else can still fall back to a global default rule&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Fallback Protection
&lt;/h2&gt;

&lt;p&gt;I also didn’t want a missing rule to mean &lt;strong&gt;unlimited access&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So if no specific rule matches a request, APIShield applies a &lt;strong&gt;safe fallback limit&lt;/strong&gt;. That acts as a default protection layer and prevents accidental gaps in enforcement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Violation Tracking
&lt;/h2&gt;

&lt;p&gt;Blocking requests is useful, but I also wanted visibility into who was repeatedly crossing limits.&lt;/p&gt;

&lt;p&gt;So I added &lt;strong&gt;violation tracking&lt;/strong&gt; in Redis for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;per-user violations&lt;/li&gt;
&lt;li&gt;per-IP violations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This made it possible to surface analytics in the dashboard and inspect how the system was actually being used.&lt;/p&gt;




&lt;h2&gt;
  
  
  Admin Dashboard
&lt;/h2&gt;

&lt;p&gt;The project also includes an admin dashboard where rules can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;created&lt;/li&gt;
&lt;li&gt;updated&lt;/li&gt;
&lt;li&gt;deleted&lt;/li&gt;
&lt;li&gt;toggled on/off&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and where rate limit violations can be monitored.&lt;/p&gt;

&lt;p&gt;I liked this part because it turned the project from “a rate limiting middleware” into something that felt more like an internal platform.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dockerized setup
&lt;/h2&gt;

&lt;p&gt;The whole system runs with Docker Compose using separate services for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;redis&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mongo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backend1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backend2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dashboard&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This made it much easier to validate distributed behavior locally.&lt;/p&gt;

&lt;p&gt;One of the most satisfying tests was applying a global limit, sending alternating requests to both backend instances, and watching the 6th request get blocked even though the requests were split across servers.&lt;/p&gt;

&lt;p&gt;That was the point where the system actually felt distributed instead of just pretending to be.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this project taught me
&lt;/h2&gt;

&lt;p&gt;APIShield taught me a lot more than just how to implement rate limiting.&lt;/p&gt;

&lt;p&gt;It forced me to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how state should be shared across multiple backend instances&lt;/li&gt;
&lt;li&gt;when in-memory logic stops being enough&lt;/li&gt;
&lt;li&gt;how to reduce race conditions in distributed systems&lt;/li&gt;
&lt;li&gt;when to move logic closer to the data store&lt;/li&gt;
&lt;li&gt;how to design backend systems that are configurable instead of hardcoded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also reminded me that some of the most interesting engineering problems aren’t always flashy user-facing features. Sometimes they’re the quiet infrastructure pieces that keep everything else stable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Rate limiting is one of those things users rarely notice when it works well, but systems definitely notice when it doesn’t.&lt;/p&gt;

&lt;p&gt;Building APIShield gave me hands-on experience with distributed backend design, Redis scripting, and the tradeoffs behind different rate limiting strategies. It also made me appreciate how much engineering depth can hide behind a feature that, on the surface, sounds as simple as “block requests after a certain limit.”&lt;/p&gt;

&lt;p&gt;If I continue iterating on this project, I’d love to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;richer analytics dashboards&lt;/li&gt;
&lt;li&gt;per-plan throttling for SaaS-style use cases&lt;/li&gt;
&lt;li&gt;Redis Cluster support&lt;/li&gt;
&lt;li&gt;stronger observability and failure-mode handling&lt;/li&gt;
&lt;li&gt;benchmarking different strategies under load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve built something similar, or would approach rate limiting differently, I’d love to hear your thoughts.&lt;/p&gt;

&lt;p&gt;If you want to explore the implementation in more detail, the project is here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ankita007-coder/API_Shield" rel="noopener noreferrer"&gt;APIShield&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>redis</category>
      <category>node</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
