<?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: Subhajit Gorai</title>
    <description>The latest articles on DEV Community by Subhajit Gorai (@subhajit_gorai_66abca05f3).</description>
    <link>https://dev.to/subhajit_gorai_66abca05f3</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%2F3819325%2F8dd78c7d-23d7-4984-b2c5-32c259e2c996.jpg</url>
      <title>DEV Community: Subhajit Gorai</title>
      <link>https://dev.to/subhajit_gorai_66abca05f3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/subhajit_gorai_66abca05f3"/>
    <language>en</language>
    <item>
      <title>Wanna Learn Web Development?</title>
      <dc:creator>Subhajit Gorai</dc:creator>
      <pubDate>Tue, 02 Jun 2026 07:14:17 +0000</pubDate>
      <link>https://dev.to/subhajit_gorai_66abca05f3/wanna-learn-web-development-52ko</link>
      <guid>https://dev.to/subhajit_gorai_66abca05f3/wanna-learn-web-development-52ko</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Original article: &lt;a href="https://www.opencanvas.institute/p/wanna-learn-web-development-6a19a10bc7d49a7b9a4eda21" rel="noopener noreferrer"&gt;https://www.opencanvas.institute/p/wanna-learn-web-development-6a19a10bc7d49a7b9a4eda21&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you want to start web dev. Nice :)&lt;br&gt;
What kind of developer you wanna be? &lt;/p&gt;

&lt;p&gt;You got inspired by some great 3D, WebGL website on &lt;a href="https://www.awwwards.com" rel="noopener noreferrer"&gt;awwwards&lt;/a&gt; and now wanna see yourself doing the same... &lt;strong&gt;Creative Developer / Design Engineer / Frontend Engineer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You are thrilled with backend systems and wanna design a highly scalable site capable of handling millions of concurrent requests... &lt;strong&gt;Backend Engineer / Distributed Systems Engineer / Site Reliability Engineer (SRE)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You wanna develop something, build your own, or maybe build every dev aspect like a &lt;strong&gt;Fullstack Engineer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Or maybe you are into machine learning or other domains and wanna deploy something real quick. &lt;strong&gt;Project Need&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Find the one which best suits you.&lt;/p&gt;

&lt;p&gt;If it is &lt;strong&gt;Project Need&lt;/strong&gt; then you don't have to spend your time learning web dev things. Right now lots of frameworks and AI tools exist. Give a good prompt, tell your target requirement specifically, tell the AI not to overdo it, and it will be done. My suggestion is to focus on your main project goal, not the presentation.&lt;/p&gt;

&lt;p&gt;Also worth noting: I mentioned some general and specialised resources but this might not be the best article if you are into a serious guide on how to make "awwwards level designs and sites". My journey is Fullstack/SRE kind. So it's prone to those aspects.&lt;/p&gt;
&lt;h2&gt;
  
  
  Must Know for Everyone
&lt;/h2&gt;

&lt;p&gt;HTML, CSS, JS -&amp;gt; Must learn&lt;/p&gt;

&lt;p&gt;Initially have an idea of HTML and CSS, learn basic things only. You will learn other things while developing projects. Don't try to do it all from the start.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;u&gt;First HTML&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;quick guide:&lt;/strong&gt; Bro Code is what I followed as it was short. You can watch &lt;a href="https://www.youtube.com/results?search_query=html+tutorial" rel="noopener noreferrer"&gt;any&lt;/a&gt;. &lt;br&gt;
&lt;a href="https://youtu.be/HD13eq_Pmp8?si=4g6dJOpcw6TY1LkA" rel="noopener noreferrer"&gt;Bro code html&lt;/a&gt;, &lt;a href="https://youtu.be/HcOc7P5BMi4?si=N_Oh20h-e3z8rT5d" rel="noopener noreferrer"&gt;apna college&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;best for in depth:&lt;/strong&gt; &lt;a href="https://web.dev/learn/html/" rel="noopener noreferrer"&gt;https://web.dev/learn/html/&lt;/a&gt; [go through some of it at first]&lt;/p&gt;

&lt;p&gt;Have clarity on basic tags like h tags, p, div, span, code, pre, strong&lt;br&gt;
ul, ol, li etc. Also some basic meta tags. like viewport, title, description, icon, link tags etc etc. &lt;strong&gt;And always follow the semantics and proper order of tags.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's a basic boilerplate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Document Title&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"style.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello, World!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"script.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;For &lt;code&gt;vs code&lt;/code&gt; download some extension for getting boilerplate code. In &lt;code&gt;zed&lt;/code&gt; it already exists. &lt;code&gt;!+enter / !+tab&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;Then CSS&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;It's vast. Here it's important that you don't try to go through all of it at first. It's inefficient and not fun that way. You will learn with projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;quick guide:&lt;/strong&gt; &lt;a href="https://youtu.be/wRNinF7YQqQ?si=l5UZCNRdV5Ulsqr6" rel="noopener noreferrer"&gt;bro code&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;best playlist:&lt;/strong&gt; covers things that are vastly sufficient, like layouting, positioning, flexbox, grid etc. &lt;a href="https://www.youtube.com/playlist?list=PLlKKnT3UqNu0RWvCCZBnSPfhsepQ5BYN3" rel="noopener noreferrer"&gt;slaying the dragon&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;detailed docs:&lt;/strong&gt; &lt;a href="https://web.dev/learn/css" rel="noopener noreferrer"&gt;https://web.dev/learn/css&lt;/a&gt;, mdn docs, w3school&lt;/p&gt;

&lt;p&gt;learn &lt;a href="https://sass-lang.com/guide/" rel="noopener noreferrer"&gt;SCSS&lt;/a&gt; and &lt;a href="https://tailwindcss.com/docs/styling-with-utility-classes" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; too. Those are simple and at most will take an hour each for understanding. Ask any AI to reference them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you make fairly large projects you will understand why Tailwind is a lifesaver. You'll enjoy it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;so, how to center a div? (qn for you)&lt;/em&gt; &lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;Now JavaScript&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;This is the most vital, and it's gonna be of super use. Do not skip and jump to React unless you are comfortable in it. Previous HTML, CSS can be done in a day but here spend one week at minimum. It will take 1-6 weeks depending on your current programming knowledge.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;note: you need to properly understand &lt;a href="https://www.geeksforgeeks.org/dsa/introduction-of-object-oriented-programming/" rel="noopener noreferrer"&gt;Object Oriented Programming&lt;/a&gt; for this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;best:&lt;/strong&gt; &lt;a href="https://web.dev/learn/javascript" rel="noopener noreferrer"&gt;https://web.dev/learn/javascript&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;good if you don't like reading:&lt;/strong&gt; &lt;a href="https://www.youtube.com/playlist?list=PLu71SKxNbfoBuX3f4EOACle2y-tRC5Q37" rel="noopener noreferrer"&gt;chai or code js playlist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Must have clarity with anonymous functions, objects, destructuring, realms, prototypal inheritance, js execution, event loop, task queue, micro task queue, promises in JS.&lt;br&gt;
also know IIFE, closures and similar things. &lt;/p&gt;

&lt;p&gt;These visualisations by &lt;a href="https://www.youtube.com/@theavocoder/videos" rel="noopener noreferrer"&gt;Lydia&lt;/a&gt; are great: &lt;a href="https://youtu.be/eiC58R16hb8?si=j2F8h26JJq79flJH" rel="noopener noreferrer"&gt;event loop&lt;/a&gt;, &lt;a href="https://youtu.be/6Ixyltr8_R0?si=qYVutzMD413Dv1uA" rel="noopener noreferrer"&gt;closures&lt;/a&gt;, &lt;a href="https://youtu.be/zdGfo6I1yrA?si=vonj32EKP2JvREK4" rel="noopener noreferrer"&gt;execution context&lt;/a&gt;, &lt;a href="https://youtu.be/Xs1EMmBLpn4?si=Ryv889XU9P4o6eZ_" rel="noopener noreferrer"&gt;promise execution&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, Document Object Model (DOM) is an essential part for web dev. You can learn it from anywhere. My recommendations will be: &lt;a href="https://www.youtube.com/watch?v=aewQJ-sK9SU" rel="noopener noreferrer"&gt;designcourse&lt;/a&gt;, &lt;a href="https://youtu.be/7zcXPCt8Ck0?si=OyeWAPwYUQsEKzP7" rel="noopener noreferrer"&gt;apna college&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;u&gt;Quick project:&lt;/u&gt;&lt;br&gt;
After learning HTML, CSS and JS make sure to build something with it. It's vital. I recommend this: &lt;a href="https://www.youtube.com/watch?v=QRrPE9aj3wI&amp;amp;t=2s" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=QRrPE9aj3wI&amp;amp;t=2s&lt;/a&gt; (html, css)&lt;br&gt;
Or anything you wish. But do make.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Getting less popular in vibe coding era, but here are some icon libraries you'll need for sure while building projects with html &amp;amp; css.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;svgrepo&lt;/strong&gt;: &lt;a href="https://www.svgrepo.com" rel="noopener noreferrer"&gt;https://www.svgrepo.com&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;hero icons&lt;/strong&gt;: &lt;a href="https://heroicons.com" rel="noopener noreferrer"&gt;https://heroicons.com&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;lucide&lt;/strong&gt;: &lt;a href="https://lucide.dev" rel="noopener noreferrer"&gt;https://lucide.dev&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that HTML, CSS and JS are done and if you honestly didn't skip JS, you are already a good web developer. Not kidding.&lt;/p&gt;

&lt;p&gt;It's good to have some knowledge on browser internals for everyone, maybe watch this: &lt;a href="https://youtu.be/5rLFYtXHo9s?si=KaUl08CMwBSbqsSv" rel="noopener noreferrer"&gt;https://youtu.be/5rLFYtXHo9s?si=KaUl08CMwBSbqsSv&lt;/a&gt;&lt;br&gt;
For more go to the frontend section below.&lt;/p&gt;




&lt;h2&gt;
  
  
  Specialisation
&lt;/h2&gt;

&lt;p&gt;Now there is no such bound here, like "for frontend learn all of this, for backend that". No. You can pick any tech from both frontend and backend sides. It's a mix.&lt;br&gt;
Your specialisation will be on your depth of knowledge in the technologies you chose.&lt;/p&gt;

&lt;p&gt;So here I'm listing the best resources I found.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;u&gt;React&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://react.dev" rel="noopener noreferrer"&gt;https://react.dev&lt;/a&gt; (best source, nothing else needed). Have clarity on useState, useEffect, useRef, useContext, useMemo.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chai or code&lt;/code&gt; react playlist is also good.&lt;br&gt;
&lt;a href="https://youtube.com/playlist?list=PLC3y8-rFHvwgg3vaYJgHGnModB54rxOk3&amp;amp;si=jY8G_EIAS5Iva5jT" rel="noopener noreferrer"&gt;https://youtube.com/playlist?list=PLC3y8-rFHvwgg3vaYJgHGnModB54rxOk3&amp;amp;si=jY8G_EIAS5Iva5jT&lt;/a&gt; this is fine too. Search on YT, watch any.&lt;/p&gt;

&lt;p&gt;Must learn &lt;a href="https://tanstack.com" rel="noopener noreferrer"&gt;Tanstack&lt;/a&gt; alongside it. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/Ua__7-x6MWs?si=tH5QcJGCZg6eXUqW" rel="noopener noreferrer"&gt;tanstack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/mPaCnwpFvZY?si=k54m3Y5GLDV59riF" rel="noopener noreferrer"&gt;tanstack query&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;State management&lt;/strong&gt;: Once your app grows, prop drilling gets painful. useState alone stops scaling.&lt;/p&gt;

&lt;p&gt;Options: Zustand, Redux Toolkit, Jotai. For most projects Zustand is the right choice. Minimal API, no boilerplate. Redux only makes sense if the team is large or the state is genuinely complex. Jotai if you want atomic granularity.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't add state management until you actually need it. Premature abstraction gets problematic.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;Routing&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;React Router v6 is standard. Have clarity on nested routes, loaders, and the difference between client-side and server-side navigation. Check HashRouter too.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;u&gt;Browser internals&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;Important, not optional. &lt;br&gt;
Understand the event loop, call stack, task queue, microtask queue. Async JS knowledge should be concrete. Knowing why &lt;code&gt;Promise.then&lt;/code&gt; fires before &lt;code&gt;setTimeout&lt;/code&gt; matters when you're debugging real bugs.&lt;/p&gt;

&lt;p&gt;How the render pipeline, hydration works (&lt;code&gt;parse HTML -&amp;gt; build DOM -&amp;gt; CSSOM -&amp;gt; layout -&amp;gt; paint -&amp;gt; composite&lt;/code&gt;). This directly helps you write performant UI.&lt;/p&gt;

&lt;p&gt;Repaint vs reflow. Avoiding layout thrash. These come up.&lt;/p&gt;

&lt;p&gt;check &lt;a href="https://browser.engineering" rel="noopener noreferrer"&gt;https://browser.engineering&lt;/a&gt;, it's really great.&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;PWAs&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;Now you can have almost app like experience with dev skills only. Progressive Web Applications are for this. Try adding a simple &lt;code&gt;manifest.json&lt;/code&gt; and you'll see the difference.&lt;/p&gt;

&lt;p&gt;Best source to learn more: &lt;a href="https://web.dev/articles/what-are-pwas" rel="noopener noreferrer"&gt;web.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also you can use &lt;strong&gt;capacitor.js&lt;/strong&gt; etc frameworks to haven an proper android apk.&lt;/p&gt;

&lt;p&gt;some useful sites for generating pwa icons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://icon-gen.netlify.app" rel="noopener noreferrer"&gt;https://icon-gen.netlify.app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pwabuilder.com/imageGenerator" rel="noopener noreferrer"&gt;https://www.pwabuilder.com/imageGenerator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://icon.kitchen" rel="noopener noreferrer"&gt;https://icon.kitchen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://romannurik.github.io/AndroidAssetStudio/icons-launcher.html" rel="noopener noreferrer"&gt;https://romannurik.github.io/AndroidAssetStudio/icons-launcher.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;some for pwa to apk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pwabuilder.com" rel="noopener noreferrer"&gt;pwabuilder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;not necessary if you are just starting&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;u&gt;TypeScript&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;When working with large react projects you will understand the need of type safety in React. TS saves you from this fustration. Its a superset of JS.&lt;/p&gt;

&lt;p&gt;Core things to have clarity on: primitive types, interfaces vs types, generics, utility types (Partial, Pick, Omit, Record), and how to type props and hooks in React.&lt;/p&gt;

&lt;p&gt;Sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;best: &lt;a href="https://www.typescriptlang.org/docs/handbook/intro.html" rel="noopener noreferrer"&gt;TS Handbook&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;for React specifically: &lt;a href="https://www.totaltypescript.com/" rel="noopener noreferrer"&gt;Total TypeScript by Matt Pocock&lt;/a&gt;, he has free tutorials and they are genuinely the best on typing React components, hooks, and event handlers.&lt;/li&gt;
&lt;li&gt;for practice, make projects, you can use this too, its great. &lt;a href="https://github.com/type-challenges/type-challenges" rel="noopener noreferrer"&gt;https://github.com/type-challenges/type-challenges&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;Safe to skip for starters&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;u&gt;IndexedDB&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;In case you need more than ~5 MB of your Local Storage, this gives you a lot more. Async and no sql.&lt;br&gt;
Dexie is a great wrapper for it: &lt;a href="https://dexie.org" rel="noopener noreferrer"&gt;dexie.js&lt;/a&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Definitely skip if you are just starting&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;u&gt;WebAssembly (WASM)&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;When JS performance is a bottleneck. Image processing, audio, physics simulation, in-browser ML inference. If you know Rust you can compile to WASM and call it from JS. The interop is straightforward.&lt;/p&gt;

&lt;p&gt;Most projects don't need it. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want a real hands-on target: take a computationally heavy function you've written in JS, rewrite it in Rust, compile to WASM, and benchmark the difference. You'll never forget what WASM is for.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;animations&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;Its honestly not my turf. Suggestion would be if you want motion, look into Framer Motion for React. GSAP if you want timeline control. Three.js / React Three Fiber for 3D. These are rabbit holes, go in only if that's your direction.&lt;/p&gt;




&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;p&gt;Here DSA &amp;amp; DEV intersects. Try buillding any core backend tech from scratch. You are gonna love it. &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;u&gt;SQL and DBMS&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;Understand joins deeply, understand indexing (clustered vs non-clustered, when composite indexes help), understand query plans.&lt;br&gt;
Have clear idea of Normalization &amp;amp; Denormalization, use based on requirements.&lt;/p&gt;

&lt;p&gt;Learning PostgreSQL is best for now. MySQL is fine too but Postgres has better extensions, better JSON support, better window functions.&lt;/p&gt;

&lt;p&gt;resources: &lt;a href="https://www.postgresqltutorial.com" rel="noopener noreferrer"&gt;https://www.postgresqltutorial.com&lt;/a&gt;, CMU 15-445 lectures on YouTube for depth.&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;MongoDB&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;Do SQL first. Then only you will get this properly. Good for flexible schema, rapid prototyping, and when your data is naturally hierarchical.&lt;/p&gt;

&lt;p&gt;Dont use MongoDB only to avoid schema design. That's not a valid reason. If your data is relational, use a relational DB.&lt;/p&gt;

&lt;p&gt;sufficient: &lt;a href="https://www.youtube.com/watch?v=4EjKroJCpFA&amp;amp;list=PLA3GkZPtsafZydhN4nP0h7hw7PQuLsBv1" rel="noopener noreferrer"&gt;engineering digest playlist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have clarity on:  Query operators like $match, $group, $lookup, $unwind, aggregation pipelines. &lt;br&gt;
Go through mongoose &amp;amp; mongodb client. &lt;/p&gt;

&lt;p&gt;The data objects returned by mongoose have to go through hydration which takes time and can be a bottleneck for high throughput / rps. Use lean in those cases to make data retrival much faster. Aggregation pipelines do it by default.&lt;/p&gt;

&lt;p&gt;Here is one of my repo to see production ready use: &lt;a href="https://github.com/Dream-World-Coder/opencanvas/tree/main/server/src" rel="noopener noreferrer"&gt;opencanvas backend&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;Flask&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;Finally, some proper web development framework right? after all that...&lt;br&gt;
Flask is the best framework I have personally worked with. It is tidy, sufficient, has everything, you have proper customisation. Truely gold. But it was rabit hole for me. See how it works out for you. &lt;/p&gt;

&lt;p&gt;Learn flask in depth, rest will be very easy to get then afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;core things to have clarity upon:&lt;/strong&gt; request response cycle, templating, file serving, request handling, user session, cookies, csrf/xss etc common attacks, blueprints and factory pattern and database integration, migrations&lt;/p&gt;

&lt;p&gt;Trust me. knowing these in flask, you will understand any framework. Everything will be relateable.&lt;/p&gt;

&lt;p&gt;sources: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PL-osiE80TeTs4UjLw5MM6OjgkjFeUxCYH" rel="noopener noreferrer"&gt;Corey Schafer's playlist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;docs: &lt;a href="https://flask.palletsprojects.com/en/stable/" rel="noopener noreferrer"&gt;https://flask.palletsprojects.com/en/stable/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;sqlalchemy docs: &lt;a href="https://docs.sqlalchemy.org/en/20/intro.html" rel="noopener noreferrer"&gt;https://docs.sqlalchemy.org/en/20/intro.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;mrigul grinberg's book is excellent. Give it a read, on portions you need clarity: &lt;a href="https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world" rel="noopener noreferrer"&gt;web version&lt;/a&gt;, &lt;a href="https://github.com/Dream-World-Coder/dev-resources" rel="noopener noreferrer"&gt;pdf&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;FastAPI&lt;/u&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://fastapi.tiangolo.com" rel="noopener noreferrer"&gt;Official FastAPI Documentation&lt;/a&gt;&lt;/strong&gt;: Good, interactive guide covering basics to advanced topics like dependency injection and security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://fastapi-users.github.io/fastapi-users/" rel="noopener noreferrer"&gt;Fastapi-users&lt;/a&gt;&lt;/strong&gt;: Library for ready to use registration and authentication systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production Templates &amp;amp; Architecture&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/fastapi/full-stack-fastapi-template" rel="noopener noreferrer"&gt;Full Stack FastAPI Template&lt;/a&gt;&lt;/strong&gt;: Official boilerplate by fastapi creator showing FastAPI, PostgreSQL, Docker, and frontend integration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/zhanymkanov/fastapi-best-practices" rel="noopener noreferrer"&gt;FastAPI Best Practices&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;I recommend learning Flask or Other Frameworks first&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;u&gt;Django&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;Flask is a lightweight micro framework that relies on a collection of external modules, whereas Django provides a complete ecosystem, often said as "batteries-included".&lt;/p&gt;

&lt;p&gt;Core Resources&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.djangoproject.com/" rel="noopener noreferrer"&gt;Official Django Documentation&lt;/a&gt;&lt;/strong&gt;: Best reference for Django, featuring detailed explanations of the ORM, routing, and security features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.django-rest-framework.org/" rel="noopener noreferrer"&gt;Django REST Framework (DRF)&lt;/a&gt;&lt;/strong&gt;: Standard toolkit for building Web APIs on top of Django.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production Templates &amp;amp; Architecture&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/cookiecutter/cookiecutter-django" rel="noopener noreferrer"&gt;Cookiecutter Django&lt;/a&gt;&lt;/strong&gt;: Most popular framework for jumpstarting production ready Django projects quickly. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/HackSoftware/Django-Styleguide" rel="noopener noreferrer"&gt;HackSoft Django Styleguide&lt;/a&gt;&lt;/strong&gt;: Great set of rules and architectural patterns that separate business logic from models and views for large scale Django apps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;Express&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;Official &amp;amp; Core Resources&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Official Express Documentation&lt;/a&gt;&lt;/strong&gt;: Best guide for routing, middleware, and request handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production Templates &amp;amp; Architecture&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/hagopj13/node-express-boilerplate" rel="noopener noreferrer"&gt;Node Express Boilerplate by hagopj13&lt;/a&gt;&lt;/strong&gt;:  Featuring built in JWT authentication, Jest testing, Docker support, and a centralized error handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/foyzulkarim/nodejs-boilerplate" rel="noopener noreferrer"&gt;Node.js Boilerplate by foyzulkarim&lt;/a&gt;&lt;/strong&gt;:  Best practices for folder architecture, modular routing, and clean code separation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=6_wfq76CG6g" rel="noopener noreferrer"&gt;Building a Production Ready Node.js Boilerplate&lt;/a&gt;&lt;br&gt;
This is good. Give it a watch if facing trouble understanding.&lt;/p&gt;

&lt;p&gt;Also here is one of my repo to see production ready use: &lt;a href="https://github.com/Dream-World-Coder/opencanvas/tree/main/server/src" rel="noopener noreferrer"&gt;opencanvas backend&lt;/a&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Skip for Starters&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;u&gt;System design&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;This is something people treat as interview prep only. Don't do that.&lt;br&gt;
Have genuine clarity on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when to use serverless (bursty traffic, low baseline, short execution time). Consider cold starts. Do not use serverless for latency sensitive always on services.&lt;/li&gt;
&lt;li&gt;horizontal vs vertical scaling &lt;/li&gt;
&lt;li&gt;rate limiting, API gateways, load balancers, cdns&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;Redis&lt;/u&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;chai or code: &lt;a href="https://youtu.be/5YqP18Gyop0?si=H3BC7GZjKuQzX9-w" rel="noopener noreferrer"&gt;https://youtu.be/5YqP18Gyop0?si=H3BC7GZjKuQzX9-w&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;web dev simplified: &lt;a href="https://youtu.be/jgpVdJB2sKQ?si=TX4njkS6h_tf6vh-" rel="noopener noreferrer"&gt;https://youtu.be/jgpVdJB2sKQ?si=TX4njkS6h_tf6vh-&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;real situation example: Read through this section. You will understand the need of redis in real environment. &lt;a href="https://www.opencanvas.institute/p/from-1993-to-17007-requests-per-second-how-i-optimised-a-nodejs-mongodb-backend-at-scale-69ad48fc4684635da9b4c72c#7-in-memory-ttl-cache-with-intelligent-invalidation" rel="noopener noreferrer"&gt;https://www.opencanvas.institute/p/from-1993-to-17007-requests-per-second-how-i-optimised-a-nodejs-mongodb-backend-at-scale-69ad48fc4684635da9b4c72c#7-in-memory-ttl-cache-with-intelligent-invalidation&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;u&gt;Next.js&lt;/u&gt;
&lt;/h4&gt;

&lt;p&gt;React but with SSR (server side rendering) and SSG (static site generation) built in. Use it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEO matters (rendered HTML, not just husk)&lt;/li&gt;
&lt;li&gt;You want your frontend and lightweight API routes in one codebase&lt;/li&gt;
&lt;li&gt;You're building a content heavy site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right now its getting used everywhere. But don't use it just because of that. If SEO doesn't matter and you don't need SSR, a plain React SPA is simpler and easier to reason about.&lt;/p&gt;

&lt;p&gt;sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;official next js docs are the best: &lt;a href="https://nextjs.org/docs" rel="noopener noreferrer"&gt;https://nextjs.org/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/learn/dashboard-app" rel="noopener noreferrer"&gt;https://nextjs.org/learn/dashboard-app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Deploying is the most awaited part. The desire to see your work on a live url is insane after completing a project. But it gets really messy sometimes. You dont find appropriate free plans, errors just keep popping out of nowhere. That's why you need to have some knowledge on production environments and practices. Discussing all that here will make it long. So for now let's be aware with these platforms briefly. I'll link a seperate article on common error fixes, troubleshooting etc here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: Best for static html css based sites and supports React etc SPAs too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt;: Best for Next.js and any frontend based project. Free analytics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netlify&lt;/strong&gt;: Similar to Vercel for static sites. I dont like it as it charges for analytics.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CloudFlare&lt;/strong&gt;: cloudflare workers and pagers are also great.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PythonAnywhere&lt;/strong&gt;: Best for small (less than 512mb) Flask/Django projects. Free tier is genuinely good. No Docker needed. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Railway&lt;/strong&gt;: They give 5$ credit for new accounts. So if you need quick demo for a short period you can get everything for free here. Its almost the new Heroku.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HuggingFace Spaces&lt;/strong&gt;: For ML demos and Gradio/Streamlit apps. If you want to show a model working, this is the fastest path.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;VPS (DigitalOcean, Hetzner, Linode)&lt;/strong&gt;: When you need actual control. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt;: Learn it. Not optional if you're serious. Containerising your apps means your deployment is reproducible regardless of where it runs. &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;docker-compose&lt;/code&gt;, basic networking between containers. That's sufficient to start.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;One last thing.&lt;/p&gt;

&lt;p&gt;The best engineers I've seen around me didn't learn the most tools. They got very good at a small set of things, built actual projects, and read enough to understand what was happening under the surface. The intersection of knowing your data structures and knowing your system is where the interesting problems live. &lt;/p&gt;

&lt;p&gt;Best of luck 🍀&lt;br&gt;
Keep building!&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>learning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From 1,993 to 17,007 RPS: How I Optimized a Node.js/MongoDB Backend on a Single Machine</title>
      <dc:creator>Subhajit Gorai</dc:creator>
      <pubDate>Thu, 12 Mar 2026 03:13:46 +0000</pubDate>
      <link>https://dev.to/subhajit_gorai_66abca05f3/from-1993-to-17007-rps-how-i-optimized-a-nodejsmongodb-backend-on-a-single-machine-3o75</link>
      <guid>https://dev.to/subhajit_gorai_66abca05f3/from-1993-to-17007-rps-how-i-optimized-a-nodejsmongodb-backend-on-a-single-machine-3o75</guid>
      <description>&lt;p&gt;I seeded a MongoDB database with 1.4 million documents and tried to break my own backend. What started as a curiosity about real-world performance turned into a months long optimisation journey - from 1,993 to 17,007 requests per second, on a single machine, with zero request failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;OpenCanvas is a platform built to bridge the gap between ResearchGate and Reddit, giving college researchers and writers a place to publish, discover, and discuss work that would otherwise never surface. Behind it sits a standard Express and MongoDB stack, nothing exotic. What made this a meaningful engineering exercise was the constraint: the database was seeded with 400,000 users, 100,000 posts, 500,000 interactions, 100,000 follows, and 320,000 comments, and the goal was to squeeze every last request per second out of a single Node.js process before reaching for additional infrastructure.&lt;/p&gt;

&lt;p&gt;The result was a journey from 1,993 RPS to 6,138 RPS on a single thread, and then to 17,007 RPS in cluster mode, all measured under aggressive autocannon stress tests against a live MongoDB-backed feed route. This article documents every decision that drove that improvement, with the actual code, the reasoning, and the numbers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub Link of the codes provided: &lt;a href="https://github.com/Dream-World-Coder/opencanvas/tree/main/server/src" rel="noopener noreferrer"&gt;https://github.com/Dream-World-Coder/opencanvas/tree/main/server/src&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Baseline: What I Was Working With
&lt;/h2&gt;

&lt;p&gt;Before any optimization, the primary bottleneck was the &lt;code&gt;/articles&lt;/code&gt; feed route. Every request was doing the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Running a &lt;code&gt;find&lt;/code&gt; query with a &lt;code&gt;skip&lt;/code&gt; offset for pagination&lt;/li&gt;
&lt;li&gt;Populating the author's data on every post document using Mongoose's &lt;code&gt;.populate()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Returning the full post &lt;code&gt;content&lt;/code&gt; field, which for article-type posts could be tens of kilobytes of Markdown&lt;/li&gt;
&lt;li&gt;No caching whatsoever&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Under a simple autocannon test with 100 connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Requests per second: 1,993
    Average Latency: 49ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under a stressed test with 500 connections and 10 pipelining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Requests per second: 2,504
    Average Latency: 1,609ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those numbers with 100,000 posts and half a million interactions in the database meant the platform would fall over under any meaningful traffic. The fixes were architectural, not superficial.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 1: Denormalization and the authorSnapshot Pattern
&lt;/h2&gt;

&lt;p&gt;The most expensive operation in the original feed query was the &lt;code&gt;.populate()&lt;/code&gt; call. Every time a list of posts was fetched, Mongoose would issue a separate query to the &lt;code&gt;users&lt;/code&gt; collection for each unique &lt;code&gt;authorId&lt;/code&gt; in the result set. At 10 posts per page, that is up to 10 additional round trips to MongoDB before the response could be sent.&lt;/p&gt;

&lt;p&gt;The fix was to embed a snapshot of the author's display data directly inside the Post document at write time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Post.js (schema)&lt;/span&gt;
&lt;span class="nx"&gt;authorSnapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;profilePicture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;authorSnapshot&lt;/code&gt; is written when a post is created or updated, always kept current, and never populated on read. The feed query now fetches everything it needs in a single collection scan. There is a known trade-off: &lt;u&gt;if a user changes their username or profile picture, older posts will briefly show stale snapshot data until they next save a post.&lt;/u&gt; For a content platform, this is an entirely acceptable consistency model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// post.js (route) - snapshot is always refreshed on save&lt;/span&gt;
&lt;span class="nx"&gt;authorSnapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;profilePicture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profilePicture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same pattern is used for comments. Rather than populating &lt;code&gt;authorId&lt;/code&gt; on every comment fetch, the &lt;code&gt;authorSnapshot&lt;/code&gt; with &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;profilePicture&lt;/code&gt; is embedded at comment creation time. At 320,000 comments in the database, the savings are significant.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 2: contentPreview and Payload Reduction
&lt;/h2&gt;

&lt;p&gt;The original feed was returning the full &lt;code&gt;content&lt;/code&gt; field of every post. A research article might be 8,000 to 15,000 characters of Markdown. Returning that for 10 posts per page, across potentially thousands of concurrent users, is an enormous waste of bandwidth and serialization time.&lt;/p&gt;

&lt;p&gt;The solution was a dedicated &lt;code&gt;contentPreview&lt;/code&gt; field, populated at write time with the first 700 characters of the content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Post.js (schema)&lt;/span&gt;
&lt;span class="nx"&gt;contentPreview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;maxlength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// post.js (route) - sliced on save, not on read&lt;/span&gt;
&lt;span class="nx"&gt;contentPreview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The feed query then selects only &lt;code&gt;contentPreview&lt;/code&gt;, never &lt;code&gt;content&lt;/code&gt;. The full content field is only fetched on the individual post page route. &lt;u&gt;This single change reduced per-response payload size by roughly 95 percent for article-type posts,&lt;/u&gt; which directly translates to higher RPS and lower average latency.&lt;/p&gt;

&lt;p&gt;The field selection in the feed route is explicit and tight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// feed.js &lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title contentPreview slug type tags readTime thumbnailUrl isPremium isPublic authorSnapshot stats createdAt updatedAt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;content&lt;/code&gt; field is never included. Every byte not sent is a byte the event loop does not have to serialize.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 3: Eliminating .populate() with .lean()
&lt;/h2&gt;

&lt;p&gt;Mongoose documents returned from a query are &lt;u&gt;full class instances.&lt;/u&gt; They carry prototype methods, virtual fields, getter and setter logic, and change-tracking overhead. For read-only endpoints, all of that is pure waste.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.lean()&lt;/code&gt; modifier tells Mongoose to return plain JavaScript objects instead of Mongoose document instances, bypassing all of that overhead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// feed.js &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title contentPreview slug type tags readTime thumbnailUrl isPremium isPublic authorSnapshot stats createdAt updatedAt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lean&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used it in other read only parts also. like &lt;code&gt;updateEngagementScore.js&lt;/code&gt; and &lt;code&gt;search.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The performance gain from &lt;code&gt;.lean()&lt;/code&gt; is especially meaningful on high-throughput routes where the same query runs thousands of times per minute. Combined with the removal of &lt;code&gt;.populate()&lt;/code&gt; via denormalization, the per-query CPU cost drops substantially.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 4: Cursor-Based Pagination vs skip()
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;skip()&lt;/code&gt; approach to pagination is one of the most common performance mistakes in MongoDB applications. When you write &lt;code&gt;.skip(500).limit(10)&lt;/code&gt;, MongoDB still has to scan through 500 documents before discarding them and returning the next 10. So, for example, a user on page 50 causes MongoDB to scan 500 documents every time. Under concurrent load, this degrades quadratically.&lt;/p&gt;

&lt;p&gt;Cursor based pagination &lt;u&gt;replaces the offset with a positional bookmark.&lt;/u&gt; The client sends the position of the last seen item, and the server queries for documents that come after that position. MongoDB can use an index to jump directly to the right location.&lt;/p&gt;

&lt;p&gt;The feed cursor is encoded as a base64 JSON object containing &lt;code&gt;createdAt&lt;/code&gt; and &lt;code&gt;_id&lt;/code&gt; of the last document seen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// feed.js &lt;/span&gt;
&lt;span class="nx"&gt;nextCursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;lastId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the next request, the server decodes this cursor and constructs a range query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// feed.js &lt;/span&gt;
&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$or&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$lt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cursorDate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cursorDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$lt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cursorId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tie-break on &lt;code&gt;_id&lt;/code&gt; handles the edge case where two posts have identical &lt;code&gt;createdAt&lt;/code&gt; timestamps, which can happen in test environments with seeded data or under very high write concurrency. This compound condition maps directly onto the compound index defined in the schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Post.js &lt;/span&gt;
&lt;span class="nx"&gt;postSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;isPublic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MongoDB can satisfy this query with a single index scan, no collection scan, no document discard. The cost of fetching page 1 and page 5,000 is identical.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;skip()&lt;/code&gt; approach is still used on lower traffic routes like paginated comments, follower lists, and collection browsers where the dataset per user is bounded and the trade-off in code simplicity is justified.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 5: Proper Indexing Strategy
&lt;/h2&gt;

&lt;p&gt;Indexes are the single highest-leverage optimization in any database-backed application. &lt;u&gt;The wrong indexes will slow writes without helping reads. The right indexes turn expensive collection scans into fast index scans.&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;The Post collection carries three compound indexes, each targeting a specific query pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Post.js &lt;/span&gt;
&lt;span class="nx"&gt;postSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isPublic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// profile page posts&lt;/span&gt;
&lt;span class="nx"&gt;postSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;isPublic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;  &lt;span class="c1"&gt;// articles feed&lt;/span&gt;
&lt;span class="nx"&gt;postSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isPublic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;  &lt;span class="c1"&gt;// topic search&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The field order within a compound index matters. The feed query always filters by &lt;code&gt;isPublic: true&lt;/code&gt; first, then sorts by &lt;code&gt;createdAt&lt;/code&gt; descending. Placing &lt;code&gt;isPublic&lt;/code&gt; first in the index allows MongoDB to immediately narrow to the public subset before doing any range scan. &lt;u&gt;If the order were reversed, the index would be far less useful.&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;The Follow and Interaction collections both carry unique compound indexes that serve double duty: they enforce data integrity (no duplication) and provide fast lookup paths for the most common read patterns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Follow.js&lt;/span&gt;
&lt;span class="nx"&gt;followSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;followerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;followingId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Interaction.js&lt;/span&gt;
&lt;span class="nx"&gt;interactionSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;interactionSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second index on Interaction (&lt;code&gt;targetId + type&lt;/code&gt;) allows efficient queries like "how many likes does this post have" without scanning the entire interactions collection for a specific user.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 6: Atomic Counters and Denormalized Stats
&lt;/h2&gt;

&lt;p&gt;A naive implementation of a likes counter would count matching Interaction documents every time the stat is needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// what not to do on every feed request&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;likeCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;countDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;like&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At 500,000 interactions in the database, this is unacceptably expensive for a field displayed on every post card in the feed.&lt;/p&gt;

&lt;p&gt;The solution is to maintain denormalized counters on the parent document and update them atomically using MongoDB's &lt;code&gt;$inc&lt;/code&gt; operator. The counter update happens at write time (when a like or dislike action occurs), so read time requires no computation at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// post.js (like route)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByIdAndUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$inc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;statField&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same pattern governs follow counts on the User document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// follow.js&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByIdAndUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;$inc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stats.followingCount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByIdAndUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetUserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;$inc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stats.followersCount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These &lt;u&gt;&lt;code&gt;$inc&lt;/code&gt; operations are atomic in MongoDB&lt;/u&gt;, meaning concurrent requests cannot produce a race condition that results in an incorrect count. The Interaction collection remains the source of truth for deduplication (enforced by the unique index), while the counters on User and Post exist purely to serve read performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 7: In-Memory TTL Cache with Intelligent Invalidation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/articles&lt;/code&gt; feed is the most read route on the platform. Under the Artillery load test configuration, 70 percent of all simulated traffic targeted this single endpoint. Sending every one of those requests to MongoDB would be wasteful given that the feed content changes slowly, not on every request.&lt;/p&gt;

&lt;p&gt;The cache implementation is a simple but well designed in-memory TTL store backed by a JavaScript &lt;code&gt;Map&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cacheService.js&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CacheService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_evictExpired&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unref&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttlSeconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ttlSeconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// lazy eviction on read&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two TTL values govern the two cached routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// cacheService.js &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TTL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ARTICLES_FEED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 30 seconds&lt;/span&gt;
  &lt;span class="na"&gt;TOP_WRITERS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// 5 minutes&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 30-second TTL on the articles feed is intentionally short. It is long enough that a viral traffic spike serves hundreds of cache hits per second without touching the database, but short enough that new posts appear within half a minute.&lt;/p&gt;

&lt;p&gt;Cache keys are constructed to account for pagination state and limit variations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// feed.js &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`articles:c&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rawCursor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:l&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means &lt;code&gt;articles:c:l10&lt;/code&gt;, &lt;code&gt;articles:cABC123:l10&lt;/code&gt;, and &lt;code&gt;articles:c:l25&lt;/code&gt; are all separate cache entries. The Artillery load test exploited this deliberately, with 30 percent of traffic using randomized limit values to generate cache misses and force DB hits, simulating worst-case conditions.&lt;/p&gt;

&lt;p&gt;The cache also supports prefix-based invalidation. When a post is created, deleted, or toggled between public and private, the entire articles feed cache is invalidated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// post.js&lt;/span&gt;
&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidatePrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;articles:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses a simple iteration over the Map's keys, deleting any entry whose key starts with the given prefix. It is $O(n)$ over the number of cached keys, but with a 30 second TTL the cache rarely holds more than a few dozen entries, making this cost negligible.&lt;/p&gt;

&lt;p&gt;The top writers route uses a single named key with a 5-minute TTL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// user.js &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CACHE_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;writers:top&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CACHE_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fromCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This key is deleted by name whenever a like or post creation/deletion event changes the underlying ranking data, ensuring correctness without waiting for TTL expiry.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 8: Aggregation Pipeline for Ranked Queries
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This is optimal but not related to the high RPS gain on &lt;code&gt;/articles&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The top writers feature computes a &lt;code&gt;likesPerPost&lt;/code&gt; ratio across all users and returns the top five. Doing this in application code would require loading all users with at least one post into memory, computing the ratio for each, sorting them, and slicing the result. In JavaScript, on a 400,000-user dataset, that would be both memory intensive and slow.&lt;/p&gt;

&lt;p&gt;MongoDB's aggregation pipeline does this entirely inside the database engine, returning only the five documents the application actually needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// user.js &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topWriters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stats.postsCount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$gt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$addFields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;likesPerPost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;$divide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$stats.likesReceivedCount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$stats.postsCount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;likesPerPost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;profilePicture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;designation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;likesPerPost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;$match&lt;/code&gt; stage filters out users with no posts before the sort, dramatically reducing the working set. The &lt;code&gt;$addFields&lt;/code&gt; stage computes the virtual &lt;code&gt;likesPerPost&lt;/code&gt; field server-side. The &lt;code&gt;$project&lt;/code&gt; stage trims the output to only what the frontend needs. Combined with the 5-minute cache on the result, this expensive aggregation runs at most once every five minutes regardless of traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimization 9: Streaming Cursor for Batch Jobs
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This is optimal but not related to the high RPS gain on &lt;code&gt;/articles&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The engagement score recalculation job runs every 15 minutes via cron and must update all 100,000 posts. The naive approach would load all 100,000 documents into memory at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// what not to do&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({}).&lt;/span&gt;&lt;span class="nf"&gt;lean&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// posts is now 100k objects in RAM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, a streaming cursor is used to process documents one at a time, batching writes into groups of 1,000 for efficiency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// updateEngagementScore.js &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_id stats createdAt updatedAt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.cursor()&lt;/code&gt; call returns an async iterable that fetches documents from MongoDB in batches internally, keeping memory usage constant regardless of collection size. The &lt;code&gt;.lean()&lt;/code&gt; combined with tight field selection via &lt;code&gt;.select()&lt;/code&gt; means each document in memory is a minimal plain object, not a full Mongoose instance.&lt;/p&gt;

&lt;p&gt;Writes are batched and dispatched using &lt;code&gt;bulkWrite&lt;/code&gt; with &lt;code&gt;ordered: false&lt;/code&gt;, which allows MongoDB to execute the updates in parallel and continue past any individual failure rather than aborting the batch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// updateEngagementScore.js &lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bulkOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bulkWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bulkOps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;updatedCount&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;bulkOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;bulkOps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This processes 100,000 posts in 100 round trips to MongoDB instead of 100,000 individual updates.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scalable Schema Design
&lt;/h2&gt;

&lt;p&gt;Performance is not only about query optimization. The schema design itself determines how well the system handles growth and viral events.&lt;/p&gt;

&lt;p&gt;Social graph data lives in a dedicated &lt;code&gt;Follow&lt;/code&gt; collection rather than as an embedded array on the User document. A user with 50,000 followers would create an enormous User document if followers were embedded, making every User read slow. The separate collection keeps User documents small and follow lookups indexed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Follow.js&lt;/span&gt;
&lt;span class="nx"&gt;followSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;followerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;followingId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, all engagement actions (likes, dislikes, saves) live in a single polymorphic &lt;code&gt;Interaction&lt;/code&gt; collection rather than per-type arrays on their target documents. A post that goes viral might receive 100,000 likes. Storing those as an embedded array would make the Post document unmanageable. The Interaction collection scales independently, and the unique compound index prevents duplicate votes at the database level, not just the application level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Interaction.js&lt;/span&gt;
&lt;span class="nx"&gt;interactionSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;URL design also reflects scalability thinking. Post URLs follow the format &lt;code&gt;/p/&amp;lt;title-slug&amp;gt;-&amp;lt;24-char-objectid&amp;gt;&lt;/code&gt;. The ObjectId is always the last segment and is the actual lookup key. The slug portion is cosmetic. This means the title can change without breaking links and the database lookup is always an O(1) ObjectId match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// post.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extractPostId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-f0-9&lt;/span&gt;&lt;span class="se"&gt;]{24}&lt;/span&gt;&lt;span class="sr"&gt;$/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;After applying all of the above, the numbers under autocannon stress tests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Pre-Optimization&lt;/th&gt;
&lt;th&gt;Post-Optimization (Single Thread)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple (100 conn)&lt;/td&gt;
&lt;td&gt;1,993 RPS&lt;/td&gt;
&lt;td&gt;6,138 RPS  (avg latency 15ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stressed (500x10)&lt;/td&gt;
&lt;td&gt;2,504 RPS&lt;/td&gt;
&lt;td&gt;6,679 RPS  (avg latency 353ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Artillery real-world simulation ran three phases totaling 8,600 virtual user requests: a 30-second warm-up at 10 req/sec, a 60-second ramp from 10 to 100 req/sec, and a 10-second viral spike at 500 req/sec.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;metric&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total requests&lt;/td&gt;
&lt;td&gt;8,600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 200 responses&lt;/td&gt;
&lt;td&gt;8,600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failures&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overall median&lt;/td&gt;
&lt;td&gt;1ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p95&lt;/td&gt;
&lt;td&gt;1ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p99&lt;/td&gt;
&lt;td&gt;2ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absolute max latency&lt;/td&gt;
&lt;td&gt;18ms (cold cache, first window only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 18ms maximum in the entire test occurred in the very first window, when the cache was cold and the first request hit MongoDB directly. After that, the worst response across the entire 102-second test, including the 500 req/sec spike, was 5ms.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cluster Mode: The Multiplier
&lt;/h2&gt;

&lt;p&gt;Node.js runs on a single thread. No matter how well the application code is optimized, a single process is bounded by one CPU core. The Node.js &lt;code&gt;cluster&lt;/code&gt; module solves this by forking one worker process per CPU core, all listening on the same port, with the OS kernel distributing incoming connections across them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NUM_WORKERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPrimary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;NUM_WORKERS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// auto-restart on crash&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*/15 * * * *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;updateEngagementScore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// cron runs only in primary&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// workers just run express&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cron job runs only in the primary process to avoid N simultaneous engagement score recalculations. Each worker maintains its own MongoDB connection pool, which MongoDB Atlas handles without issue.&lt;/p&gt;

&lt;p&gt;Cluster results under the same autocannon tests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Single Thread&lt;/th&gt;
&lt;th&gt;Cluster Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple (100 conn)&lt;/td&gt;
&lt;td&gt;6,138 RPS&lt;/td&gt;
&lt;td&gt;15,913 RPS  (avg latency 5ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stressed (500x10)&lt;/td&gt;
&lt;td&gt;6,679 RPS&lt;/td&gt;
&lt;td&gt;17,007 RPS  (avg latency 209ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The more meaningful number is the error rate: &lt;u&gt;under the stressed test, single thread produced 1,000 timeouts. Cluster mode under identical conditions produced 70&lt;/u&gt;. That is a &lt;u&gt;14x reduction in failures&lt;/u&gt;, which is what matters in production.&lt;/p&gt;

&lt;p&gt;One important caveat: the in-memory cache in &lt;code&gt;cacheService.js&lt;/code&gt; is per-process. &lt;u&gt;Each worker has its own independent cache&lt;/u&gt;. Under cluster mode, the first request on each worker will be a cache miss even if another worker has already cached the result. With 8 cores, you could theoretically have 8 simultaneous cold cache DB hits for the same cache key. This is not a correctness problem, but it does reduce cache efficiency compared to a shared external cache. This is the primary reason Redis becomes compelling at scale, which is discussed next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Comes Next: Fastify, Redis, and Nginx
&lt;/h2&gt;

&lt;p&gt;The current architecture is Express, Node.js cluster, MongoDB, and an in-memory cache. This is solid for early production but has well understood ceilings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fastify&lt;/strong&gt; is a drop in replacement for Express with a significantly faster HTTP layer. It uses a radix tree router instead of a linear scan, serializes JSON responses via compiled schemas instead of &lt;code&gt;JSON.stringify&lt;/code&gt;, and has lower per-request overhead across the board. Benchmarks consistently show Fastify handling 30 to 50 percent more requests per second than Express on identical hardware for equivalent workloads. Migrating to Fastify would require porting route and middleware definitions but would not change any MongoDB or business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt; &lt;u&gt;solves the per process cache problem&lt;/u&gt;. Moving the &lt;code&gt;cacheService&lt;/code&gt; from an in-memory Map to Redis means all workers share a single cache. A cache miss on worker 3 populates an entry that worker 7 will then hit. This also unlocks persistence across restarts, distributed invalidation across multiple server instances, and access to Redis data structures like sorted sets, which could power the engagement score ranking query directly instead of running it as a cron job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nginx&lt;/strong&gt; sits in front of Node.js and handles concerns that Node.js is not well-suited for: &lt;u&gt;TLS termination, static file serving, connection rate limiting, request buffering, and load balancing across multiple server instances&lt;/u&gt;. Offloading TLS termination to Nginx alone reduces CPU load on Node.js meaningfully under HTTPS traffic.&lt;/p&gt;

&lt;p&gt;The target architecture 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
                           |
                           | HTTPS
                           v
              +------------------------+
              |         NGINX          |
              |  TLS termination       |
              |  Static file serving   |
              |  Rate limiting         |
              |  Load balancing        |
              +------------------------+
                    |           |
               HTTP/1.1    HTTP/1.1
                    |           |
         +----------+           +----------+
         |  Server Instance 1   |  Server Instance 2   (horizontal scaling)
         |                      |
         |  +----------------+  |  +----------------+
         |  | Node Cluster   |  |  | Node Cluster   |
         |  |                |  |  |                |
         |  | W1  W2  W3 W4  |  |  | W1  W2  W3 W4  |
         |  | (Fastify)      |  |  | (Fastify)      |
         |  +----------------+  |  +----------------+
         |         |            |         |
         +---------+------------+---------+
                         |
          +--------------+--------------+
          |                             |
    +----------+                  +----------+
    |  REDIS   |                  | MongoDB  |
    |          |                  | (Atlas   |
    | Shared   |                  |  Replica |
    | Cache    |                  |  Set)    |
    | Pub/Sub  |                  |          |
    | Sessions |                  | Primary  |
    +----------+                  | Secondary|
                                  | Secondary|
                                  +----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this architecture, Nginx terminates all SSL connections and distributes traffic across multiple server instances. Each instance runs a Node.js cluster with Fastify workers. All workers on all instances share a single Redis instance for caching, session data, and pub/sub notifications. MongoDB runs as a replica set, enabling read scaling by routing read queries to secondary nodes while writes go to the primary.&lt;/p&gt;

&lt;p&gt;The path from the current setup to this architecture is incremental. Redis can be introduced first by replacing the &lt;code&gt;CacheService&lt;/code&gt; class with a Redis-backed equivalent without changing any route code. Nginx can be added as a reverse proxy with minimal configuration. Fastify migration can happen route by route. None of these changes require schema redesign or data migration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The optimization journey on OpenCanvas demonstrates that the most impactful performance improvements come from rethinking data shape and query patterns rather than from infrastructure changes. Removing &lt;code&gt;.populate()&lt;/code&gt; through denormalization, reducing payload size through field selection and content previews, eliminating skip based pagination with cursor queries, and adding a small in-memory cache together produced a 3x improvement in RPS and a 100x improvement in tail latency on the most trafficked route.&lt;/p&gt;

&lt;p&gt;The database scale, 400,000 users, 100,000 posts, and over a million secondary documents, was not a liability. It was the condition under which these optimizations were proven. A single Node.js thread, with no special hardware, handled 8,600 concurrent user requests spanning a warm-up, a ramp-up, and a 500 req/sec viral spike with zero failures and a p99 latency of 2 milliseconds.&lt;/p&gt;

&lt;p&gt;This is the ceiling of what application level optimiation can achieve. The architecture described in the final section is what pushes the ceiling higher without changing a line of business logic.&lt;/p&gt;

</description>
      <category>node</category>
      <category>mongodb</category>
      <category>backend</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
