<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" 
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>CodeKK Blog</title>
    <link>https://www.codekk.dev/blog</link>
    <description>Articles, tutorials, and insights about web development, UI design, and coding from the CodeKK community.</description>
    <language>en-us</language>
    <lastBuildDate>Fri, 11 Sep 2026 19:23:02 GMT</lastBuildDate>
    <pubDate>Thu, 10 Sep 2026 10:56:12 GMT</pubDate>
    <ttl>60</ttl>
    <atom:link href="https://www.codekk.dev/blogs/rss.xml" rel="self" type="application/rss+xml" />
    <image>
      <url>https://www.codekk.dev/logo.svg</url>
      <title>CodeKK Blog</title>
      <link>https://www.codekk.dev/blog</link>
    </image>
    
    <item>
      <title>50+ Basic Linux Commands Every Developer Should Know</title>
      <link>https://www.codekk.dev/blogs/basic-linux-commands-every-developer-should-know</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/basic-linux-commands-every-developer-should-know</guid>
      <pubDate>Thu, 10 Sep 2026 10:56:12 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[You don't need to memorize a man page to be productive in the terminal. This blog walks through the commands you'll actually reach for every day, then builds out into a full reference covering processes, networking, permissions, and more, all with real examples you can run yourself.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1789035804/uifindr/components/hkbtfejzntqxfgmi8nf2.png" alt="50+ Basic Linux Commands Every Developer Should Know" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/basic-linux-commands-every-developer-should-know">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>JavaScript</category>
      <category>linux</category>
      <category>terminal</category>
      <category>linux-commands</category>
      <category>cli</category>
      <category>command-line</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1789035804/uifindr/components/hkbtfejzntqxfgmi8nf2.png" type="image/jpeg" />
    </item>

    <item>
      <title>Higher-Order Functions in JavaScript</title>
      <link>https://www.codekk.dev/blogs/higher-order-functions-javascript</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/higher-order-functions-javascript</guid>
      <pubDate>Tue, 01 Sep 2026 18:10:40 GMT</pubDate>
      <author>vvishal0226@gmail.com (Vishal Vishwakarma)</author>
      <description><![CDATA[Higher-order functions are one of the most important ideas in JavaScript, and you're probably already using them without realizing it. This blog explains what makes a function "higher-order," why they matter, and how methods like map, filter, and setTimeout all rely on this same simple idea.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1788286070/uifindr/components/bfadehohb5fal2gafwxl.png" alt="Higher-Order Functions in JavaScript" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/higher-order-functions-javascript">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>JavaScript</category>
      <category>web-development</category>
      <category>javascript</category>
      <category>callbacks</category>
      <category>higher-order-functions</category>
      <category>functional-programming</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1788286070/uifindr/components/bfadehohb5fal2gafwxl.png" type="image/jpeg" />
    </item>

    <item>
      <title>HTTP vs HTTPS: How HTTPS Actually Keeps Your Data Safe</title>
      <link>https://www.codekk.dev/blogs/http-vs-https-how-https-keeps-data-safe</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/http-vs-https-how-https-keeps-data-safe</guid>
      <pubDate>Sun, 30 Aug 2026 14:42:12 GMT</pubDate>
      <author>vvishal0226@gmail.com (Vishal Vishwakarma)</author>
      <description><![CDATA[HTTP and HTTPS look almost identical, just one letter apart, but the difference between them matters a lot - for security, for user trust, and even for SEO. This blog explains what each one actually does, how HTTPS keeps your data safe using encryption, and what happens behind the scenes]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1788100783/uifindr/components/a04qlulnyni8hi5ewcdo.png" alt="HTTP vs HTTPS: How HTTPS Actually Keeps Your Data Safe" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/http-vs-https-how-https-keeps-data-safe">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>JavaScript</category>
      <category>web-development</category>
      <category>http</category>
      <category>web-security</category>
      <category>ssl-tls</category>
      <category>https</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1788100783/uifindr/components/a04qlulnyni8hi5ewcdo.png" type="image/jpeg" />
    </item>

    <item>
      <title>Middleware in Express.js</title>
      <link>https://www.codekk.dev/blogs/middleware-in-expressjs</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/middleware-in-expressjs</guid>
      <pubDate>Tue, 25 Aug 2026 02:51:36 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[You've used app.use(express.json()) a hundred times without asking what it actually does. This blog breaks down what middleware really is, how requests flow through a stack of them in order, and how to write your own, including the error-handling kind most tutorials skip.]]></description>
      <content:encoded><![CDATA[
        
        <h3>You've already used middleware. You just didn't know that's what it was called.</h3><p> </p><p>If you've written even one Express app, you've typed something like this:</p><p>js</p><pre><code class="language-js">app.use(express.json());</code></pre><p>You probably knew it lets you read <code>req.body</code>, and moved on. Fair enough, it works. But that one line is your first encounter with middleware, and once you understand what's actually happening there, a lot of Express starts making more sense, including why some bugs happen (like <code>req.body</code> being <code>undefined</code>) and why route order in your file actually matters.</p><h3>The problem middleware solves</h3><p>Every request that hits your Express server needs to go through some common processing before it reaches the actual logic you care about. Things like: is this request's body valid JSON? Is the user logged in? Should this request even be allowed to reach this route, or should it get rejected? Do we want to log that this request happened?</p><p>Without a shared way to handle this, you'd end up writing the same checks inside every single route handler.</p><p>js</p><pre><code class="language-js">app.get("/dashboard", (req, res) =&gt; {
  if (!req.headers.authorization) {
    return res.status(401).json({ message: "Not authorized" });
  }
  // ... actual dashboard logic
});

app.get("/settings", (req, res) =&gt; {
  if (!req.headers.authorization) {
    return res.status(401).json({ message: "Not authorized" });
  }
  // ... actual settings logic
});</code></pre><p>That's repetitive, error-prone, and gets worse as your app grows. If you have twenty protected routes, you have twenty copies of that same check, and if you ever need to change how auth works, you're editing twenty places instead of one.</p><p>Express solves this with middleware: functions that sit in between the incoming request and your final route handler, each one able to inspect, modify, or reject the request before passing it along.</p><p><code>[IMAGE: Diagram showing a request arriving at the left, passing through three labeled middleware boxes in sequence (logger, auth check, body parser), and finally reaching the route handler box on the right, placed right after this paragraph]</code></p><h3>The airport security analogy</h3><p>Think of your Express app like an airport. A passenger (the request) doesn't walk straight from the entrance to their gate (the route handler). They pass through a sequence of checkpoints first: ID verification, baggage scanning, boarding pass check. Each checkpoint does one specific job, and each one has the power to let you through to the next checkpoint, or stop you right there and send you back.</p><p>That's exactly what middleware does. A request comes in, passes through however many middleware functions you've set up, each one does its job, checks something, logs something, parses something, and then either calls <code>next()</code> to send the passenger to the next checkpoint, or ends the journey right there by sending a response, like a rejected boarding pass.</p><h3>What a middleware function actually looks like</h3><p>Every middleware function in Express has the same basic shape:</p><p>js</p><pre><code class="language-js">function myMiddleware(req, res, next) {
  // do something with req or res
  console.log(`${req.method} request to ${req.url}`);
  next(); // pass control to the next middleware or route handler
}

app.use(myMiddleware);</code></pre><p>Three parameters: <code>req</code> (the incoming request), <code>res</code> (the response you can send back), and <code>next</code> (a function you call to hand off control). That's it. That's the entire contract.</p><p>The part people forget most often is <code>next()</code>. If your middleware doesn't call <code>next()</code> and doesn't send a response either, the request just hangs there forever. The client keeps waiting, nothing times out on its own, and you'll be staring at your terminal wondering why the request never completes.</p><h3>Middleware runs in order, and order matters</h3><p>This is the part that trips up a lot of people building their first few Express apps. Middleware doesn't run all at once, it runs in the exact order you registered it, top to bottom in your code.</p><p>js</p><pre><code class="language-js">app.use(logger);        // runs first
app.use(checkAuth);     // runs second
app.use(express.json()); // runs third
app.get("/profile", getProfile); // runs last, only if all above called next()</code></pre><p><code>[IMAGE: A vertical flowchart showing the exact order above, logger then checkAuth then express.json then getProfile, with arrows labeled "next()" between each step, placed right after this code block]</code></p><p>If you put <code>checkAuth</code> before <code>express.json()</code>, but your auth check needed to read something from <code>req.body</code>, it would fail, because the body hasn't been parsed yet at that point in the chain. This is also why you'll sometimes see <code>express.json()</code> registered right at the top of the file, before almost anything else, it needs to run early so everything after it can rely on <code>req.body</code> actually being there.</p><h3>Built-in and third-party middleware</h3><p>Express ships with a few built-in middleware functions you've probably already used:</p><p>js</p><pre><code class="language-js">app.use(express.json());              // parses JSON request bodies
app.use(express.urlencoded({ extended: true })); // parses form data
app.use(express.static("public"));    // serves static files like images and CSS</code></pre><p>And the wider ecosystem has middleware for almost anything you'd want to do at the request level:</p><p>js</p><pre><code class="language-js">const cors = require("cors");
const morgan = require("morgan");
const helmet = require("helmet");

app.use(cors());     // handles cross-origin requests, covered in detail in the CORS blog
app.use(morgan("dev")); // logs every request to the console
app.use(helmet());   // sets a bunch of security-related HTTP headers</code></pre><p>If you've read <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/what-is-cors-and-how-to-fix-it-in-express">what is CORS and how to fix it</a>, that entire fix is middleware, <code>cors()</code> is just a function that inspects the request's origin and adds the right headers before passing it along, exactly the same pattern as everything else on this page.</p><h3>Writing your own middleware: an auth check example</h3><p>Here's a realistic one, checking if a request has a valid token before letting it reach a protected route:</p><p>js</p><pre><code class="language-js">function requireAuth(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];

  if (!token) {
    return res.status(401).json({ message: "No token provided" });
  }

  try {
    const decoded = verifyToken(token); // your JWT verification logic
    req.user = decoded; // attach user info to the request for later use
    next();
  } catch (err) {
    res.status(403).json({ message: "Invalid or expired token" });
  }
}

app.get("/dashboard", requireAuth, (req, res) =&gt; {
  res.json({ message: `Welcome ${req.user.name}` });
});</code></pre><p>Notice <code>requireAuth</code> is placed directly as a second argument to <code>app.get</code>, before the actual route handler. Express lets you chain as many middleware functions as you want for a single route this way. If <code>requireAuth</code> calls <code>next()</code>, the request moves on to the route handler. If it sends a response instead (like the 401 or 403 above), the route handler never runs at all. If you want to go deeper on what's actually inside that token and why it works this way, that's covered in <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/how-jwt-authentication-works">how JWT authentication works</a>.</p><p>This is also exactly why the status codes you choose here matter, a well-behaved API should be specific about why a request failed, something explained further in <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/http-status-codes-explained">HTTP status codes explained</a>.</p><h3>Error-handling middleware: the one everyone forgets exists</h3><p>There's a second kind of middleware most tutorials skip over: error-handling middleware. It looks almost identical to regular middleware, except it takes four parameters instead of three.</p><p>js</p><pre><code class="language-js">function errorHandler(err, req, res, next) {
  console.error(err.stack);
  res.status(500).json({ message: "Something went wrong" });
}

app.use(errorHandler);</code></pre><p><code>[IMAGE: A simple diagram showing a normal middleware chain on top with three parameters, and an error-handling middleware chain below it triggered by "next(err)" with four parameters, placed right after this code block]</code></p><p>Express recognizes this as error-handling middleware specifically because of the four arguments, that's not a style choice, it's how Express distinguishes it from regular middleware. You trigger it by calling <code>next(err)</code> anywhere in your app instead of <code>next()</code>:</p><p>js</p><pre><code class="language-js">app.get("/user/:id", async (req, res, next) =&gt; {
  try {
    const user = await getUserById(req.params.id);
    res.json(user);
  } catch (err) {
    next(err); // skips straight to error-handling middleware
  }
});</code></pre><p>The important rule here: error-handling middleware needs to be registered last, after all your other routes and middleware. Express walks through middleware in order, and error handlers only catch errors from things registered before them.</p><h3>Common mistakes</h3><ul><li><p><strong>Forgetting to call </strong><code>next()</code><strong>.</strong> The request just hangs, no error, no response, nothing. This is one of the most confusing bugs to debug for the first time because nothing actually crashes.</p></li><li><p><strong>Registering middleware in the wrong order.</strong> Auth checks that need <code>req.body</code> but run before <code>express.json()</code>, or route-specific middleware registered after the routes that need it.</p></li><li><p><strong>Calling </strong><code>next()</code><strong> after already sending a response.</strong> This throws a "Cannot set headers after they are sent" error, usually because a middleware both calls <code>res.send()</code> and <code>next()</code> in the same code path by mistake.</p></li><li><p><strong>Writing error-handling middleware with only three parameters.</strong> Express silently treats it as regular middleware instead, and it just never gets triggered by <code>next(err)</code>.</p></li></ul><h3>What to actually take away from this</h3><p>Middleware is just functions in a queue, each one gets a chance to look at the request, do something with it, and decide whether to pass it forward or stop it there. Once you see <code>app.use(express.json())</code> as "a function that runs on every request and calls next() when it's done," the rest of Express middleware, auth checks, logging, CORS, error handling, all follow the exact same shape. You're not learning five different concepts, you're learning one pattern applied five different ways.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/middleware-in-expressjs">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>Backend</category>
      <category>backend</category>
      <category>javascript</category>
      <category>nodejs</category>
      <category>middleware</category>
      <category>express</category>
      
    </item>

    <item>
      <title>How the Virtual DOM Works in React (And Why It Exists)</title>
      <link>https://www.codekk.dev/blogs/react-virtual-dom-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/react-virtual-dom-explained</guid>
      <pubDate>Sun, 23 Aug 2026 11:27:13 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Every React developer has heard "Virtual DOM" a hundred times, but few can explain what it actually does. This blog breaks down the real problem it solves, how React's diffing algorithm decides what to update, and why that matters for how you write components.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1787483761/uifindr/components/dqfzd0egnvsp7jthsrmt.png" alt="How the Virtual DOM Works in React (And Why It Exists)" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <h3>You change one line of state, and the whole UI just updates. How?</h3><p> </p><p>You click a button, <code>setCount(count + 1)</code> runs, and somehow only the number on screen changes. Nothing flickers, nothing reloads, nothing else on the page gets touched. If you've used React for more than a week you've seen this happen a thousand times and probably stopped thinking about it.</p><p>But something is deciding <em>what</em> changed and <em>what</em> to leave alone. That something is the Virtual DOM, and understanding how it actually works will change how you think about performance, re-renders, and those weird bugs where a list item updates the wrong row.</p><h3>The problem the Virtual DOM was built to solve</h3><p>To understand why the Virtual DOM exists, you first need to understand what it's protecting you from: the real DOM.</p><p>The DOM (Document Object Model) is the browser's live representation of your HTML page. Every <code>&lt;div&gt;</code>, every <code>&lt;p&gt;</code>, every button is a DOM node, and the browser keeps track of all of them. The problem is that DOM nodes are heavy. They carry layout information, event listeners, computed styles, and a dozen other properties. When you change something in the DOM, the browser often has to recalculate the layout of the page (this is called reflow) and repaint pixels on screen.</p><p>Now imagine you're building something like a live chat app or a dashboard that updates every second. If every small data change meant directly touching the real DOM, tearing down elements and rebuilding them, your app would feel sluggish fast. Doing this manually with plain JavaScript is exactly the kind of thing that used to make jQuery-heavy apps slow and hard to maintain.</p><p>React needed a way to update the UI often, sometimes many times a second, without paying the full cost of touching the real DOM every single time. That's the actual problem the Virtual DOM solves.</p><h3>So what is the Virtual DOM, really?</h3><p>The Virtual DOM is not a browser feature. It's not part of any web standard. It's just a plain JavaScript object that React keeps in memory, structured to look like the DOM tree, but far lighter.</p><p>Think of it like this: every time you write JSX like this,</p><p>jsx</p><pre><code class="language-jsx">function Greeting() {
  return &lt;h1 className="title"&gt;Hello, Kartik&lt;/h1&gt;;
}</code></pre><p>React doesn't turn this directly into a real DOM element. It first creates a JavaScript object that describes it, something conceptually similar to:</p><p>js</p><pre><code class="language-js">{
  type: "h1",
  props: {
    className: "title",
    children: "Hello, Kartik"
  }
}</code></pre><p>That object is cheap to create and cheap to throw away. React can create thousands of these in a fraction of the time it would take to create thousands of real DOM nodes.</p><h3>The actual analogy that makes this click</h3><p>Picture a writer working on a long document with a co-editor. Instead of the co-editor rewriting the entire document every time the writer makes one small correction, they use "track changes." The editor compares the new draft to the old one, sees exactly which sentences changed, and only marks up those specific lines. The rest of the document stays untouched.</p><p>React does the same thing with your UI. Every time state or props change, React builds a fresh Virtual DOM tree describing what the UI <em>should</em> look like now. Then it compares this new tree against the previous one, line by line, node by node, and figures out exactly what's different. Nothing gets rebuilt from scratch. Only the actual differences get applied to the real DOM.</p><p>This comparison process has a name: diffing. And applying the result to the real DOM has a name too: reconciliation.</p><h3>How diffing and reconciliation actually work</h3><p>When state changes in a component, here's the real sequence of events:</p><ol><li><p>React re-runs your component function and builds a new Virtual DOM tree.</p></li><li><p>React compares this new tree to the previous Virtual DOM tree using a diffing algorithm.</p></li><li><p>React calculates the minimal set of changes needed, this is often called the "patch."</p></li><li><p>React applies only that patch to the real DOM.</p></li></ol><p>The diffing algorithm doesn't do a full, brute-force comparison of every possible tree structure. That would be too slow for large apps. Instead, React makes some practical assumptions to keep this fast:</p><ul><li><p>If two elements are of a different type (say a <code>&lt;div&gt;</code> became a <code>&lt;span&gt;</code>), React tears down the old one completely and builds a new one. It doesn't try to figure out what parts can be reused.</p></li><li><p>If two elements are the same type, React keeps the same underlying DOM node and just updates the attributes that changed.</p></li><li><p>For lists, React relies on the <code>key</code> prop to track which item is which across renders, instead of comparing them by position.</p></li></ul><p>That last point is why you'll see warnings in the console if you render a list without keys, or worse, use the array index as a key when the list can reorder. Without a stable key, React can't reliably tell whether an item moved, changed, or is brand new, and it might update the wrong DOM node with the wrong data. This becomes especially important once you're fetching and rendering dynamic lists, something covered in more depth in <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/how-to-fetch-data-in-react">how to fetch data in React</a>.</p><h3>A quick example to see it in action</h3><p>jsx</p><pre><code class="language-jsx">function Counter() {
  const [count, setCount] = useState(0);

  return (
    &lt;div&gt;
      &lt;p&gt;Count: {count}&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Increment&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre><p>Every time you click the button:</p><ul><li><p>React re-runs <code>Counter()</code> and gets a new Virtual DOM tree back.</p></li><li><p>It compares the new tree to the old one. The <code>&lt;div&gt;</code> is the same, the <code>&lt;button&gt;</code> is the same, but the text inside <code>&lt;p&gt;</code> changed from "Count: 0" to "Count: 1".</p></li><li><p>React's patch is tiny: update the text content of that one text node.</p></li><li><p>The real DOM only touches that single text node. The button, its event listener, the div, none of it gets rebuilt.</p></li></ul><p>This is exactly why React feels fast even when you're updating state constantly. You're not paying the cost of the real DOM on every render, you're paying it only where something actually changed.</p><h3>Where Fiber fits in</h3><p>Modern React (16 and above) doesn't just diff and patch in one synchronous blocking pass. It uses something called Fiber, which is a reimplementation of React's core reconciliation algorithm that lets React break rendering work into small units, pause it, prioritize it, and resume it later if needed. This is what makes features like concurrent rendering and smoother handling of large updates possible. Fiber doesn't change the core idea of the Virtual DOM, it just makes the reconciliation process smarter about scheduling. If you've written components with <code>useEffect</code> and noticed how renders and effects are ordered, that behavior ties back to this same reconciliation system, explained in more detail in <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/how-react-useeffect-actually-works">how useEffect actually works</a>.</p><h3>A misconception worth clearing up</h3><p>A lot of developers repeat "Virtual DOM is faster than the real DOM" as if it's some kind of universal law. That's not quite accurate. For a single, isolated DOM update, direct manipulation can actually be faster than going through the Virtual DOM's diffing process. The real win isn't raw speed on one update, it's predictability and efficiency at scale. When you have a complex UI with many components that can change independently, the Virtual DOM gives React a systematic way to figure out the minimal set of real DOM changes needed, without you having to manually track what changed and write that update logic yourself.</p><p>That's the actual value: you write declarative code describing what the UI should look like for any given state, and React figures out how to get the real DOM there efficiently. You stop thinking about DOM manipulation and start thinking about state, which is a big part of why frameworks like Next.js build entire rendering strategies on top of this same React core, something worth understanding further in <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/react-vs-nextjs">React vs Next.js</a>.</p><h3>What to take away from this</h3><p>The Virtual DOM isn't magic and it isn't a performance guarantee by itself. It's a practical solution to a real problem: how do you update a UI often, based on changing state, without manually managing expensive DOM operations every time. React keeps a lightweight copy of the UI in memory, compares versions of it, and applies only the necessary changes to the real DOM. Once you see it this way, a lot of React's behavior, why keys matter, why re-renders happen, why some updates feel instant, stops feeling like a black box and starts feeling like a system you can reason about.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/react-virtual-dom-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>Frontend</category>
      <category>react</category>
      <category>javascript</category>
      <category>virtual-dom</category>
      <category>reconciliation</category>
      <category>dom</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1787483761/uifindr/components/dqfzd0egnvsp7jthsrmt.png" type="image/jpeg" />
    </item>

    <item>
      <title>JavaScript Hoisting Explained (With Simple Examples)</title>
      <link>https://www.codekk.dev/blogs/javascript-hoisting-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/javascript-hoisting-explained</guid>
      <pubDate>Wed, 19 Aug 2026 05:53:56 GMT</pubDate>
      <author>vvishal0226@gmail.com (Vishal Vishwakarma)</author>
      <description><![CDATA[Hoisting is one of the first JavaScript concepts that confuses beginners, because it makes code behave in ways that don't match how it looks. This blog breaks down what hoisting actually is, how var, let, and const behave differently, and why you sometimes get "undefined" instead of an error]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1787118763/uifindr/components/mzrtdb6fmikw3knwllsa.png" alt="JavaScript Hoisting Explained (With Simple Examples)" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/javascript-hoisting-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>JavaScript</category>
      <category>web-development</category>
      <category>javascript</category>
      <category>scope</category>
      <category>hoisting</category>
      <category>temporal-dead-zone</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1787118763/uifindr/components/mzrtdb6fmikw3knwllsa.png" type="image/jpeg" />
    </item>

    <item>
      <title>JavaScript Closures - The Concept Interviewers Actually Test</title>
      <link>https://www.codekk.dev/blogs/javascript-closures-the-concept-interviewers-actually-test</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/javascript-closures-the-concept-interviewers-actually-test</guid>
      <pubDate>Tue, 18 Aug 2026 13:28:28 GMT</pubDate>
      <author>vvishal0226@gmail.com (Vishal Vishwakarma)</author>
      <description><![CDATA[Closures are one of the most asked-about JavaScript concepts in interviews, and also one of the most misunderstood. This blog explains what a closure actually is, why functions can remember variables from outside their own scope, and where closures show up in real code]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1787038385/uifindr/components/crynan1tmgfotuvv2m6m.png" alt="JavaScript Closures - The Concept Interviewers Actually Test" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/javascript-closures-the-concept-interviewers-actually-test">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>JavaScript</category>
      <category>javascript</category>
      <category>execution-context</category>
      <category>closures</category>
      <category>lexical-scope</category>
      <category>scope</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1787038385/uifindr/components/crynan1tmgfotuvv2m6m.png" type="image/jpeg" />
    </item>

    <item>
      <title>How to Build an Active Link Indicator in Next.js - Navbar and Sidebar</title>
      <link>https://www.codekk.dev/blogs/active-link-indicator-nextjs</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/active-link-indicator-nextjs</guid>
      <pubDate>Sat, 15 Aug 2026 07:44:33 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Learn how to build an active link indicator in Next.js using usePathname - works for both navbar and sidebar with clean Tailwind CSS styling.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786779828/uifindr/components/zxxtgpw0wfdbnssoue2q.png" alt="How to Build an Active Link Indicator in Next.js - Navbar and Sidebar" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <h1>How to Build an Active Link Indicator in Next.js - Navbar and Sidebar</h1><p> </p><p>If you have built a navbar in Next.js, you have probably run into this question - how do I highlight the link for the page the user is currently on? The active link indicator is one of those small UI details that makes your app feel polished and tells the user exactly where they are.</p><p>The good news is Next.js makes this surprisingly simple with one hook - <code>usePathname</code>. This blog walks through the complete setup, from creating the navbar to adding the active indicator, including a sidebar version at the end.</p><p>If you are not yet familiar with how Next.js routing works, check out the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/ssr-csr-ssg-isr-nextjs">SSR, CSR, SSG, and ISR blog</a> on codekk.dev - it covers the App Router and how pages are structured in a Next.js project.</p><hr><h2>What We Are Building</h2><p>A navbar with navigation links where the currently active route gets a visible indicator - in this case a blue bottom border. As you navigate between pages, the indicator moves to the correct link automatically.</p><p>At the end we will also build the same thing for a sidebar, using a left border instead of a bottom border.</p><hr><h2>Project Setup</h2><p>Start with a fresh Next.js project if you do not have one:</p><pre><code>npx create-next-app@latest active-link-indicator
</code></pre><p>Choose TypeScript, Tailwind CSS, and the App Router when prompted.</p><p>Your folder structure will look like this:</p><pre><code>└── active-link-indicator
    └── app
        ├── favicon.ico
        ├── globals.css
        ├── layout.tsx
        └── page.tsx
    └── public
    ├── .gitignore
    ├── next.config.ts
    ├── package.json
    └── tsconfig.json
</code></pre><hr><h2>Step 1 - Create the Navbar Component</h2><p>Create a new folder and file at <code>components/Navbar/Navbar.tsx</code>. Keeping components in their own named folders is a clean habit - it makes the project easier to navigate as it grows.</p><pre><code>└── components
    └── Navbar
        └── Navbar.tsx
</code></pre><p>Paste this starting code into <code>Navbar.tsx</code>:</p><pre><code class="language-tsx">"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import React from "react";

function Navbar() {
  const pathName = usePathname();
  console.log(pathName); // logs "/" on the home page

  const navLinks = [
    { name: "home", url: "/" },
    { name: "about us", url: "/about-us" },
    { name: "services", url: "/services" },
    { name: "blogs", url: "/blogs" },
    { name: "contact us", url: "/contact-us" },
  ];

  return (
    &lt;nav className="flex justify-between items-center px-10 py-3 border-b border-gray-400"&gt;
      &lt;div className="h-6"&gt;
        &lt;Image
          src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786772071/logo_lxnwhq.png"
          alt="CodeKK"
          width={1000}
          height={1000}
          className="w-full h-full object-contain"
          loading="eager"
        /&gt;
      &lt;/div&gt;
      &lt;ul className="flex gap-2.5"&gt;
        {navLinks.map((navLink, index) =&gt; {
          return (
            &lt;li key={index}&gt;
              &lt;Link
                href={navLink.url}
                className="text-base font-medium capitalize py-1 px-2.5"
              &gt;
                {navLink.name}
              &lt;/Link&gt;
            &lt;/li&gt;
          );
        })}
      &lt;/ul&gt;
    &lt;/nav&gt;
  );
}

export default Navbar;
</code></pre><p>There are a few things worth understanding here before moving on.</p><p><code>"use client"</code><strong> at the top</strong> - <code>usePathname</code> is a React hook, and hooks only work in Client Components. Without <code>"use client"</code>, Next.js treats this as a Server Component and the hook will throw an error. Any component that uses hooks, event listeners, or browser APIs needs this directive.</p><p><code>usePathname</code> - this hook from <code>next/navigation</code> returns the current URL path as a string. On the home page it returns <code>"/"</code>. On <code>/about-us</code> it returns <code>"/about-us"</code>. It updates automatically whenever the route changes - no manual tracking needed.</p><p><strong>The </strong><code>navLinks</code><strong> array</strong> - instead of hardcoding each link separately in JSX, we store them in an array of objects and map over them. This keeps the code clean and makes adding or removing links a one-line change.</p><hr><h2>Step 2 - Add the Navbar to Your Layout</h2><p>Open <code>app/layout.tsx</code> and import the Navbar so it appears on every page:</p><pre><code class="language-tsx">import "./globals.css";
import Navbar from "./components/Navbar/Navbar";

export default function RootLayout({
  children,
}: Readonly&lt;{
  children: React.ReactNode;
}&gt;) {
  return (
    &lt;html lang="en" className="h-full antialiased"&gt;
      &lt;body className="min-h-full flex flex-col"&gt;
        &lt;Navbar /&gt;
        {children}
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre><p>Placing the Navbar in the root layout means it renders on every single page without having to import it individually. The <code>{children}</code> slot is where each page's content goes.</p><hr><h2>Step 3 - Configure Next.js for the External Image</h2><p>Since we are loading the logo from Cloudinary, you need to tell Next.js it is an allowed image domain. Open <code>next.config.ts</code> and add this:</p><pre><code class="language-ts">import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "res.cloudinary.com",
        pathname: "/**",
      },
    ],
  },
};

export default nextConfig;
</code></pre><p>Without this, the <code>next/image</code> component will refuse to load images from external domains and throw an error.</p><hr><h2>Step 4 - Create the Test Pages</h2><p>To actually test the active indicator, you need pages to navigate between. Create these files:</p><pre><code>app/
├── about-us/
│   └── page.tsx
├── blogs/
│   └── page.tsx
├── services/
│   └── page.tsx
└── contact-us/
    └── page.tsx
</code></pre><p>The content for each is simple - just a heading to confirm which page you are on. Here is <code>about-us/page.tsx</code>:</p><pre><code class="language-tsx">import React from "react";

function AboutUs() {
  return &lt;h1 className="mt-4 text-2xl text-center"&gt;About Us&lt;/h1&gt;;
}

export default AboutUs;
</code></pre><p>Create the same pattern for <code>blogs/page.tsx</code>, <code>services/page.tsx</code>, and <code>contact-us/page.tsx</code> - just change the component name and the heading text.</p><p>Now run your dev server:</p><pre><code>npm run dev
</code></pre><p>Open the browser and check the console. You should see <code>/</code> logged on the home page.</p><p>[IMAGE: Browser showing the CodeKK navbar with Home, About Us, Services, Blogs, Contact Us links. Console open below showing "/" logged from Navbar.tsx]</p><p>Now click "About Us" in the navbar. Watch the console - it updates to <code>/about-us</code>.</p><p>[IMAGE: Browser on localhost:3001/about-us showing "About Us" heading. Console showing "/" then "/about-us" logged as navigation happened]</p><p>Click "Services" - the console shows <code>/services</code>.</p><p>[IMAGE: Browser on localhost:3001/services showing "Services" heading. Console showing the pathname updating to "/services"]</p><p>This is <code>usePathname</code> working. Every time you navigate, the hook returns the new pathname and the component re-renders with the updated value. That updated value is exactly what we will use to decide which link to highlight.</p><hr><h2>Step 5 - Adding the Active Link Indicator</h2><p>Now the actual feature. The idea is simple - compare <code>pathName</code> with each <code>navLink.url</code>. If they match, apply the active style. If they do not, apply a transparent style so the layout does not shift.</p><p>Update <code>Navbar.tsx</code> with this one addition inside the map:</p><pre><code class="language-tsx">{navLinks.map((navLink, index) =&gt; {
  const isActive = pathName === navLink.url; // this is the key line

  return (
    &lt;li key={index}&gt;
      &lt;Link
        href={navLink.url}
        className={`text-base font-medium capitalize py-1 px-2.5 border-b-2 ${
          isActive ? "border-b-[#0088d4]" : "border-b-transparent"
        }`}
      &gt;
        {navLink.name}
      &lt;/Link&gt;
    &lt;/li&gt;
  );
})}
</code></pre><p>Here is what each part does:</p><p><code>const isActive = pathName === navLink.url</code> - a simple strict equality check. If the current URL path exactly matches this link's URL, <code>isActive</code> is <code>true</code>. Otherwise it is <code>false</code>. This runs for every item in the array on every render.</p><p><code>border-b-2</code> - adds a 2px bottom border to every link. This is important. If you only added the border to the active link, the other links would shift slightly in height when they become active because the border would suddenly appear and take up space. Adding <code>border-b-2</code> to all links keeps the height consistent - only the color changes.</p><p><code>isActive ? "border-b-[#0088d4]" : "border-b-transparent"</code> - when active, the border color is blue (<code>#0088d4</code>). When inactive, the border is transparent - it still exists and takes up space, but you cannot see it. This is the trick that prevents layout shift.</p><p>[IMAGE: Browser showing the CodeKK navbar with "About Us" link highlighted with a blue bottom border, all other links having no visible border]</p><p>Navigate between pages and watch the blue border follow the current route automatically.</p><hr><h2>The Complete Navbar Component</h2><p>Here is the full updated <code>Navbar.tsx</code>:</p><pre><code class="language-tsx">"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import React from "react";

function Navbar() {
  const pathName = usePathname();

  const navLinks = [
    { name: "home", url: "/" },
    { name: "about us", url: "/about-us" },
    { name: "services", url: "/services" },
    { name: "blogs", url: "/blogs" },
    { name: "contact us", url: "/contact-us" },
  ];

  return (
    &lt;nav className="flex justify-between items-center px-10 py-3 border-b border-gray-400"&gt;
      &lt;div className="h-6"&gt;
        &lt;Image
          src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786772071/logo_lxnwhq.png"
          alt="CodeKK"
          width={1000}
          height={1000}
          className="w-full h-full object-contain"
          loading="eager"
        /&gt;
      &lt;/div&gt;
      &lt;ul className="flex gap-2.5"&gt;
        {navLinks.map((navLink, index) =&gt; {
          const isActive = pathName === navLink.url;

          return (
            &lt;li key={index}&gt;
              &lt;Link
                href={navLink.url}
                className={`text-base font-medium capitalize py-1 px-2.5 border-b-2 ${
                  isActive ? "border-b-[#0088d4]" : "border-b-transparent"
                }`}
              &gt;
                {navLink.name}
              &lt;/Link&gt;
            &lt;/li&gt;
          );
        })}
      &lt;/ul&gt;
    &lt;/nav&gt;
  );
}

export default Navbar;
</code></pre><hr><h2>Bonus - Active Link Indicator for a Sidebar</h2><p>The same concept works for a sidebar, but the design is different. Instead of a bottom border on the link, a left border on the list item makes more sense visually - it is a common pattern you see in dashboards and admin panels.</p><p>Create <code>components/Aside/Aside.tsx</code>:</p><pre><code class="language-tsx">"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";

function Aside() {
  const pathName = usePathname();

  const asideUrls = [
    { name: "all categories", url: "/all-categories" },
    { name: "product cards", url: "/loader-skeletons/product-cards" },
  ];

  return (
    &lt;aside className="shrink-0 max-w-50 w-full border-r pl-2.5 py-10"&gt;
      &lt;ul&gt;
        {asideUrls.map((asideUrl, index) =&gt; {
          const isActive = pathName === asideUrl.url;

          return (
            &lt;li
              key={index}
              className={`w-full h-fit relative after:absolute after:left-0 after:top-0 after:w-0.5 after:h-full ${
                isActive ? "after:bg-primary" : "after:bg-transparent"
              }`}
            &gt;
              &lt;Link
                href={asideUrl.url}
                className="px-3 py-1.5 text-sm font-medium capitalize block w-full h-full"
              &gt;
                {asideUrl.name}
              &lt;/Link&gt;
            &lt;/li&gt;
          );
        })}
      &lt;/ul&gt;
    &lt;/aside&gt;
  );
}

export default Aside;
</code></pre><p>The logic is identical - <code>usePathname</code>, <code>isActive</code> check, conditional class. But the styling approach is different.</p><p><code>after:absolute after:left-0 after:top-0 after:w-0.5 after:h-full</code> - this uses Tailwind's <code>after:</code> pseudo-element prefix to create a thin vertical line on the left side of the list item. The <code>after:</code> utility applies styles to the CSS <code>::after</code> pseudo-element, which is an invisible element added after the content.</p><p><code>isActive ? "after:bg-primary" : "after:bg-transparent"</code> - when active, the pseudo-element gets a background color (your primary brand color). When inactive, it is transparent. Same trick as the navbar - the element always exists, only the color toggles.</p><p>Using a pseudo-element here instead of a real element or a border keeps the layout clean. The <code>li</code> itself does not have a border that would affect its width or spacing - the indicator is painted on top using <code>position: absolute</code>.</p><hr><h2>Why This Works</h2><p>The entire feature is built on one idea: <code>usePathname</code> gives you the current route as a string. You compare that string to each link's URL. If they match, apply the active class.</p><p>No complex state management. No context. No effect. Just a hook that returns a value and a conditional class that responds to it.</p><p>The fact that <code>usePathname</code> is reactive - it updates on every navigation without you doing anything - is what makes this so clean. Next.js handles the tracking internally. You just read the value and use it.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/active-link-indicator-nextjs">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>Next Js</category>
      <category>navbar</category>
      <category>nextjs</category>
      <category>react</category>
      <category>tailwindcss</category>
      <category>app-router</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1786779828/uifindr/components/zxxtgpw0wfdbnssoue2q.png" type="image/jpeg" />
    </item>

    <item>
      <title>How to Fetch Data in React - A Complete Guide with Loading, Error, and Abort</title>
      <link>https://www.codekk.dev/blogs/how-to-fetch-data-in-react</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/how-to-fetch-data-in-react</guid>
      <pubDate>Sun, 09 Aug 2026 02:50:16 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Fetch API data in React the right way - loading, error, empty state, AbortController cleanup, retry and refresh. Step by step with real mistakes shown.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786243766/uifindr/components/qnaseudy3f0g5antlser.png" alt="How to Fetch Data in React - A Complete Guide with Loading, Error, and Abort" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/how-to-fetch-data-in-react">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>Frontend</category>
      <category>react</category>
      <category>useeffect</category>
      <category>fetch-api</category>
      <category>abortcontroller</category>
      <category>data-fetching</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1786243766/uifindr/components/qnaseudy3f0g5antlser.png" type="image/jpeg" />
    </item>

    <item>
      <title>How to Connect Supabase with Prisma ORM in Next.js</title>
      <link>https://www.codekk.dev/blogs/connect-supabase-prisma-nextjs</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/connect-supabase-prisma-nextjs</guid>
      <pubDate>Sun, 09 Aug 2026 02:50:00 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Step by step guide to connecting Supabase PostgreSQL to a Next.js project using Prisma ORM - schema setup, migrations, seeding, and Prisma Studio.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1785724736/uifindr/components/zwcsgyb7qwno5403aw4p.png" alt="How to Connect Supabase with Prisma ORM in Next.js" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>If you are building a Next.js project and you need a database, Supabase and Prisma together is one of the cleanest setups you can go with. Supabase gives you a hosted PostgreSQL database with a solid dashboard. Prisma gives you a type-safe ORM that makes querying that database feel natural in TypeScript.</p><p>This blog walks you through the entire setup - from creating a Supabase project to seeding your database with data. We will keep it practical and move step by step.</p><p>If you want to understand how Next.js itself works before diving into the database layer, check out the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/react-vs-nextjs">React vs Next.js blog</a> and the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/ssr-csr-ssg-isr-nextjs">SSR, CSR, SSG, and ISR blog</a> on codekk.dev first.</p><hr><h2>What We Are Building</h2><p>By the end of this blog you will have:</p><ul><li><p>A Supabase PostgreSQL database connected to your Next.js project</p></li><li><p>Prisma set up as your ORM with a working schema</p></li><li><p>Two database models - User and Post - migrated and ready</p></li><li><p>Seed data inserted into your database</p></li><li><p>A way to browse your data using Prisma Studio</p></li></ul><p>Let's get into it.</p><hr><h2>Step 1 - Create a Supabase Account and Project</h2><p>Before anything else, head to <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://supabase.com/">supabase.com</a> and create a free account if you do not have one already.</p><p>Once you are in, click the <strong>Create a new project</strong> button.</p><img class="" src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786065196/uifindr/components/kxz3mpgskbsdzndcuvbl.png" alt="supabase-project-setup.png"><p>You will see a project creation screen where you need to fill in your project name, set a database password, and choose a region.</p><img class="" src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786065287/uifindr/components/cuecodpgbvr82hggucih.png" alt="prisma-create-new-project.png"><p>Two things to remember here:</p><p><strong>Region</strong> - select the region closest to where your users or your server will be. This keeps latency low.</p><p><strong>Database password</strong> - copy this immediately and paste it somewhere safe like a notes app or password manager. You will need it in a later step and Supabase will not show it to you again after this screen.</p><p>Once your project is created, click the <strong>Connect</strong> button in the project dashboard.</p><img class="" src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786065366/uifindr/components/nfziiopl0eggyrykcz6n.png" alt="prisma-connect-button.png"><p>You will see several connection options. Select <strong>ORM</strong> from the tabs. Supabase will then show you two ORM options - Prisma and Drizzle. Select <strong>Prisma</strong>.</p><p>You will see two connection strings here - <code>DATABASE_URL</code> and <code>DIRECT_URL</code>. Keep this tab open. You will need to copy these in a later step.</p><hr><h2>Step 2 - Set Up Your Next.js Project</h2><p>If you do not have a Next.js project yet, create one:</p><pre><code>npx create-next-app@latest my-app
cd my-app
</code></pre><p>Choose TypeScript when prompted - Prisma works best with TypeScript and the type safety it provides is one of the main reasons to use it.</p><p>If you want a refresher on Next.js project structure and how the App Router works, the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/ssr-csr-ssg-isr-nextjs">SSR, CSR, SSG, and ISR blog</a> covers it in detail.</p><hr><h2>Step 3 - Install Prisma and Dependencies</h2><p>From your project root, run these four commands:</p><pre><code>npm install prisma --save-dev
npm install @prisma/client
npm install @prisma/adapter-pg pg dotenv
npm install --save-dev @types/pg
</code></pre><p>Here is what each one does:</p><p><code>prisma</code> - the Prisma CLI, installed as a dev dependency because you only use it during development for migrations and schema changes, not in production runtime.</p><p><code>@prisma/client</code> - the actual Prisma client that your application code uses to query the database. This one goes into production.</p><p><code>@prisma/adapter-pg</code><strong> and </strong><code>pg</code> - Prisma uses adapters to talk to different databases. Since Supabase is PostgreSQL, we need the pg adapter. <code>pg</code> is the underlying PostgreSQL client for Node.js.</p><p><code>dotenv</code> - loads your <code>.env</code> file so Prisma can read your database connection strings from environment variables.</p><p><code>@types/pg</code> - TypeScript type definitions for the <code>pg</code> package.</p><hr><h2>Step 4 - Initialize Prisma</h2><p>Now initialize Prisma in your project:</p><pre><code>npx prisma init
</code></pre><p>[IMAGE: Terminal output after running npx prisma init - showing the files created]</p><p>This command creates three things in your project:</p><ul><li><p><code>prisma/schema.prisma</code> - where you define your database models</p></li><li><p><code>prisma.config.ts</code> - Prisma configuration file</p></li><li><p><code>.env</code> - environment variables file (if it does not exist already)</p></li></ul><hr><h2>Step 5 - Add Your Database Connection Strings</h2><p>Open your <code>.env</code> file. You will see a <code>DATABASE_URL</code> placeholder already there. Remove everything in the file and replace it with the two connection strings from Supabase.</p><p>Go back to your Supabase tab where you selected Prisma as your ORM. Copy both <code>DATABASE_URL</code> and <code>DIRECT_URL</code> from there.</p><img class="" src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786065703/uifindr/components/vqsrpno8txnhoetmfhu3.png" alt="prisma-connect-to-your-project.png"><p>Paste both into your <code>.env</code> file:</p><pre><code>DATABASE_URL="your-database-url-from-supabase"
DIRECT_URL="your-direct-url-from-supabase"
</code></pre><p>Now replace the <code>[YOUR-PASSWORD]</code> placeholder in both URLs with the database password you saved when creating the project.</p><p><strong>Why two URLs?</strong> Supabase uses a connection pooler by default which is efficient for production traffic but does not support all Prisma features like migrations. <code>DATABASE_URL</code> points to the pooled connection - used by your application at runtime. <code>DIRECT_URL</code> points directly to the database - used by Prisma for migrations and seeding.</p><hr><h2>Step 6 - Configure prisma.config.ts</h2><p>Open the <code>prisma.config.ts</code> file that Prisma generated. Update it to use <code>DIRECT_URL</code> for the datasource:</p><pre><code class="language-ts">import "dotenv/config";
import { defineConfig } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  datasource: {
    url: process.env["DIRECT_URL"], // Use DIRECT_URL for direct connection to the database
  },
});
</code></pre><p>We are using <code>DIRECT_URL</code> here because <code>prisma.config.ts</code> is used for migrations - operations that need a direct connection, not a pooled one.</p><hr><h2>Step 7 - Define Your Database Schema</h2><p>Open <code>prisma/schema.prisma</code>. This is where you define what your database tables look like. Prisma calls them models.</p><p>Replace the contents with this schema:</p><pre><code class="language-prisma">generator client {
  provider        = "prisma-client-js"
  output          = "../app/generated/prisma/client"
}

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_URL")
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  authorId  Int
  author    User    @relation(fields: [authorId], references: [id])
}
</code></pre><p>Here is what each part means:</p><p><code>generator client</code> - tells Prisma to generate a TypeScript client. The <code>output</code> path is where the generated client files will go - in this case inside your <code>app/generated/prisma/client</code> folder.</p><p><code>datasource db</code> - tells Prisma it is talking to a PostgreSQL database and where to find the connection strings.</p><p><code>model User</code> - creates a <code>users</code> table with:</p><ul><li><p><code>id</code> - auto-incrementing integer, set as the primary key</p></li><li><p><code>email</code> - required unique string</p></li><li><p><code>name</code> - optional string (the <code>?</code> means nullable)</p></li><li><p><code>posts</code> - a relation field showing this user can have many posts (does not create a column - it is just for Prisma's query API)</p></li></ul><p><code>model Post</code> - creates a <code>posts</code> table with:</p><ul><li><p><code>id</code> - auto-incrementing primary key</p></li><li><p><code>title</code> - required string</p></li><li><p><code>content</code> - optional string</p></li><li><p><code>published</code> - boolean defaulting to false</p></li><li><p><code>authorId</code> - the foreign key linking to the User table</p></li><li><p><code>author</code> - the relation definition telling Prisma how the tables connect</p></li></ul><p>This is a classic one-to-many relationship - one user can have many posts. Prisma handles the JOIN logic for you when you query.</p><hr><h2>Step 8 - Run Your First Migration</h2><p>Now push this schema to your Supabase database by running:</p><pre><code>npx prisma migrate dev
</code></pre><p>Prisma will ask you to name this migration. Give it something descriptive like <code>init</code> or <code>create-users-and-posts</code>.</p><p>This command does three things:</p><ol><li><p>Compares your schema to the current state of the database</p></li><li><p>Generates a SQL migration file inside <code>prisma/migrations</code></p></li><li><p>Runs that migration against your database</p></li></ol><p>After it finishes, your <code>users</code> and <code>posts</code> tables exist in Supabase. You can open your Supabase dashboard and see them under the Table Editor.</p><p>It also generates the Prisma client code in <code>app/generated/prisma/client</code> based on your schema, so your TypeScript code gets full type safety when querying.</p><hr><h2>Step 9 - Seed Your Database</h2><p>Seeding means inserting some initial data into your database. Prisma has built-in support for this.</p><p>Create a new file at <code>prisma/seed.ts</code> and paste this code:</p><pre><code class="language-ts">import { PrismaClient, Prisma } from "../app/generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import "dotenv/config";

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL,
});

const prisma = new PrismaClient({
  adapter,
});

const userData: Prisma.UserCreateInput[] = [
  {
    name: "Sadie Sink",
    email: "ssadie0416@gmail.com",
    posts: {
      create: [
        {
          title: "Join the Prisma Discord",
          content: "https://pris.ly/discord",
          published: true,
        },
        {
          title: "Prisma on YouTube",
          content: "https://pris.ly/youtube",
        },
      ],
    },
  },
  {
    name: "Prince Vishwakarma",
    email: "prince.pv.14.2000@gmail.com",
    posts: {
      create: [
        {
          title: "Follow Prisma on Twitter",
          content: "https://www.twitter.com/prisma",
          published: true,
        },
      ],
    },
  },
];

export async function main() {
  for (const u of userData) {
    await prisma.user.create({ data: u });
  }
}

main();
</code></pre><p>A few things worth understanding here:</p><p><code>PrismaPg</code><strong> adapter</strong> - we initialize Prisma with the pg adapter and pass the <code>DATABASE_URL</code> (pooled connection) for seeding since it is a runtime operation, not a migration.</p><p><code>userData</code><strong> array</strong> - each object in this array is one user with their associated posts nested inside. Prisma's <code>create</code> API lets you create related records in a single call - no need to insert the user first, get their ID, then insert posts separately.</p><p><code>Prisma.UserCreateInput</code> - this is a TypeScript type generated by Prisma based on your schema. It ensures your seed data matches exactly what the database expects.</p><hr><h2>Step 10 - Add Seed Script to prisma.config.ts</h2><p>Now tell Prisma where to find the seed script. Open <code>prisma.config.ts</code> and add the <code>seed</code> line:</p><pre><code class="language-ts">import "dotenv/config";
import { defineConfig } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: `tsx prisma/seed.ts`, // add this line to run the seed script
  },
  datasource: {
    url: process.env["DIRECT_URL"],
  },
});
</code></pre><p>Before running the seed, install <code>tsx</code> - a tool that lets Node.js run TypeScript files directly without compiling them first:</p><pre><code>npm install --save-dev tsx
</code></pre><p>If you skip installing <code>tsx</code> and try to run the seed, you will get an error because Node.js cannot run <code>.ts</code> files directly by default.</p><p>Now run the seed:</p><pre><code>npx prisma db seed
</code></pre><hr><h2>Step 11 - Verify Your Data</h2><p>You have two ways to check that everything worked.</p><p><strong>Option 1 - Check Supabase Dashboard</strong></p><p>Open your Supabase project and go to the Table Editor. You should see your <code>users</code> and <code>posts</code> tables with the seed data inside them.</p><img class="" src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786065897/uifindr/components/uz1cdzyyxmexyureai4e.png" alt="prisma-show-tables.png"><p><strong>Option 2 - Use Prisma Studio</strong></p><p>Prisma comes with a built-in GUI for browsing your database. Run:</p><pre><code>npx prisma studio
</code></pre><p>It opens at <code>http://localhost:5555</code> (or similar port) and shows you all your tables and data in a clean interface - no need to open Supabase at all.</p><img class="" src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786065830/uifindr/components/gvq8pg0wv57hyvpyvwo3.png" alt="prisma-studio.png"><hr><h2>Using Prisma in Your Next.js Code</h2><p>Now that everything is connected, here is how you actually query your database in a Next.js Server Component:</p><pre><code class="language-ts">import { PrismaClient } from "../generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL,
});

const prisma = new PrismaClient({ adapter });

export default async function UsersPage() {
  const users = await prisma.user.findMany({
    include: {
      posts: true, // include related posts
    },
  });

  return (
    &lt;div&gt;
      {users.map(user =&gt; (
        &lt;div key={user.id}&gt;
          &lt;h2&gt;{user.name}&lt;/h2&gt;
          &lt;p&gt;{user.email}&lt;/p&gt;
          &lt;p&gt;{user.posts.length} posts&lt;/p&gt;
        &lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
}
</code></pre><p>Because this is a Server Component, the database query runs on the server before the page reaches the browser. No API route needed, no fetch call, no exposed credentials. Prisma handles the SQL, TypeScript handles the types, Next.js handles the rendering.</p><p><strong>One important note for production:</strong> Create a single Prisma client instance and reuse it across your app instead of creating a new one in every file. A common pattern is to create a <code>lib/prisma.ts</code> file that exports a single instance. This prevents connection pool exhaustion in a serverless environment like Vercel.</p><pre><code class="language-ts">// lib/prisma.ts
import { PrismaClient } from "../app/generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import "dotenv/config";

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL,
});

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const prisma =
  globalForPrisma.prisma ?? new PrismaClient({ adapter });

if (process.env.NODE_ENV !== "production") {
  globalForPrisma.prisma = prisma;
}
</code></pre><p>Import <code>prisma</code> from this file wherever you need it.</p><hr><h2>What You Have Now</h2><p>At this point your setup is complete:</p><ul><li><p>Supabase is hosting your PostgreSQL database</p></li><li><p>Prisma is managing your schema, migrations, and queries</p></li><li><p>Your Next.js app can query the database directly from Server Components</p></li><li><p>Your data is type-safe end to end - if your schema changes, TypeScript will catch anywhere your code breaks immediately</p></li></ul><p>This is a solid foundation. From here you can add more models to your schema, write more complex queries using Prisma's API, or add API routes that use the same Prisma client for client-side data fetching.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/connect-supabase-prisma-nextjs">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>Backend</category>
      <category>typescript</category>
      <category>nextjs</category>
      <category>postgresql</category>
      <category>database</category>
      <category>supabase</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1785724736/uifindr/components/zwcsgyb7qwno5403aw4p.png" type="image/jpeg" />
    </item>

    <item>
      <title>How the &apos;this&apos; Keyword Works in JavaScript</title>
      <link>https://www.codekk.dev/blogs/javascript-this-keyword-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/javascript-this-keyword-explained</guid>
      <pubDate>Thu, 06 Aug 2026 11:33:18 GMT</pubDate>
      <author>vvishal0226@gmail.com (Vishal Vishwakarma)</author>
      <description><![CDATA[The 'this' keyword confuses almost every JavaScript developer at some point because its value depends on how a function is called, not where it's written. This blog breaks down 'this' in every context - global scope, object methods, arrow functions, classes, and event handlers]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1786015720/uifindr/components/l4empzwkwworvwr7ei6p.png" alt="How the &apos;this&apos; Keyword Works in JavaScript" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/javascript-this-keyword-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>JavaScript</category>
      <category>web-development</category>
      <category>javascript</category>
      <category>arrow-functions</category>
      <category>this-keyword</category>
      <category>execution-context</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1786015720/uifindr/components/l4empzwkwworvwr7ei6p.png" type="image/jpeg" />
    </item>

    <item>
      <title>Async/Await in JavaScript - How It Actually Works and Why It Exists</title>
      <link>https://www.codekk.dev/blogs/async-await-javascript-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/async-await-javascript-explained</guid>
      <pubDate>Tue, 04 Aug 2026 02:56:19 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[How async/await works in JavaScript - async functions, await, error handling, parallel vs sequential execution, and the most common mistakes explained.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1785811826/uifindr/components/n5ctgfdih0weuicijhfq.png" alt="Async/Await in JavaScript - How It Actually Works and Why It Exists" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>If you have read the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/promises-in-javascript-explained">Promises blog</a> and the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/callback-functions-javascript-explained">Callbacks blog</a> on codekk.dev, you already understand the problem JavaScript has been trying to solve. Async operations are everywhere, and writing clean code that handles them has always been harder than it should be.</p><p>Callbacks gave us a way to handle async results but turned into nested pyramids. Promises flattened that pyramid into a readable chain. Async/await takes it one step further - it makes async code look and feel like synchronous code, while still being completely non-blocking underneath.</p><p>This is the final piece of the JavaScript async series on codekk.dev. By the end of this blog, you will understand not just how to write async/await but what it is actually doing under the hood, why it behaves the way it does, and the mistakes that trip developers up in real projects.</p><hr><h2>What Async/Await Actually Is</h2><p>Async/await is not a new async system. It is not replacing Promises or the event loop. It is <strong>syntax built directly on top of Promises</strong> that makes them easier to read and write.</p><p>Every <code>async</code> function returns a Promise. Every <code>await</code> expression pauses that function and waits for a Promise to settle. That is the entire model. Everything else follows from these two facts.</p><pre><code class="language-js">// This async function
async function greet() {
  return "Hello";
}

// Is exactly the same as this
function greet() {
  return Promise.resolve("Hello");
}

// Both are used the same way
greet().then(message =&gt; console.log(message)); // "Hello"
</code></pre><p>The <code>async</code> keyword before a function does one thing - it makes that function always return a Promise. If you return a plain value, it gets wrapped in <code>Promise.resolve()</code> automatically. If you throw an error, it gets wrapped in <code>Promise.reject()</code>.</p><hr><h2>The await Keyword - What It Actually Does</h2><p><code>await</code> can only be used inside an <code>async</code> function. It pauses the execution of that function until the Promise it is waiting for settles, then resumes with the resolved value.</p><pre><code class="language-js">async function fetchUser() {
  const response = await fetch("https://api.example.com/users/1");
  const user = await response.json();
  console.log(user);
}
</code></pre><p>Here is what happens line by line:</p><ol><li><p><code>fetch()</code> is called - it returns a Promise immediately</p></li><li><p><code>await</code> pauses <code>fetchUser</code> and hands control back to the caller</p></li><li><p>The event loop is free to do other work</p></li><li><p>When the fetch Promise resolves, <code>fetchUser</code> resumes with the response</p></li><li><p><code>response.json()</code> is called - it also returns a Promise</p></li><li><p><code>await</code> pauses again, event loop is free again</p></li><li><p>When the json Promise resolves, <code>fetchUser</code> resumes with the parsed data</p></li><li><p><code>console.log(user)</code> runs</p></li></ol><p>The function pauses twice. But the rest of your application keeps running during those pauses. This is not blocking - it is the event loop working exactly as we covered in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/javascript-event-loop-explained">JavaScript Event Loop blog</a>.</p><p>[IMAGE: Diagram showing async function execution - function starts, hits await, pauses and hands control to event loop (shown as circular arrow), event loop handles other tasks, Promise resolves, function resumes from where it paused. Labels: "Function pauses here" at await, "Resumes here" when Promise resolves]</p><hr><h2>Async/Await vs Promise Chains - The Same Thing Written Differently</h2><p>The best way to understand async/await is to see it next to the equivalent Promise chain. They are doing exactly the same thing.</p><p><strong>Promise chain version:</strong></p><pre><code class="language-js">function loadDashboard(userId) {
  return fetchUser(userId)
    .then(user =&gt; {
      return fetchOrders(user.id)
        .then(orders =&gt; ({ user, orders }));
    })
    .then(({ user, orders }) =&gt; {
      return fetchRecommendations(user.preferences)
        .then(recommendations =&gt; ({ user, orders, recommendations }));
    })
    .then(({ user, orders, recommendations }) =&gt; {
      renderDashboard(user, orders, recommendations);
    })
    .catch(err =&gt; {
      console.error("Dashboard failed to load:", err);
    });
}
</code></pre><p><strong>Async/await version:</strong></p><pre><code class="language-js">async function loadDashboard(userId) {
  try {
    const user = await fetchUser(userId);
    const orders = await fetchOrders(user.id);
    const recommendations = await fetchRecommendations(user.preferences);
    renderDashboard(user, orders, recommendations);
  } catch (err) {
    console.error("Dashboard failed to load:", err);
  }
}
</code></pre><p>Same operations. Same order. Same error handling. The async/await version reads like a simple list of steps from top to bottom. No chaining, no nesting, no callbacks. This is the whole point.</p><p>Under the hood, the JavaScript engine converts the async/await version into something similar to the Promise chain. You are writing cleaner code - the engine handles the Promise machinery for you.</p><hr><h2>Error Handling With Try/Catch</h2><p>In Promise chains, you use <code>.catch()</code> for error handling. With async/await, you use the standard JavaScript <code>try/catch</code> that you already know from synchronous code.</p><pre><code class="language-js">async function getUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);

    if (!response.ok) {
      throw new Error(`HTTP error - status: ${response.status}`);
    }

    const user = await response.json();
    return user;
  } catch (err) {
    console.error("Failed to fetch user:", err.message);
    return null;
  }
}
</code></pre><p>When any <code>await</code> inside the <code>try</code> block rejects, execution jumps to the <code>catch</code> block. Exactly like a synchronous <code>try/catch</code>.</p><p><strong>Handling errors for specific steps differently:</strong></p><p>Sometimes you want to handle errors at individual steps rather than catching everything in one place:</p><pre><code class="language-js">async function processOrder(orderId) {
  let order;

  try {
    order = await fetchOrder(orderId);
  } catch (err) {
    // This specific error we can recover from
    console.log("Order not found, creating new one");
    order = await createDefaultOrder();
  }

  // This error we want to propagate up
  const payment = await processPayment(order.total);
  return payment;
}
</code></pre><p>You can nest try/catch blocks or use separate try/catch for each step when different errors need different handling.</p><hr><h2>Async/Await With Multiple Promises - Parallel vs Sequential</h2><p>This is one of the most important things to understand with async/await, and a very common source of performance bugs.</p><p><strong>Sequential - each waits for the previous:</strong></p><pre><code class="language-js">async function loadData() {
  const users = await fetchUsers();       // waits ~300ms
  const products = await fetchProducts(); // waits ~300ms after users
  const orders = await fetchOrders();     // waits ~300ms after products
  // Total time: ~900ms
}
</code></pre><p>This is fine when each call depends on the result of the previous one. But if these three calls are completely independent of each other, you are making them wait in line for no reason.</p><p><strong>Parallel - all start at the same time:</strong></p><pre><code class="language-js">async function loadData() {
  const [users, products, orders] = await Promise.all([
    fetchUsers(),
    fetchProducts(),
    fetchOrders()
  ]);
  // Total time: ~300ms (waits for the slowest one)
}
</code></pre><p><code>Promise.all</code> starts all three requests simultaneously. You <code>await</code> the combined Promise that resolves when all three finish. The total time is roughly equal to the slowest single request instead of the sum of all three.</p><p>This is one of the biggest performance wins you can get with very little code change. If you have multiple independent async operations, run them in parallel.</p><p>[IMAGE: Timeline diagram showing two approaches - top row shows sequential awaits as three blocks one after another totaling 900ms. Bottom row shows Promise.all as three overlapping blocks all starting at the same time totaling 300ms. Labels: "Sequential - 900ms total" and "Parallel with Promise.all - 300ms total"]</p><p><strong>When to use sequential vs parallel:</strong></p><pre><code class="language-js">// Sequential is correct here - second depends on first
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id); // needs user.id

// Parallel is correct here - neither depends on the other
const [profile, settings] = await Promise.all([
  fetchProfile(userId),
  fetchSettings(userId)
]);
</code></pre><p>The rule is simple: if a request needs data from a previous request, run them sequentially. If they are independent, run them in parallel.</p><hr><h2>Async Functions in Different Contexts</h2><p>Async/await works in regular functions, arrow functions, and methods. The syntax is slightly different in each case but the behavior is the same.</p><p><strong>Regular function:</strong></p><pre><code class="language-js">async function fetchData() {
  const data = await getData();
  return data;
}
</code></pre><p><strong>Arrow function:</strong></p><pre><code class="language-js">const fetchData = async () =&gt; {
  const data = await getData();
  return data;
};
</code></pre><p><strong>Class method:</strong></p><pre><code class="language-js">class UserService {
  async getUser(id) {
    const user = await db.users.findById(id);
    return user;
  }

  async createUser(data) {
    const user = await db.users.create(data);
    return user;
  }
}
</code></pre><p><strong>In Next.js Server Components</strong> - which we covered in detail in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/ssr-csr-ssg-isr-nextjs">SSR, CSR, SSG, and ISR blog</a>:</p><pre><code class="language-jsx">// Server Component - async works directly at the component level
export default async function ProductsPage() {
  const products = await fetch("https://api.example.com/products")
    .then(r =&gt; r.json());

  return (
    &lt;div&gt;
      {products.map(product =&gt; (
        &lt;div key={product.id}&gt;{product.name}&lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
}
</code></pre><p><strong>In Express route handlers</strong> - from the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express blog</a>:</p><pre><code class="language-js">app.get("/users/:id", async (req, res) =&gt; {
  try {
    const user = await db.users.findById(req.params.id);
    if (!user) return res.status(404).json({ error: "User not found" });
    res.json(user);
  } catch (err) {
    res.status(500).json({ error: "Internal server error" });
  }
});
</code></pre><hr><h2>Top Level Await</h2><p>Traditionally, <code>await</code> could only be used inside an <code>async</code> function. If you tried to use it at the top level of a module, you would get a syntax error.</p><p>Modern JavaScript (ES2022) introduced <strong>top-level await</strong> which allows you to use <code>await</code> directly in ES modules without wrapping it in an async function.</p><pre><code class="language-js">// In an ES module (.mjs or with "type": "module" in package.json)
const data = await fetch("https://api.example.com/config").then(r =&gt; r.json());
console.log(data); // works at the top level
</code></pre><p>This is particularly useful in Node.js scripts and configuration files. In Next.js and most modern build setups, top-level await works out of the box.</p><hr><h2>Common Mistakes With Async/Await</h2><p><strong>Forgetting await:</strong></p><pre><code class="language-js">async function getUser(id) {
  const user = fetchUser(id); // missing await
  console.log(user); // logs Promise { &lt;pending&gt; } not the actual user
  return user;
}
</code></pre><p>Without <code>await</code>, you get the Promise object itself, not the resolved value. TypeScript catches this in many cases - another reason to use TypeScript as we covered in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/typescript-vs-javascript">TypeScript vs JavaScript blog</a>.</p><p><strong>Using await inside forEach:</strong></p><pre><code class="language-js">// This does NOT work as expected
async function processOrders(orders) {
  orders.forEach(async (order) =&gt; {
    await processOrder(order); // these all fire simultaneously and forEach doesn't wait
  });
  console.log("Done"); // runs before any orders are processed
}

// Use for...of instead
async function processOrders(orders) {
  for (const order of orders) {
    await processOrder(order); // waits for each one sequentially
  }
  console.log("Done"); // runs after all orders are processed
}

// Or if you want parallel processing
async function processOrders(orders) {
  await Promise.all(orders.map(order =&gt; processOrder(order)));
  console.log("Done"); // runs after all orders are processed in parallel
}
</code></pre><p><code>forEach</code> does not care about Promises. It fires all the async callbacks and moves on. Use <code>for...of</code> for sequential processing or <code>Promise.all</code> with <code>.map()</code> for parallel processing.</p><p><strong>Not handling errors:</strong></p><pre><code class="language-js">// If this rejects, the error is swallowed silently
async function riskyOperation() {
  const data = await fetchSomething();
  return data;
}

// Always wrap in try/catch or handle at the call site
async function safeOperation() {
  try {
    const data = await fetchSomething();
    return data;
  } catch (err) {
    console.error("Operation failed:", err);
    return null;
  }
}
</code></pre><p><strong>Async functions in useEffect - the wrong way:</strong></p><pre><code class="language-js">// Wrong - useEffect callback cannot be async directly
useEffect(async () =&gt; {
  const data = await fetchData();
  setData(data);
}, []);

// Correct - define async function inside and call it
useEffect(() =&gt; {
  async function load() {
    const data = await fetchData();
    setData(data);
  }
  load();
}, []);
</code></pre><p>We covered useEffect in detail in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/how-react-useeffect-actually-works">useEffect blog</a>. The async function pattern inside useEffect is one of the most common React mistakes developers hit when they first learn async/await.</p><hr><h2>How Async/Await Connects to the Event Loop</h2><p>If you read the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/javascript-event-loop-explained">JavaScript Event Loop blog</a>, you know that <code>.then()</code> callbacks go into the Microtask Queue - the high priority queue that runs before setTimeout callbacks.</p><p><code>await</code> works through the same mechanism. When a function hits <code>await</code>, it pauses and registers the rest of the function as a microtask to run when the awaited Promise resolves. This is why async/await code has the same priority as Promise chains - because it is Promise chains, just with different syntax.</p><pre><code class="language-js">async function example() {
  console.log("1 - Before await");
  await Promise.resolve();
  console.log("3 - After await");
}

example();
console.log("2 - After calling example");

// Output:
// 1 - Before await
// 2 - After calling example
// 3 - After await
</code></pre><p>When <code>example()</code> hits <code>await</code>, it pauses and the <code>console.log("2 - After calling example")</code> on the outer code runs first. Then the microtask queue runs and <code>example()</code> resumes with "3 - After await".</p><p>This is exactly the same behavior we traced through with <code>.then()</code> in the Promises blog - because the mechanism is identical.</p><hr><h2>Async/Await in Real Projects - Putting It All Together</h2><p>Here is what a real-world async/await pattern looks like in a complete Node.js and Express API - combining everything from the backend series on codekk.dev:</p><pre><code class="language-js">// Utility function for reusable async error handling
const asyncHandler = (fn) =&gt; (req, res, next) =&gt; {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Route handler - clean, readable, no try/catch clutter
app.get("/users/:id", asyncHandler(async (req, res) =&gt; {
  const user = await db.user.findUnique({
    where: { id: req.params.id },
    include: { posts: true }
  });

  if (!user) {
    return res.status(404).json({ error: "User not found" });
  }

  res.json(user);
}));

// Running multiple queries in parallel
app.get("/dashboard/:userId", asyncHandler(async (req, res) =&gt; {
  const { userId } = req.params;

  const [user, orders, notifications] = await Promise.all([
    db.user.findUnique({ where: { id: userId } }),
    db.order.findMany({ where: { userId }, take: 10 }),
    db.notification.findMany({ where: { userId, read: false } })
  ]);

  if (!user) {
    return res.status(404).json({ error: "User not found" });
  }

  res.json({ user, orders, notifications });
}));

// Global error handler catches anything that slips through
app.use((err, req, res, next) =&gt; {
  console.error(err.stack);
  res.status(500).json({ error: "Internal server error" });
});
</code></pre><p>The <code>asyncHandler</code> wrapper is a common pattern in Express - it wraps any async route handler and forwards any unhandled rejections to the next error handler. This removes the need for <code>try/catch</code> in every single route handler.</p><hr><h2>The Complete Picture - JavaScript Async From Start to Finish</h2><p>Now that you have read through the entire async series on codekk.dev, here is how all the pieces fit together:</p><p><strong>Callbacks</strong> - the original pattern. Functions passed to be called later. Still used everywhere for event listeners, array methods, and simple cases.</p><p><strong>Promises</strong> - a cleaner API around callbacks. An object representing a future value. Chainable with <code>.then()</code> and <code>.catch()</code>. The foundation everything else builds on.</p><p><strong>Async/Await</strong> - syntax sugar over Promises. Makes async code look synchronous. <code>async</code> functions always return Promises. <code>await</code> pauses execution until a Promise settles.</p><p><strong>Event Loop</strong> - the underlying mechanism that makes all of this possible. Callbacks and microtasks get queued and run when the call stack is empty. None of this blocks - it all works through the queue system.</p><p>They are not competing concepts. They are layers of the same system, each one built on top of the previous, each one making async JavaScript a little cleaner to write.</p><p>[IMAGE: Layered architecture diagram showing four layers stacked - bottom layer "Event Loop" in blue, second layer "Callbacks" in orange, third layer "Promises" in yellow, top layer "Async/Await" in green. Arrow on the side labeled "Each layer builds on the one below it". This shows the complete async JavaScript picture]</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/async-await-javascript-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>Backend</category>
      <category>javascript</category>
      <category>nodejs</category>
      <category>async-await</category>
      <category>event-loop</category>
      <category>promises</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1785811826/uifindr/components/n5ctgfdih0weuicijhfq.png" type="image/jpeg" />
    </item>

    <item>
      <title>PostgreSQL vs MongoDB - How to Choose the Right Database for Your Project</title>
      <link>https://www.codekk.dev/blogs/postgresql-vs-mongodb-how-to-choose</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/postgresql-vs-mongodb-how-to-choose</guid>
      <pubDate>Fri, 24 Jul 2026 02:45:56 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[PostgreSQL and MongoDB are fundamentally different databases. This guide explains how each works, how they handle relationships, schema, and transactions - so you can make the right choice instead of just going with whatever a tutorial used.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1783997396/uifindr/components/xymusglbf3fbxtijmile.jpg" alt="PostgreSQL vs MongoDB - How to Choose the Right Database for Your Project" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>Every developer building a backend eventually hits this question. You have data to store, you need a database, and two names keep coming up - PostgreSQL and MongoDB. Someone in a tutorial uses one, someone on Reddit recommends the other, and you're left wondering which one you should actually learn and use.</p><p>This blog explains what each database actually is, how they store data differently, and gives you a clear way to think about which one fits your project. Not a "one is better" argument - because neither is universally better. Just an honest breakdown so you can make the right call.</p><p>If you've been following the backend series on codekk.dev, you've already covered <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express</a>, <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/how-jwt-authentication-works">JWT authentication</a>, and <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/what-is-cors-and-how-to-fix-it-in-express">CORS</a>. The missing piece has been the database layer - this is it.</p><hr><h2>The Fundamental Difference - How They Think About Data</h2><p>Before anything else, you need to understand that PostgreSQL and MongoDB are built on completely different ideas about what data looks like and how it should be stored.</p><p><strong>PostgreSQL is a relational database.</strong> It stores data in tables - rows and columns, like a spreadsheet. Every row in a table follows the same structure. Relationships between different types of data are handled by linking tables together using keys.</p><p><strong>MongoDB is a document database.</strong> It stores data as JSON-like documents inside collections. Each document can have a different structure. Related data can either be referenced or embedded directly inside the same document.</p><img class="" src="https://res.cloudinary.com/dyswu0gin/image/upload/v1783997905/uifindr/components/tctcjvekyzdao24ovbli.png" alt="postgresql-vs-mongodb-comparision.png"><p>This isn't just a syntax difference. It's a fundamentally different way of thinking about your data, and it affects how you design your application from the start.</p><hr><h2>How PostgreSQL Stores Data</h2><p>In PostgreSQL, before you store anything, you define a schema. A schema is a strict blueprint that says exactly what columns exist, what type of data each column holds, and which columns are required.</p><pre><code class="language-sql">CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id),
  total_amount DECIMAL(10, 2) NOT NULL,
  status VARCHAR(50) DEFAULT 'pending',
  created_at TIMESTAMP DEFAULT NOW()
);
</code></pre><p>Every user stored in this table will have exactly these four fields - nothing more, nothing less. If you try to insert a user without an email, PostgreSQL rejects it. If you try to add a field that doesn't exist in the schema, PostgreSQL rejects it.</p><p>The <code>orders</code> table has a <code>user_id</code> column that references the <code>users</code> table. This is a <strong>foreign key</strong> - it creates a formal relationship between the two tables. PostgreSQL enforces this relationship. You cannot create an order for a user that doesn't exist. If you try to delete a user who has orders, PostgreSQL will either block you or cascade the deletion depending on how you configured it.</p><p>To get a user and all their orders together, you use a JOIN:</p><pre><code class="language-sql">SELECT users.name, users.email, orders.total_amount, orders.status
FROM users
JOIN orders ON users.id = orders.user_id
WHERE users.id = 1;
</code></pre><img class="" src="https://res.cloudinary.com/dyswu0gin/image/upload/v1784861124/uifindr/components/mgc4jbpqgcqhq86rw2qx.png" alt="Foreign Key Relationship Diagram with JOIN.png"><p>This relational model is powerful when your data has clear relationships and you need to query across those relationships in complex ways. The strict schema means your data is always consistent and predictable.</p><hr><h2>How MongoDB Stores Data</h2><p>In MongoDB, there's no schema to define upfront. You create a collection and start inserting documents immediately. A document is a JSON object and it can have whatever fields you want.</p><pre><code class="language-js">// A user document in MongoDB
{
  _id: ObjectId("64a7b3c2e1234567890abcde"),
  name: "Rahul",
  email: "rahul@example.com",
  createdAt: new Date("2024-01-15"),
  preferences: {
    theme: "dark",
    language: "en"
  },
  tags: ["developer", "react"]
}
</code></pre><p>Notice a few things here. The user document has nested data inside it - <code>preferences</code> is an object embedded directly in the user document. <code>tags</code> is an array. This is natural in MongoDB.</p><p>In a relational database, preferences and tags would typically live in separate tables with their own foreign key relationships. In MongoDB you just put them inside the document because they belong to the user.</p><p>For orders, you have a choice in MongoDB. You can reference the user by their ID (similar to how SQL does it):</p><pre><code class="language-js">// Order document with a reference to the user
{
  _id: ObjectId("64a7b3c2e1234567890abcdf"),
  userId: ObjectId("64a7b3c2e1234567890abcde"),
  totalAmount: 2499,
  status: "pending",
  items: [
    { productName: "Mechanical Keyboard", quantity: 1, price: 2499 }
  ],
  createdAt: new Date("2024-01-15")
}
</code></pre><p>Or, if orders are always accessed together with the user, you could embed them directly inside the user document:</p><pre><code class="language-js">// User document with orders embedded
{
  _id: ObjectId("64a7b3c2e1234567890abcde"),
  name: "Rahul",
  email: "rahul@example.com",
  orders: [
    {
      totalAmount: 2499,
      status: "pending",
      items: [{ productName: "Mechanical Keyboard", quantity: 1, price: 2499 }]
    }
  ]
}
</code></pre><p>[IMAGE: Diagram showing a MongoDB document with nested objects and arrays embedded inside it, contrasted with the same data spread across multiple SQL tables]</p><p>The decision between referencing and embedding in MongoDB depends entirely on how you access the data. If you always need the orders when you fetch a user, embedding saves you an extra query. If orders can grow very large or are accessed independently, referencing makes more sense.</p><hr><h2>Querying - SQL vs MongoDB Query Language</h2><p>This is where the day-to-day developer experience differs most noticeably.</p><p>PostgreSQL uses SQL - Structured Query Language. It's been around since the 1970s and is one of the most widely known programming interfaces in software development.</p><pre><code class="language-sql">-- Get all users who signed up in the last 30 days
SELECT id, name, email
FROM users
WHERE created_at &gt;= NOW() - INTERVAL '30 days'
ORDER BY created_at DESC;

-- Get total revenue per user
SELECT users.name, SUM(orders.total_amount) as total_spent
FROM users
JOIN orders ON users.id = orders.user_id
GROUP BY users.id, users.name
ORDER BY total_spent DESC;
</code></pre><p>MongoDB has its own query language based on JavaScript objects:</p><pre><code class="language-js">// Get all users who signed up in the last 30 days
db.users.find({
  createdAt: { $gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }
}).sort({ createdAt: -1 });

// Get total spent per user (using aggregation pipeline)
db.orders.aggregate([
  {
    $group: {
      _id: "$userId",
      totalSpent: { $sum: "$totalAmount" }
    }
  },
  { $sort: { totalSpent: -1 } }
]);
</code></pre><p>SQL is more readable for complex queries involving multiple tables. MongoDB's aggregation pipeline is powerful but has a steeper learning curve for complex operations. For simple queries, both feel comparable.</p><hr><h2>Schema Flexibility - A Feature and a Risk</h2><p>MongoDB's schema flexibility is often presented as a major advantage, and it genuinely can be. During early development when your data model is still changing, not having to write migrations every time you add a field is genuinely convenient.</p><p>But flexibility has a cost. Without a schema enforced at the database level, the responsibility for data consistency falls entirely on your application code. If one part of your code saves a user with an <code>email</code> field and another part accidentally saves with <code>emailAddress</code>, both are valid in MongoDB. Your database will happily store inconsistent data and you'll only discover the problem when your code breaks trying to read it.</p><p>PostgreSQL's strictness feels limiting at first. Adding a new column to a table requires a migration. Changing a column type requires careful planning. But this strictness is also what guarantees that every row in your table looks exactly the way you expect it to. Your data is consistent by definition.</p><p>[IMAGE: Split diagram - left side shows MongoDB collection with documents having inconsistent field names (email vs emailAddress vs e_mail) with a warning icon. Right side shows a PostgreSQL table where all rows have consistent columns with a checkmark icon]</p><p>Most teams working with MongoDB end up adding a schema validation layer in their application code anyway - using libraries like Mongoose which adds schemas on top of MongoDB. At that point you have the flexibility of MongoDB with schema enforcement added back in through your ORM. It works, but it's worth knowing you're solving a problem that PostgreSQL doesn't have in the first place.</p><hr><h2>Transactions - When They Matter</h2><p>A transaction is a way to group multiple database operations so they either all succeed or all fail together. This matters enormously in certain situations.</p><p>Consider a bank transfer. You're moving money from Account A to Account B. That's two operations - deduct from A, add to B. If the first operation succeeds and then something fails before the second one completes, Account A lost money that never arrived in Account B. Without transactions, this is a real risk.</p><p>PostgreSQL has had full ACID transaction support since the beginning. Wrapping multiple operations in a transaction is straightforward:</p><pre><code class="language-sql">BEGIN;
  UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
  UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;
</code></pre><p>If anything fails between <code>BEGIN</code> and <code>COMMIT</code>, the entire thing rolls back as if nothing happened.</p><p>MongoDB added multi-document transaction support in version 4.0, so it does support transactions now. But they come with performance overhead and are generally considered a workaround in MongoDB - if you find yourself needing frequent multi-document transactions, it often means your data model would work better in a relational database.</p><hr><h2>Performance - It Depends on What You're Doing</h2><p>Neither database is faster in absolute terms. Performance depends heavily on your data access patterns.</p><p>MongoDB tends to perform well when you're reading and writing single documents that contain all the data you need. Because related data is often embedded, you're doing fewer queries. Reading a user with all their preferences and recent activity in one document query is fast.</p><p>PostgreSQL tends to perform well for complex queries across multiple tables, especially with proper indexing. Its query planner is sophisticated and can optimize JOINs and aggregations efficiently. For analytical queries or reporting where you're aggregating data across the whole database, PostgreSQL typically has an edge.</p><p>For most web applications, both will perform well within your expected load. The performance difference rarely matters until you're at significant scale, at which point you'll have enough engineering resources to address it properly. Don't make your database choice based on hypothetical future scale - make it based on your current data model and access patterns.</p><hr><h2>When PostgreSQL is the Right Choice</h2><p>PostgreSQL makes sense when:</p><p>Your data has clear relationships that you need to query across. A user has orders, orders have items, items have products - that relational structure is exactly what PostgreSQL was designed for.</p><p>Data consistency is critical. Financial applications, healthcare data, anything where you cannot afford inconsistent or corrupt data belongs in a relational database with proper constraints and transactions.</p><p>You're doing complex reporting or analytics. SQL is genuinely excellent for aggregation, grouping, and cross-table queries. If your app needs to generate reports across your data, PostgreSQL will serve you better.</p><p>Your schema is stable and well-defined. If you know what your data looks like and it's not going to change dramatically, the strictness of PostgreSQL works in your favor.</p><hr><h2>When MongoDB is the Right Choice</h2><p>MongoDB makes sense when:</p><p>Your data is naturally document-shaped. If you're storing things like product catalogs where each product has wildly different attributes, or user-generated content that varies in structure, the document model fits naturally.</p><p>You're prototyping or iterating quickly. During early development when your data model is still evolving, MongoDB lets you move fast without writing migrations constantly.</p><p>You're storing hierarchical or nested data. If your data has a lot of nesting and you always access it together, embedding in MongoDB is cleaner than managing multiple joined tables.</p><p>You're working with content-heavy applications like CMS platforms, blogs, or catalog systems where the content structure varies and relations are simple.</p><hr><h2>The Honest Summary</h2><p>[IMAGE: Clean comparison table showing PostgreSQL vs MongoDB across key dimensions - data model, schema, transactions, query language, best for, and flexibility]</p><p>PostgreSQL is the safer default for most applications. It's been battle-tested for decades, has excellent tooling, enforces data integrity at the database level, and SQL is a skill that transfers everywhere. If you're not sure which one to pick, PostgreSQL is the lower-risk choice.</p><p>MongoDB is genuinely good for what it's designed for - flexible, document-shaped data where the structure varies and relations are simple. It's not a "beginner" database or a "worse" one, just a different tool for a different kind of problem.</p><p>The mistake most developers make is picking one based on which tutorial they happened to follow first. Pick based on your data. Sketch out what your data actually looks like and how you'll query it. If it naturally fits in tables with clear relationships, use PostgreSQL. If it naturally fits in nested documents with varying structure, use MongoDB.</p><p>Both are excellent databases. The decision matters a lot less than people make it out to be as long as you make it consciously.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/postgresql-vs-mongodb-how-to-choose">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>backend</category>
      <category>postgresql</category>
      <category>mongodb</category>
      <category>database</category>
      <category>sql</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1783997396/uifindr/components/xymusglbf3fbxtijmile.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>Promises in JavaScript - How They Actually Work</title>
      <link>https://www.codekk.dev/blogs/promises-in-javascript-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/promises-in-javascript-explained</guid>
      <pubDate>Fri, 17 Jul 2026 03:44:36 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Learn how JavaScript Promises actually work - the three states, chaining, error handling, Promise.all, race, any, and how they connect to async/await. ]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1784259647/uifindr/components/kuujlzjtbqvilmiyul2p.jpg" alt="Promises in JavaScript - How They Actually Work" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>If you have read the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/callback-functions-javascript-explained">callbacks blog</a> on codekk.dev, you already know the problem. Sequential async operations with callbacks turn into deeply nested pyramids that are hard to read, hard to maintain, and painful to debug. Promises were created specifically to fix that.</p><p>But most developers learn just enough about Promises to use <code>.then()</code> and <code>.catch()</code> and move on. They do not really understand what a Promise object is, what states it goes through, how chaining actually works, or why error handling behaves the way it does.</p><p>This blog covers all of that properly. By the end, you will not just know how to use Promises - you will understand what they actually are and why they work the way they do. And when you get to async/await after this, it will make complete sense because async/await is just Promises with cleaner syntax underneath.</p><hr><h2>What a Promise Actually Is</h2><p>A Promise is an object that represents the eventual result of an async operation. It is a placeholder for a value that does not exist yet but will exist at some point in the future - or will fail trying.</p><p>Think of it like ordering food at a restaurant. When you order, the waiter does not disappear and come back only when the food is ready while making you stand at the counter. They give you a token - a promise that your food will come. You go sit down and do other things. When the food is ready, it gets delivered to you.</p><p>In JavaScript terms:</p><ul><li><p>The token is the Promise object</p></li><li><p>Sitting down and doing other things is your code continuing to run</p></li><li><p>The food arriving is the Promise resolving</p></li><li><p>The kitchen running out of ingredients is the Promise rejecting</p></li></ul><pre><code class="language-js">const foodPromise = orderFood("Biryani"); // returns a Promise immediately

// You do not wait here - code continues
console.log("Sitting down...");
console.log("Checking phone...");

// When food is ready, this runs
foodPromise.then(food =&gt; {
  console.log("Food arrived:", food);
}).catch(err =&gt; {
  console.log("Order failed:", err.message);
});
</code></pre><p>The Promise object is returned immediately. The actual value comes later.</p><hr><h2>The Three States of a Promise</h2><p>Every Promise is always in exactly one of three states. Understanding these states is the foundation of understanding how Promises work.</p><p>[IMAGE: Diagram showing three states of a Promise - "Pending" in yellow center circle with arrows pointing to "Fulfilled" in green on the right and "Rejected" in red on the left. One-way arrows showing you can only go from Pending to either Fulfilled or Rejected, never back. Labels: "resolve() called" on the arrow to Fulfilled, "reject() called" on the arrow to Rejected]</p><p><strong>Pending</strong> - the initial state. The async operation has started but has not completed yet. The Promise is waiting.</p><p><strong>Fulfilled</strong> - the operation completed successfully. The Promise now has a value. Your <code>.then()</code> callback runs.</p><p><strong>Rejected</strong> - the operation failed. The Promise has a reason for failure. Your <code>.catch()</code> callback runs.</p><p>Once a Promise moves from Pending to either Fulfilled or Rejected, it is settled and can never change state again. A fulfilled Promise will always be fulfilled with the same value. A rejected Promise will always be rejected with the same reason.</p><pre><code class="language-js">const promise = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; {
    resolve("Success!"); // moves to Fulfilled
    resolve("Another value"); // ignored - already settled
    reject("Error"); // ignored - already settled
  }, 1000);
});
</code></pre><p>This immutability is important - it means you can safely pass a Promise around and attach multiple handlers to it without worrying about the state changing unexpectedly.</p><hr><h2>Creating a Promise - The Promise Constructor</h2><p>You create a Promise using the <code>new Promise()</code> constructor. It takes one function as an argument - called the <strong>executor function</strong>. The executor runs immediately and receives two functions: <code>resolve</code> and <code>reject</code>.</p><pre><code class="language-js">const myPromise = new Promise((resolve, reject) =&gt; {
  // This runs immediately when the Promise is created

  const success = true;

  if (success) {
    resolve("Operation completed successfully"); // fulfills the Promise
  } else {
    reject(new Error("Something went wrong")); // rejects the Promise
  }
});
</code></pre><p>Call <code>resolve(value)</code> when the operation succeeds. Call <code>reject(error)</code> when it fails. Only the first call matters - any subsequent calls to either <code>resolve</code> or <code>reject</code> are ignored because the Promise is already settled.</p><p><strong>A real async example - wrapping setTimeout in a Promise:</strong></p><pre><code class="language-js">function delay(ms) {
  return new Promise((resolve) =&gt; {
    setTimeout(() =&gt; {
      resolve(`Done after ${ms}ms`);
    }, ms);
  });
}

delay(2000).then(message =&gt; {
  console.log(message); // "Done after 2000ms" - appears after 2 seconds
});

console.log("This runs immediately");
</code></pre><p><strong>Wrapping a callback-based function in a Promise:</strong></p><p>This is called promisification - converting an older callback-based API into a Promise-based one.</p><pre><code class="language-js">const fs = require("fs");

// Old callback style
function readFileCallback(path, callback) {
  fs.readFile(path, "utf8", callback);
}

// Promisified version
function readFilePromise(path) {
  return new Promise((resolve, reject) =&gt; {
    fs.readFile(path, "utf8", (err, data) =&gt; {
      if (err) {
        reject(err); // error goes to reject
      } else {
        resolve(data); // data goes to resolve
      }
    });
  });
}

// Now you can use it with .then() and .catch()
readFilePromise("users.txt")
  .then(data =&gt; console.log(data))
  .catch(err =&gt; console.error(err));
</code></pre><p>Node.js actually has a built-in utility for this called <code>util.promisify</code> that does exactly the same conversion automatically for any error-first callback function.</p><hr><h2>Using a Promise - then, catch, finally</h2><p>Once you have a Promise, you use three methods to handle its outcome.</p><p><code>.then(onFulfilled, onRejected)</code></p><p><code>.then()</code> takes up to two arguments - a function to run when the Promise fulfills and an optional function to run when it rejects.</p><pre><code class="language-js">fetchUser(1)
  .then(
    (user) =&gt; console.log("Got user:", user),   // runs on success
    (err) =&gt; console.error("Failed:", err)       // runs on failure
  );
</code></pre><p>In practice, most developers only pass the success handler to <code>.then()</code> and use <code>.catch()</code> separately for errors. It is cleaner.</p><p><code>.catch(onRejected)</code></p><p><code>.catch()</code> handles rejections. It is shorthand for <code>.then(undefined, onRejected)</code>.</p><pre><code class="language-js">fetchUser(1)
  .then(user =&gt; console.log("Got user:", user))
  .catch(err =&gt; console.error("Failed:", err));
</code></pre><p><code>.finally(onFinally)</code></p><p><code>.finally()</code> runs no matter what - whether the Promise fulfilled or rejected. Useful for cleanup like hiding a loading spinner.</p><pre><code class="language-js">showLoadingSpinner();

fetchUser(1)
  .then(user =&gt; displayUser(user))
  .catch(err =&gt; showError(err))
  .finally(() =&gt; hideLoadingSpinner()); // always runs
</code></pre><p><code>.finally()</code> receives no arguments - it does not know whether the Promise succeeded or failed. It just knows it is settled.</p><hr><h2>Promise Chaining - The Real Power</h2><p>This is what makes Promises genuinely better than callbacks. When you return a value from a <code>.then()</code> callback, it gets wrapped in a new Promise. This lets you chain multiple async operations in a flat, readable sequence instead of nesting them.</p><pre><code class="language-js">fetchUser(1)
  .then(user =&gt; {
    console.log("Got user:", user.name);
    return fetchOrders(user.id); // return a new Promise
  })
  .then(orders =&gt; {
    console.log("Got orders:", orders.length);
    return sendEmail(orders);  // return another Promise
  })
  .then(result =&gt; {
    console.log("Email sent:", result);
  })
  .catch(err =&gt; {
    console.error("Something failed:", err); // catches errors from any step
  });
</code></pre><p>Compare this to the callback hell version from the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/callback-functions-javascript-explained">callbacks blog</a>. The logic is exactly the same but the structure is flat. You read it top to bottom like a series of steps. One <code>.catch()</code> at the end handles errors from any step in the chain.</p><p>[IMAGE: Side by side comparison - left shows callback hell pyramid (deeply nested, hard to read), right shows the same logic as a flat Promise chain (clean, readable top to bottom). Arrow between them labeled "Same logic, different structure"]</p><p><strong>What gets passed through the chain:</strong></p><pre><code class="language-js">Promise.resolve(1)
  .then(val =&gt; val + 1)     // receives 1, returns 2
  .then(val =&gt; val * 3)     // receives 2, returns 6
  .then(val =&gt; val - 1)     // receives 6, returns 5
  .then(val =&gt; console.log(val)); // receives 5, logs 5
</code></pre><p>Each <code>.then()</code> receives whatever the previous one returned. If you return a plain value, it gets passed directly. If you return a Promise, the next <code>.then()</code> waits for that Promise to resolve and receives its value.</p><hr><h2>Error Handling in Promise Chains</h2><p>Errors in Promise chains deserve special attention because they behave differently from synchronous try/catch.</p><p>When any step in a chain throws an error or rejects, the chain skips all remaining <code>.then()</code> handlers and jumps straight to the next <code>.catch()</code>.</p><pre><code class="language-js">fetchUser(1)
  .then(user =&gt; {
    throw new Error("Something broke here"); // this error is caught below
  })
  .then(orders =&gt; {
    console.log("This never runs");
  })
  .then(result =&gt; {
    console.log("This never runs either");
  })
  .catch(err =&gt; {
    console.error("Caught:", err.message); // "Caught: Something broke here"
  });
</code></pre><p>You can also recover from errors mid-chain by returning a value from <code>.catch()</code>:</p><pre><code class="language-js">fetchUser(999) // user does not exist - will reject
  .then(user =&gt; fetchOrders(user.id))
  .catch(err =&gt; {
    console.log("User not found, using default");
    return { id: 0, orders: [] }; // recover with a default value
  })
  .then(data =&gt; {
    console.log("Continuing with:", data); // chain continues here
  });
</code></pre><p>When <code>.catch()</code> returns a value, the chain continues with that value as if nothing went wrong. This lets you handle specific errors gracefully and continue the chain instead of stopping entirely.</p><p>[IMAGE: Diagram showing Promise chain with an error - first .then() runs fine (green), second .then() throws error (red with lightning bolt), third and fourth .then() are skipped (gray with skip arrows), .catch() at the bottom catches it (orange). Label: "Error skips remaining .then() handlers and goes straight to .catch()"]</p><hr><h2>Promise.all - Running Multiple Promises in Parallel</h2><p>Sometimes you have multiple async operations that do not depend on each other and you want to run them at the same time instead of one by one.</p><pre><code class="language-js">// Sequential - slow (waits for each one)
const user = await fetchUser(1);        // waits 300ms
const orders = await fetchOrders(1);    // then waits 300ms
const products = await fetchProducts(); // then waits 300ms
// Total: ~900ms

// Parallel with Promise.all - fast (all run at the same time)
const [user, orders, products] = await Promise.all([
  fetchUser(1),
  fetchOrders(1),
  fetchProducts()
]);
// Total: ~300ms (waits for the slowest one)
</code></pre><p><code>Promise.all</code> takes an array of Promises and returns a new Promise that:</p><ul><li><p>Fulfills when ALL Promises in the array fulfill - with an array of their results in the same order</p></li><li><p>Rejects immediately when ANY Promise in the array rejects - with that rejection reason</p></li></ul><pre><code class="language-js">Promise.all([
  fetchUser(1),
  fetchOrders(1),
  fetchProducts()
])
  .then(([user, orders, products]) =&gt; {
    // all three are ready here
    console.log(user, orders, products);
  })
  .catch(err =&gt; {
    // if any one of them fails, we end up here
    console.error("One of the requests failed:", err);
  });
</code></pre><p>The "fail fast" behavior of <code>Promise.all</code> is important to know. If you have three requests and the second one fails, you immediately get the rejection. The third request might still be running but you will never get its result through this Promise.</p><hr><h2>Promise.allSettled - When You Want All Results Regardless</h2><p>Sometimes you want all results even if some fail. <code>Promise.allSettled</code> waits for all Promises to settle (fulfill or reject) and gives you the outcome of each one.</p><pre><code class="language-js">Promise.allSettled([
  fetchUser(1),
  fetchUser(999),  // this one will fail - user does not exist
  fetchUser(2)
]).then(results =&gt; {
  results.forEach(result =&gt; {
    if (result.status === "fulfilled") {
      console.log("Success:", result.value);
    } else {
      console.log("Failed:", result.reason);
    }
  });
});
</code></pre><p>Each item in the results array has a <code>status</code> of either <code>"fulfilled"</code> or <code>"rejected"</code>, and either a <code>value</code> (on success) or <code>reason</code> (on failure). No short-circuiting - you always get all results.</p><hr><h2>Promise.race - First One Wins</h2><p><code>Promise.race</code> takes an array of Promises and resolves or rejects as soon as the first one settles, with that Promise's value or reason.</p><pre><code class="language-js">// Useful for timeouts - reject if API takes too long
function fetchWithTimeout(url, timeoutMs) {
  const fetchPromise = fetch(url);

  const timeoutPromise = new Promise((_, reject) =&gt; {
    setTimeout(() =&gt; {
      reject(new Error(`Request timed out after ${timeoutMs}ms`));
    }, timeoutMs);
  });

  return Promise.race([fetchPromise, timeoutPromise]);
}

fetchWithTimeout("https://api.example.com/users", 3000)
  .then(response =&gt; response.json())
  .then(data =&gt; console.log(data))
  .catch(err =&gt; console.error(err.message));
</code></pre><p>If the fetch completes in under 3 seconds, <code>fetchPromise</code> wins the race and you get the response. If it takes longer, <code>timeoutPromise</code> wins and you get a timeout error.</p><hr><h2>Promise.any - First Successful One Wins</h2><p><code>Promise.any</code> is like <code>Promise.race</code> but it only cares about the first fulfillment, not the first settlement. It ignores rejections unless all Promises reject.</p><pre><code class="language-js">// Try multiple API endpoints - use whichever responds first successfully
Promise.any([
  fetch("https://api1.example.com/data"),
  fetch("https://api2.example.com/data"),
  fetch("https://api3.example.com/data")
])
  .then(response =&gt; response.json())
  .then(data =&gt; console.log("Got data from fastest server:", data))
  .catch(err =&gt; console.error("All servers failed:", err));
</code></pre><p>If the first two fail but the third succeeds, you get the third one's result. Only if all three fail do you get a rejection.</p><p>[IMAGE: Comparison table of Promise.all, Promise.allSettled, Promise.race, Promise.any - showing what each one does when promises succeed vs fail, and what it returns. Dark background table with color coded rows]</p><hr><h2>Promise.resolve and Promise.reject</h2><p>These are shorthand ways to create already-settled Promises.</p><pre><code class="language-js">// Creates a Promise that is already fulfilled with this value
Promise.resolve("immediate value")
  .then(val =&gt; console.log(val)); // "immediate value"

// Creates a Promise that is already rejected with this error
Promise.reject(new Error("immediate failure"))
  .catch(err =&gt; console.error(err.message)); // "immediate failure"
</code></pre><p>These are useful when you need to return a Promise from a function but already have the value available synchronously:</p><pre><code class="language-js">function getUser(id) {
  // Check cache first
  if (cache.has(id)) {
    return Promise.resolve(cache.get(id)); // return immediately, but as a Promise
  }

  // Otherwise fetch from API
  return fetch(`/api/users/${id}`).then(r =&gt; r.json());
}

// Caller always gets a Promise, whether data was cached or not
getUser(1).then(user =&gt; console.log(user));
</code></pre><hr><h2>Common Mistakes With Promises</h2><p><strong>Forgetting to return inside .then():</strong></p><pre><code class="language-js">// Bug - nothing is chained
fetchUser(1)
  .then(user =&gt; {
    fetchOrders(user.id); // forgot to return!
  })
  .then(orders =&gt; {
    console.log(orders); // undefined - the Promise was not returned
  });

// Correct
fetchUser(1)
  .then(user =&gt; {
    return fetchOrders(user.id); // return the Promise
  })
  .then(orders =&gt; {
    console.log(orders); // actual orders data
  });
</code></pre><p>If you forget <code>return</code> inside <code>.then()</code>, the next <code>.then()</code> receives <code>undefined</code> instead of the resolved value. This is one of the most common Promise bugs.</p><p><strong>Not catching errors:</strong></p><pre><code class="language-js">// Bug - if this rejects, you get an UnhandledPromiseRejection warning
fetchUser(1)
  .then(user =&gt; console.log(user));

// Always add .catch()
fetchUser(1)
  .then(user =&gt; console.log(user))
  .catch(err =&gt; console.error(err));
</code></pre><p>Unhandled Promise rejections will crash your Node.js server in newer versions. Always add a <code>.catch()</code> at the end of every Promise chain.</p><p><strong>Creating unnecessary Promise wrappers:</strong></p><pre><code class="language-js">// Unnecessary - fetch already returns a Promise
function getUser(id) {
  return new Promise((resolve, reject) =&gt; {
    fetch(`/api/users/${id}`)
      .then(r =&gt; r.json())
      .then(resolve)
      .catch(reject);
  });
}

// Just return the Promise directly
function getUser(id) {
  return fetch(`/api/users/${id}`).then(r =&gt; r.json());
}
</code></pre><p>Only use <code>new Promise()</code> when you are wrapping something that does not already return a Promise - like a callback-based function. If it already returns a Promise, just chain on it directly.</p><hr><h2>How Promises Connect to Async/Await</h2><p>Every <code>async</code> function returns a Promise. Every <code>await</code> expression waits for a Promise to settle. Async/await is not a separate concept - it is syntax sugar built directly on top of Promises.</p><pre><code class="language-js">// Promise chain version
function loadUserData(id) {
  return fetchUser(id)
    .then(user =&gt; fetchOrders(user.id)
      .then(orders =&gt; ({ user, orders }))
    )
    .catch(err =&gt; console.error(err));
}

// Async/await version - same thing, more readable
async function loadUserData(id) {
  try {
    const user = await fetchUser(id);
    const orders = await fetchOrders(user.id);
    return { user, orders };
  } catch (err) {
    console.error(err);
  }
}
</code></pre><p>Under the hood, the async/await version is doing exactly what the Promise chain is doing. The JavaScript engine converts <code>await</code> into <code>.then()</code> calls. Knowing Promises properly means you understand what async/await is actually doing, which makes debugging async code significantly easier.</p><p>If you have read the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/javascript-event-loop-explained">event loop blog</a> on codekk.dev, you also know that <code>.then()</code> callbacks go into the Microtask Queue - which is why Promise callbacks run before setTimeout callbacks even with zero delay. That same mechanism is what makes <code>await</code> work the way it does.</p><hr><h2>The Mental Model Worth Keeping</h2><p>A Promise is a box that says "a value is coming." It starts empty (Pending), then either gets filled with a value (Fulfilled) or gets marked as failed (Rejected). Once it is one of those two, it never changes.</p><p><code>.then()</code> says "when the box gets filled, do this with the value." <code>.catch()</code> says "when the box gets marked failed, do this." <code>.finally()</code> says "when the box is done either way, do this."</p><p>Chaining works because each <code>.then()</code> returns a new box. You are building a pipeline of boxes, each one waiting for the previous one to fill up before it starts.</p><p>Promises did not solve the fundamental challenge of async JavaScript - that is still handled by the event loop. What they solved is how you write code that deals with async results. Instead of nested callbacks, you get a flat pipeline. Same mechanics, much cleaner code.The yellow color for Promises connects intentionally to the "Pending" state which is the default starting state of every Promise. If your blog has syntax highlighting or callout boxes, using yellow for Promise-related terms, green for resolve/fulfill, and red for reject/error creates a color language that matches both the thumbnail and the mental model explained in the blog.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/promises-in-javascript-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>javascript</category>
      <category>nodejs</category>
      <category>async-await</category>
      <category>promises</category>
      <category>async</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1784259647/uifindr/components/kuujlzjtbqvilmiyul2p.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>Callback Functions in JavaScript - Everything You Need to Know</title>
      <link>https://www.codekk.dev/blogs/callback-functions-javascript-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/callback-functions-javascript-explained</guid>
      <pubDate>Thu, 16 Jul 2026 03:16:06 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Callbacks are everywhere in JavaScript and most developers use them without fully understanding what they are. This covers everything from first principles - sync vs async callbacks, error-first pattern, callback hell, and how they connect to Promises and async/await.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1784171335/uifindr/components/qmdxjrf2nbznjjpeypix.jpg" alt="Callback Functions in JavaScript - Everything You Need to Know" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>If you have been writing JavaScript for any amount of time, you have used callback functions. Maybe without even realizing it. That <code>addEventListener</code> you wrote, that <code>forEach</code> loop, that <code>setTimeout</code> - all of them use callbacks. They are everywhere in JavaScript.</p><p>But most developers learn just enough about callbacks to use them and move on. They do not really understand what a callback is at the language level, why JavaScript uses them so heavily, or how they connect to everything else in async JavaScript.</p><p>This blog covers all of it. What callbacks are, how they work, where they come from, their limitations, and how they evolved into the patterns we use today. If you have read the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/sync-async-nodejs-explained">synchronous and asynchronous Node.js blog</a> or the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/javascript-event-loop-explained">JavaScript event loop blog</a> on codekk.dev, this blog fills in an important piece of that picture.</p><hr><h2>What a Callback Function Actually Is</h2><p>A callback function is just a function that you pass as an argument to another function, to be called later.</p><p>That is the entire definition. There is nothing magical about it. It is just a function being passed around like any other value.</p><pre><code class="language-js">function greet(name) {
  console.log(`Hello, ${name}`);
}

function processUser(name, callback) {
  console.log("Processing user...");
  callback(name); // calling the function that was passed in
}

processUser("Rahul", greet);
</code></pre><p>Output:</p><pre><code>Processing user...
Hello, Rahul
</code></pre><p>Here <code>greet</code> is the callback. We did not call <code>greet()</code> ourselves - we passed it to <code>processUser</code> and let <code>processUser</code> call it when it was ready.</p><p>This is possible because in JavaScript, <strong>functions are first-class citizens</strong>. This means functions can be:</p><ul><li><p>Stored in variables</p></li><li><p>Passed as arguments to other functions</p></li><li><p>Returned from functions</p></li><li><p>Stored in arrays or objects</p></li></ul><pre><code class="language-js">// Storing a function in a variable
const sayHello = function(name) {
  console.log(`Hello, ${name}`);
};

// Storing functions in an object
const actions = {
  greet: function(name) { console.log(`Hi ${name}`); },
  farewell: function(name) { console.log(`Bye ${name}`); }
};

// Returning a function from a function
function multiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiplier(2);
console.log(double(5)); // 10
console.log(double(8)); // 16
</code></pre><p>This ability to treat functions as values is the foundation of callbacks. Without it, callbacks would not exist.</p><p>[IMAGE: Diagram showing a function being passed as an argument into another function, with an arrow showing the callback being called inside the receiving function. Label: "Function passed in → Called when ready"]</p><hr><h2>Two Ways to Write a Callback</h2><p>You can pass a callback as a named function or as an anonymous function written inline. Both work the same way.</p><p><strong>Named function as callback:</strong></p><pre><code class="language-js">function handleClick() {
  console.log("Button was clicked");
}

document.getElementById("btn").addEventListener("click", handleClick);
</code></pre><p><strong>Anonymous function as callback:</strong></p><pre><code class="language-js">document.getElementById("btn").addEventListener("click", function() {
  console.log("Button was clicked");
});
</code></pre><p><strong>Arrow function as callback (modern style):</strong></p><pre><code class="language-js">document.getElementById("btn").addEventListener("click", () =&gt; {
  console.log("Button was clicked");
});
</code></pre><p>All three do exactly the same thing. The named function approach is cleaner when the callback logic is complex or when you need to reuse it. The anonymous and arrow function approach is cleaner for short, one-time callbacks.</p><hr><h2>Synchronous Callbacks</h2><p>Not all callbacks are async. Some callbacks run immediately, right inside the function they are passed to. These are called synchronous callbacks.</p><p>The built-in array methods are the most common examples:</p><pre><code class="language-js">const numbers = [1, 2, 3, 4, 5];

// forEach - callback runs for each element, synchronously
numbers.forEach(function(num) {
  console.log(num);
});

console.log("This runs AFTER forEach finishes");
</code></pre><p>Output:</p><pre><code>1
2
3
4
5
This runs AFTER forEach finishes
</code></pre><p>The callback passed to <code>forEach</code> runs synchronously - one by one, in order, before the next line runs. Same with <code>map</code>, <code>filter</code>, <code>reduce</code>, and <code>sort</code>:</p><pre><code class="language-js">// map - transforms each element using the callback
const doubled = numbers.map(num =&gt; num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// filter - keeps elements where callback returns true
const evens = numbers.filter(num =&gt; num % 2 === 0);
console.log(evens); // [2, 4]

// reduce - accumulates a value using the callback
const sum = numbers.reduce((total, num) =&gt; total + num, 0);
console.log(sum); // 15

// sort - sorts based on callback comparison
const words = ["banana", "apple", "cherry"];
const sorted = words.sort((a, b) =&gt; a.localeCompare(b));
console.log(sorted); // ["apple", "banana", "cherry"]
</code></pre><p>These are all callbacks - functions passed as arguments to be called by the method. They just happen to run synchronously, one by one, right there in place.</p><p>[IMAGE: Diagram showing synchronous callback flow - main code runs line by line, forEach iterates and calls callback for each element in order, then continues to next line. All on the same call stack thread]</p><hr><h2>Asynchronous Callbacks</h2><p>This is where callbacks become more interesting and where most of the confusion happens.</p><p>An asynchronous callback is a function that gets called later - after some async operation completes. The surrounding code does not wait for it. It moves on immediately.</p><pre><code class="language-js">console.log("1 - Start");

setTimeout(function() {
  console.log("3 - Inside timeout");
}, 2000);

console.log("2 - End");
</code></pre><p>Output:</p><pre><code>1 - Start
2 - End
3 - Inside timeout  ← appears after 2 seconds
</code></pre><p>The callback inside <code>setTimeout</code> does not run immediately. It runs 2 seconds later, after the rest of the code has already finished. This is the async callback pattern.</p><p>If you read the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/javascript-event-loop-explained">JavaScript event loop blog</a>, you already know why this happens - the <code>setTimeout</code> callback goes into the Callback Queue and only runs after the call stack is empty. This is exactly that mechanism at work.</p><p><strong>Real world async callback - reading a file in Node.js:</strong></p><pre><code class="language-js">const fs = require("fs");

console.log("1 - Before reading file");

fs.readFile("users.txt", "utf8", function(err, data) {
  if (err) {
    console.error("Error:", err);
    return;
  }
  console.log("3 - File contents:", data);
});

console.log("2 - After calling readFile");
</code></pre><p>Output:</p><pre><code>1 - Before reading file
2 - After calling readFile
3 - File contents: (file data here)
</code></pre><p><code>readFile</code> starts reading the file and immediately returns. The callback runs later when the file is ready. Your code keeps going in the meantime. This is exactly what we covered in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express blog</a> - non-blocking I/O that keeps the server responsive.</p><hr><h2>Error-First Callbacks - The Node.js Convention</h2><p>When you work with Node.js built-in modules, you'll notice all async callbacks follow a specific pattern - the first argument is always an error, and the second is the data.</p><pre><code class="language-js">fs.readFile("data.txt", "utf8", function(err, data) {
  // err comes first, always
  if (err) {
    console.error("Something went wrong:", err.message);
    return; // stop here if there's an error
  }
  // only use data if there's no error
  console.log(data);
});
</code></pre><p>This is called the <strong>error-first callback pattern</strong> (sometimes called Node-style callbacks or errbacks). The convention is:</p><ul><li><p>First argument: error object if something went wrong, <code>null</code> if everything is fine</p></li><li><p>Second argument onwards: the actual result data</p></li></ul><pre><code class="language-js">// Writing a function that follows the error-first pattern
function divideNumbers(a, b, callback) {
  if (b === 0) {
    callback(new Error("Cannot divide by zero"), null);
    return;
  }
  callback(null, a / b);
}

divideNumbers(10, 2, function(err, result) {
  if (err) {
    console.error("Error:", err.message);
    return;
  }
  console.log("Result:", result); // Result: 5
});

divideNumbers(10, 0, function(err, result) {
  if (err) {
    console.error("Error:", err.message); // Error: Cannot divide by zero
    return;
  }
  console.log("Result:", result);
});
</code></pre><p>Always check for the error first. Always. If you skip the error check and something went wrong, <code>data</code> will be undefined and your code will break in a confusing way further down.</p><hr><h2>Callback Hell - The Biggest Problem With Callbacks</h2><p>Here is where callbacks start causing real pain.</p><p>Imagine you need to do several async operations in sequence - read a user from a file, then fetch their orders from a database, then send them an email. Each step depends on the result of the previous one.</p><p>With callbacks, this looks like:</p><pre><code class="language-js">fs.readFile("user.txt", "utf8", function(err, userId) {
  if (err) return console.error(err);

  database.getUser(userId, function(err, user) {
    if (err) return console.error(err);

    database.getOrders(user.id, function(err, orders) {
      if (err) return console.error(err);

      emailService.sendSummary(user.email, orders, function(err, result) {
        if (err) return console.error(err);

        console.log("Email sent successfully:", result);
      });
    });
  });
});
</code></pre><p>This is <strong>callback hell</strong> - also called the pyramid of doom because of the shape the indentation makes. Each operation nests inside the previous one, pushing the code further and further to the right.</p><p>[IMAGE: Visual representation of callback hell - code shown as a pyramid shape with increasing indentation at each level, each level labeled with the operation (read file, get user, get orders, send email). Arrow pointing to the shape saying "Pyramid of Doom"]</p><p>The problems with this pattern:</p><p><strong>Hard to read</strong> - you have to trace through layers of nesting to follow the logic. By the time you reach the actual work being done, you are four levels deep.</p><p><strong>Hard to maintain</strong> - adding a new step means adding another level of nesting. Removing a step means carefully unwrapping a layer.</p><p><strong>Error handling is repetitive</strong> - notice how <code>if (err) return console.error(err)</code> appears at every single level. If you want consistent error handling, you write the same thing over and over.</p><p><strong>Hard to debug</strong> - stack traces in deeply nested callbacks are confusing to read.</p><p>This is exactly the problem that Promises were created to solve - which we covered in detail in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/sync-async-nodejs-explained">synchronous and asynchronous Node.js blog</a>.</p><hr><h2>How to Avoid Callback Hell - Even With Callbacks</h2><p>Before Promises existed, developers came up with patterns to manage callback hell. Even if you use Promises today, knowing these patterns is useful.</p><p><strong>Named functions instead of anonymous ones:</strong></p><pre><code class="language-js">function handleUserId(err, userId) {
  if (err) return handleError(err);
  database.getUser(userId, handleUser);
}

function handleUser(err, user) {
  if (err) return handleError(err);
  database.getOrders(user.id, handleOrders.bind(null, user));
}

function handleOrders(user, err, orders) {
  if (err) return handleError(err);
  emailService.sendSummary(user.email, orders, handleEmailResult);
}

function handleEmailResult(err, result) {
  if (err) return handleError(err);
  console.log("Email sent:", result);
}

function handleError(err) {
  console.error("Error:", err.message);
}

// Now the main flow reads cleanly
fs.readFile("user.txt", "utf8", handleUserId);
</code></pre><p>The logic is the same but the nesting is gone. Each callback is a named function that clearly states what it handles. The main flow reads like a sequence of steps.</p><p><strong>Modularizing into separate functions:</strong></p><pre><code class="language-js">function readUserId(callback) {
  fs.readFile("user.txt", "utf8", callback);
}

function fetchUser(userId, callback) {
  database.getUser(userId, callback);
}

function fetchOrders(userId, callback) {
  database.getOrders(userId, callback);
}

function sendEmail(email, orders, callback) {
  emailService.sendSummary(email, orders, callback);
}
</code></pre><p>Breaking each async operation into its own named function makes the code testable, reusable, and much easier to read.</p><hr><h2>Callbacks vs Promises vs Async/Await</h2><p>Callbacks are not wrong or outdated. But it is worth understanding how they relate to the newer patterns.</p><p><strong>Callbacks</strong> - the original async pattern. Work fine for simple cases. Become messy with sequential async operations.</p><p><strong>Promises</strong> - wrap the callback pattern in a cleaner API. Instead of passing a callback into a function, the function returns a Promise object and you chain <code>.then()</code> on it. Flat instead of nested.</p><p><strong>Async/Await</strong> - syntax built on top of Promises. Makes async code look synchronous and is the most readable of the three.</p><p>Here is the same operation written all three ways:</p><pre><code class="language-js">// 1. Callback style
fs.readFile("user.txt", "utf8", function(err, data) {
  if (err) return console.error(err);
  console.log(data);
});

// 2. Promise style
fs.promises.readFile("user.txt", "utf8")
  .then(data =&gt; console.log(data))
  .catch(err =&gt; console.error(err));

// 3. Async/Await style
async function readUser() {
  try {
    const data = await fs.promises.readFile("user.txt", "utf8");
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}
</code></pre><p>All three do exactly the same thing. Callbacks are not replaced by Promises - Promises are built on top of callbacks under the hood. When you call <code>.then(callback)</code>, you are still passing a callback function. The API is just cleaner.</p><p>[IMAGE: Side by side comparison of the same async operation written in all three styles - callback (pyramid shape, nested), Promise (flat chain), async/await (looks synchronous). Arrows connecting them showing evolution]</p><hr><h2>Where You Still See Callbacks Today</h2><p>Even though async/await is now the standard for most async code, callbacks are still used everywhere:</p><p><strong>Event listeners</strong> - always use callbacks:</p><pre><code class="language-js">button.addEventListener("click", (event) =&gt; {
  console.log("Clicked:", event.target);
});

window.addEventListener("resize", () =&gt; {
  console.log("Window resized");
});
</code></pre><p><strong>Array methods</strong> - always use synchronous callbacks:</p><pre><code class="language-js">const users = [
  { name: "Rahul", age: 25 },
  { name: "Priya", age: 22 },
  { name: "Arjun", age: 28 }
];

const adults = users.filter(user =&gt; user.age &gt;= 18);
const names = users.map(user =&gt; user.name);
const oldest = users.reduce((max, user) =&gt; user.age &gt; max.age ? user : max);
</code></pre><p><strong>setTimeout and setInterval:</strong></p><pre><code class="language-js">setTimeout(() =&gt; console.log("Runs once after 1 second"), 1000);

const interval = setInterval(() =&gt; console.log("Runs every 2 seconds"), 2000);
setTimeout(() =&gt; clearInterval(interval), 10000); // stop after 10 seconds
</code></pre><p><strong>Express middleware and route handlers</strong> - these are callbacks:</p><pre><code class="language-js">// The function passed to app.get is a callback
app.get("/users", (req, res) =&gt; {
  res.json({ users: [] });
});

// Middleware is a callback with three parameters
app.use((req, res, next) =&gt; {
  console.log(`${req.method} ${req.url}`);
  next();
});
</code></pre><p>If you built anything with Express from our <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express blog</a>, you have been writing callbacks the whole time - just without thinking of them that way.</p><hr><h2>Higher Order Functions - The Bigger Concept</h2><p>When you understand callbacks, you also understand <strong>higher order functions</strong> - functions that take other functions as arguments or return functions.</p><p><code>forEach</code>, <code>map</code>, <code>filter</code>, <code>reduce</code>, <code>setTimeout</code>, <code>addEventListener</code> - all of these are higher order functions. They accept a callback and do something with it.</p><p>You can write your own higher order functions too:</p><pre><code class="language-js">// A higher order function that runs a callback a given number of times
function repeat(times, callback) {
  for (let i = 0; i &lt; times; i++) {
    callback(i);
  }
}

repeat(3, (i) =&gt; console.log(`Running iteration ${i}`));
// Running iteration 0
// Running iteration 1
// Running iteration 2

// A higher order function that adds logging to any function
function withLogging(fn) {
  return function(...args) {
    console.log(`Calling function with args:`, args);
    const result = fn(...args);
    console.log(`Function returned:`, result);
    return result;
  };
}

function add(a, b) { return a + b; }

const loggedAdd = withLogging(add);
loggedAdd(3, 4);
// Calling function with args: [3, 4]
// Function returned: 7
</code></pre><p>This pattern - wrapping one function with another - is used everywhere in real codebases. Express middleware is this pattern. React's higher order components are this pattern. The <code>withLogging</code> example above is a simplified version of what logging libraries actually do.</p><hr><h2>Common Mistakes With Callbacks</h2><p><strong>Calling the callback instead of passing it:</strong></p><pre><code class="language-js">// Wrong - greet() is called immediately, its return value (undefined) is passed
processUser("Rahul", greet());

// Correct - greet function reference is passed, to be called later
processUser("Rahul", greet);
</code></pre><p>This is one of the most common beginner mistakes. Adding <code>()</code> calls the function right there. Without <code>()</code>, you are passing the function itself.</p><p><strong>Calling the callback more than once:</strong></p><pre><code class="language-js">function doSomething(callback) {
  callback("first call");
  callback("second call"); // this is a bug - callback called twice
}
</code></pre><p>In most patterns, a callback should be called exactly once. Calling it multiple times causes unexpected behavior, especially in async code.</p><p><strong>Forgetting to return after calling the callback in error cases:</strong></p><pre><code class="language-js">// Bug - both branches run
function process(data, callback) {
  if (!data) {
    callback(new Error("No data"));
    // forgot return - code continues below
  }
  callback(null, data.toUpperCase()); // crashes because data is null
}

// Correct
function process(data, callback) {
  if (!data) {
    callback(new Error("No data"));
    return; // stop here
  }
  callback(null, data.toUpperCase());
}
</code></pre><p>Always <code>return</code> after calling the callback in an error branch. Without it, the function keeps running and the callback might get called a second time.</p><hr><h2>The Mental Model Worth Keeping</h2><p>A callback is just a function you hand to someone else and say "call this when you are ready." That is it.</p><p>JavaScript uses callbacks heavily because it is single threaded and needs a way to say "do this thing, and when it is done, run this code" - without blocking everything while waiting. Callbacks were the original answer to that problem.</p><p>They are not outdated. They are still the right tool for event listeners, array methods, and simple async operations. For complex sequences of async operations, Promises and async/await are cleaner - but they are built on the same callback concept underneath.</p><p>Once callbacks click properly, Promises make more sense. And once Promises make sense, async/await becomes obvious. They are not separate concepts - they are the same idea, getting cleaner with each version.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/callback-functions-javascript-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>javascript</category>
      <category>nodejs</category>
      <category>callbacks</category>
      <category>promises</category>
      <category>async</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1784171335/uifindr/components/qmdxjrf2nbznjjpeypix.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>React vs Next.js: What&apos;s Actually Different and When to Use Which</title>
      <link>https://www.codekk.dev/blogs/react-vs-nextjs</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/react-vs-nextjs</guid>
      <pubDate>Fri, 10 Jul 2026 03:43:13 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[React and Next.js are often confused - one is a UI library, the other is a full framework built on top of it. This post breaks down the real differences: rendering strategies, routing, API routes, and when to pick one over the other.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1780596753/uifindr/components/sg64dsmfouzygrhywj2y.jpg" alt="React vs Next.js: What&apos;s Actually Different and When to Use Which" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/react-vs-nextjs">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>web-development</category>
      <category>nextjs</category>
      <category>react</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1780596753/uifindr/components/sg64dsmfouzygrhywj2y.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>How the JavaScript Event Loop Actually Works - A Deep Dive</title>
      <link>https://www.codekk.dev/blogs/javascript-event-loop-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/javascript-event-loop-explained</guid>
      <pubDate>Wed, 08 Jul 2026 03:17:12 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[A complete deep dive into the JavaScript event loop - the call stack, callback queue, microtask queue, and how they work together to make async JavaScript possible.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1781840114/uifindr/components/js9h2sj49inlko0qsnf1.jpg" alt="How the JavaScript Event Loop Actually Works - A Deep Dive" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>If you've been writing JavaScript for a while, you've probably heard the phrase "JavaScript is single threaded." And if you've read our blog on <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/sync-async-nodejs-explained">synchronous and asynchronous in Node.js</a>, you already know that JavaScript handles async tasks without blocking everything else.</p><p>But here's the question most tutorials stop short of answering: if JavaScript can only do one thing at a time, how does it handle a timer, a fetch call, and a user click all seemingly at the same time without freezing?</p><p>The answer is the Event Loop. And once you truly understand it, a lot of things about JavaScript that felt confusing or unpredictable will suddenly make complete sense.</p><hr><h2>The Problem the Event Loop Solves</h2><p>Let's set the stage properly.</p><p>JavaScript runs on a single thread. That means it has one call stack, one piece of code running at any given moment. There's no parallel execution happening inside JavaScript itself.</p><p>But your browser or Node.js server needs to handle multiple things at the same time - responding to clicks, fetching data, running timers, handling multiple incoming requests. If JavaScript truly blocked on every slow operation, no web app would be usable.</p><p>The Event Loop is the mechanism that lets JavaScript appear to handle multiple things concurrently, even though it's technically doing one thing at a time. It does this by being very smart about how it manages waiting time.</p><hr><h2>The Moving Parts - What You Need to Know First</h2><p>Before explaining how the event loop works, you need to know the pieces it works with. There are four of them.</p><p><strong>The Call Stack</strong></p><p>This is where your JavaScript code actually executes. Every time you call a function, it gets pushed onto the call stack. When that function returns, it gets popped off. JavaScript executes whatever is at the top of the stack.</p><pre><code class="language-js">function greet(name) {
  return `Hello, ${name}`;
}

function main() {
  const message = greet("Rahul");
  console.log(message);
}

main();
</code></pre><p>Here's what happens on the call stack step by step:</p><pre><code>1. main() is pushed onto the stack
2. greet("Rahul") is pushed onto the stack
3. greet() returns "Hello, Rahul" and is popped off
4. console.log() is pushed onto the stack
5. console.log() finishes and is popped off
6. main() finishes and is popped off
7. Stack is empty
</code></pre><p>Simple, predictable, top to bottom. The issue is that some things take time - and if a slow operation sat on the call stack, nothing else could run until it finished.</p><p><strong>Web APIs / Node.js APIs</strong></p><p>These are capabilities provided by the environment JavaScript is running in, not by JavaScript itself. Things like <code>setTimeout</code>, <code>fetch</code>, file system operations, DOM event listeners - these are not part of the JavaScript language. They're provided by the browser or Node.js, and crucially, they run outside the call stack.</p><p>When JavaScript hits a <code>setTimeout</code> or a <code>fetch</code>, it hands that task off to the browser/Node.js API layer and immediately moves on. The API handles the waiting. JavaScript's call stack is free.</p><p><strong>The Callback Queue (also called the Task Queue or Macrotask Queue)</strong></p><p>When a Web API finishes its work - a timer fires, a fetch returns, a user clicks something - it doesn't immediately put code back on the call stack. Instead, it places the associated callback into the Callback Queue. It waits there until the call stack is empty.</p><p><strong>The Microtask Queue</strong></p><p>This is a separate, higher priority queue. Promises and certain other operations use this queue instead of the Callback Queue. The difference in priority is important - we'll get to that shortly.</p><hr><h2>How the Event Loop Ties All of This Together</h2><p>The Event Loop has one job: check if the call stack is empty, and if it is, take something from one of the queues and push it onto the stack.</p><p>That's it. That's the event loop. It's a loop that keeps checking:</p><pre><code>Is the call stack empty?
  - Yes: is there anything in the Microtask Queue? 
      - Yes: push it to the call stack and run it
      - No: is there anything in the Callback Queue?
          - Yes: push it to the call stack and run it
  - No: wait, check again
</code></pre><p>This loop runs continuously. It never stops. Every tick of this loop is one cycle - check the stack, check the queues, push something if available, repeat.</p><hr><h2>Seeing It in Action - setTimeout</h2><p>Let's trace through a real example:</p><pre><code class="language-js">console.log("1 - Start");

setTimeout(() =&gt; {
  console.log("2 - Inside timeout");
}, 0);

console.log("3 - End");
</code></pre><p>Most beginners expect this to print 1, 2, 3 because they see a zero millisecond delay and think "that's instant." But the actual output is:</p><pre><code>1 - Start
3 - End
2 - Inside timeout
</code></pre><p>Here's exactly what happens:</p><ol><li><p><code>console.log("1 - Start")</code> is pushed to the call stack, runs, logs "1 - Start", gets popped off</p></li><li><p><code>setTimeout(callback, 0)</code> is pushed to the call stack. JavaScript sees it's a Web API call, hands the callback and the timer to the browser API layer, and immediately pops <code>setTimeout</code> off the stack</p></li><li><p>The browser starts a 0ms timer (it's nearly instant but still async)</p></li><li><p><code>console.log("3 - End")</code> is pushed to the stack, runs, logs "3 - End", gets popped off</p></li><li><p>The call stack is now empty</p></li><li><p>The browser's timer finishes, puts the callback into the Callback Queue</p></li><li><p>The Event Loop sees the call stack is empty and the Callback Queue has something - it pushes the callback to the call stack</p></li><li><p>The callback runs, logs "2 - Inside timeout", gets popped off</p></li></ol><p>The zero milliseconds doesn't mean "run immediately." It means "run as soon as possible after the current call stack is empty." That distinction matters a lot in practice.</p><hr><h2>The Microtask Queue - Why Promises Behave Differently</h2><p>Now here's where it gets more interesting. Not all async callbacks go to the same queue.</p><p>Promises use the <strong>Microtask Queue</strong>, which has higher priority than the Callback Queue. After every task that runs on the call stack, the event loop completely drains the Microtask Queue before it even looks at the Callback Queue.</p><p>If you've read our blog on <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/sync-async-nodejs-explained">synchronous and asynchronous in Node.js</a> and you've used Promises or async/await, this is why they behave the way they do under the hood.</p><pre><code class="language-js">console.log("1 - Start");

setTimeout(() =&gt; {
  console.log("2 - setTimeout");
}, 0);

Promise.resolve().then(() =&gt; {
  console.log("3 - Promise");
});

console.log("4 - End");
</code></pre><p>Output:</p><pre><code>1 - Start
4 - End
3 - Promise
2 - setTimeout
</code></pre><p>Let's trace through this:</p><ol><li><p><code>console.log("1 - Start")</code> runs immediately - call stack</p></li><li><p><code>setTimeout</code> callback gets handed to the browser API, will go to the Callback Queue when timer fires</p></li><li><p><code>Promise.resolve().then(callback)</code> - the promise is already resolved, so the callback goes straight to the Microtask Queue</p></li><li><p><code>console.log("4 - End")</code> runs immediately - call stack</p></li><li><p>Call stack is now empty</p></li><li><p>Event Loop checks the Microtask Queue first - finds the Promise callback, runs it, logs "3 - Promise"</p></li><li><p>Microtask Queue is now empty</p></li><li><p>Event Loop checks the Callback Queue - finds the setTimeout callback, runs it, logs "2 - setTimeout"</p></li></ol><p>The Promise ran before the setTimeout even though both were async. This isn't random - it's because of the queue priority. Microtasks always go before macrotasks (callback queue items).</p><hr><h2>Async/Await Is Just Promises in Disguise</h2><p>If you use async/await (which we covered in detail in our <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/sync-async-nodejs-explained">sync and async Node.js blog</a>), you're still dealing with the Microtask Queue. <code>await</code> is essentially <code>Promise.then</code> written in a more readable way.</p><pre><code class="language-js">async function fetchData() {
  console.log("1 - Before await");
  
  const result = await Promise.resolve("data");
  
  console.log("3 - After await:", result);
}

console.log("Start");
fetchData();
console.log("2 - After calling fetchData");
</code></pre><p>Output:</p><pre><code>Start
1 - Before await
2 - After calling fetchData
3 - After await: data
</code></pre><p>When JavaScript hits the <code>await</code> keyword, the async function pauses and returns control to the caller. Whatever comes after <code>await</code> inside that function is essentially a <code>.then()</code> callback waiting in the Microtask Queue. The code outside the function (<code>"2 - After calling fetchData"</code>) runs first because it's still on the main call stack. Once the call stack clears, the continuation of the async function runs from the Microtask Queue.</p><p>This is why in Next.js Server Components (which we covered in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/ssr-csr-ssg-isr-nextjs">SSR, CSR, SSG, and ISR blog</a>), async components work the way they do - the <code>await</code> inside them pauses that component's execution while the rest of the rendering pipeline continues.</p><hr><h2>A More Complex Example - Putting It All Together</h2><p>Here's an example that combines everything, the kind that shows up in JavaScript interviews and genuinely tests whether you understand the event loop:</p><pre><code class="language-js">console.log("1");

setTimeout(() =&gt; console.log("2"), 0);

Promise.resolve()
  .then(() =&gt; {
    console.log("3");
    setTimeout(() =&gt; console.log("4"), 0);
  })
  .then(() =&gt; console.log("5"));

setTimeout(() =&gt; console.log("6"), 0);

console.log("7");
</code></pre><p>Before reading the answer, take a moment and try to trace through it using what you know now.</p><p>Output:</p><pre><code>1
7
3
5
2
6
4
</code></pre><p>Here's why:</p><ul><li><p><code>1</code> and <code>7</code> run synchronously on the call stack first</p></li><li><p>Two <code>setTimeout</code> callbacks (for <code>2</code> and <code>6</code>) go to the Callback Queue</p></li><li><p>The first Promise <code>.then</code> callback (for <code>3</code>) goes to the Microtask Queue</p></li><li><p>Call stack empties</p></li><li><p>Microtask Queue runs first - <code>3</code> logs. Inside that callback, a new <code>setTimeout</code> for <code>4</code> is registered, going to the Callback Queue</p></li><li><p>The <code>.then(() =&gt; console.log("5"))</code> chained on the same Promise goes to the Microtask Queue next</p></li><li><p>Microtask Queue runs - <code>5</code> logs</p></li><li><p>Microtask Queue is now empty</p></li><li><p>Callback Queue runs - <code>2</code> then <code>6</code> then <code>4</code> in the order they were added</p></li></ul><p>Notice that <code>4</code> comes after <code>6</code> even though the Promise that created the <code>4</code> setTimeout ran before <code>6</code>'s setTimeout was registered. By the time the Microtask Queue finished and we started draining the Callback Queue, <code>2</code> and <code>6</code> were already sitting there waiting, so they ran first.</p><hr><h2>Why This Matters in Node.js Specifically</h2><p>In a browser, a blocked call stack means your UI freezes. In Node.js, a blocked call stack means your entire server stops responding to every other request while it waits.</p><p>This is why synchronous operations in request handlers are so dangerous in Node.js - something we explained in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express blog</a>.</p><pre><code class="language-js">// This blocks the entire event loop
app.get("/users", (req, res) =&gt; {
  const data = fs.readFileSync("users.json"); // synchronous - blocks everything
  res.json(JSON.parse(data));
});

// This doesn't - the event loop stays free
app.get("/users", async (req, res) =&gt; {
  const data = await fs.promises.readFile("users.json"); // async - non-blocking
  res.json(JSON.parse(data));
});
</code></pre><p>When the synchronous version hits <code>readFileSync</code>, it sits on the call stack and blocks. No other request can be handled. No timers fire. Nothing moves until that file is read.</p><p>When the async version hits <code>await</code>, it hands the file reading to Node.js's underlying I/O layer, the function pauses and exits the call stack, and the event loop is free to handle other requests. When the file is ready, the continuation goes into the Microtask Queue and picks up where it left off.</p><p>This is why Node.js can handle thousands of concurrent connections on a single thread. It's not doing them all simultaneously - it's managing the waiting time so efficiently that it seems like it is.</p><hr><h2>One Thing That Can Still Break Everything - Blocking the Event Loop</h2><p>Even with all of this in place, you can still accidentally block the event loop with pure JavaScript code that runs for too long.</p><pre><code class="language-js">// This blocks the event loop completely
app.get("/compute", (req, res) =&gt; {
  let sum = 0;
  for (let i = 0; i &lt; 10_000_000_000; i++) {
    sum += i;
  }
  res.json({ sum });
});
</code></pre><p>This loop is pure synchronous JavaScript. There's no I/O, no async, no Web API to hand off to. It just runs on the call stack until it finishes - and nothing else can happen during that time. Every other request to your server is frozen while this runs.</p><p>This is why CPU-heavy work (like image processing, complex calculations, or large data transformations) in Node.js should be moved to worker threads or separate processes, not handled in the main event loop. The event loop is designed for I/O-heavy workloads where most of the time is spent waiting, not computing.</p><hr><h2>The Node.js Event Loop Has Extra Phases</h2><p>The browser's event loop and Node.js's event loop are similar in concept but Node.js has additional phases built on top. Node.js uses a library called libuv under the hood, and its event loop has specific phases for things like:</p><ul><li><p>Timers (<code>setTimeout</code>, <code>setInterval</code>)</p></li><li><p>I/O callbacks (file system, network)</p></li><li><p><code>setImmediate</code> callbacks (a Node.js specific API)</p></li><li><p>Close callbacks (cleanup)</p></li></ul><p>For most day to day Node.js development you don't need to know these phases in detail. But if you ever wonder why <code>setImmediate</code> fires before a <code>setTimeout(fn, 0)</code> in certain situations in Node.js, it's because of these phases running in a specific order. Worth knowing it exists even if you don't need to memorize the details.</p><hr><h2>The Mental Model Worth Keeping</h2><p>After all of this, here's the simplest version you can hold in your head:</p><p>JavaScript has one call stack. Slow things get handed off to the environment (browser or Node.js) and come back later through queues. The event loop keeps checking: is the stack empty? Run microtasks first (Promises, await continuations), then macrotasks (setTimeout, setInterval, I/O callbacks).</p><p>Synchronous code always runs first. Microtasks run next, completely, before any macrotask gets a chance. Macrotasks run one at a time, with a full microtask drain between each one.</p><p>Once this clicks, JavaScript's async behavior stops feeling unpredictable. It starts feeling logical, because it is.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/javascript-event-loop-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>javascript</category>
      <category>nodejs</category>
      <category>event-loop</category>
      <category>callback-queue</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1781840114/uifindr/components/js9h2sj49inlko0qsnf1.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>How JWT Authentication Actually Works - Explained Properly</title>
      <link>https://www.codekk.dev/blogs/how-jwt-authentication-works</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/how-jwt-authentication-works</guid>
      <pubDate>Mon, 06 Jul 2026 03:10:21 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Most developers use JWT without really understanding how it works. This blog explains what's inside a JWT, how signature verification works without a database call, and how to implement it properly in Express with refresh tokens and role-based authorization.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1783307380/uifindr/components/cpq4wj17o59pthswvrxl.jpg" alt="How JWT Authentication Actually Works - Explained Properly" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>If you've built any kind of API that needs authentication, you've probably used JWT or at least heard of it. Most developers know enough to copy a working example, generate a token on login, and verify it on protected routes. But if someone asked you to explain what's actually inside that token, how the verification works without a database call, or what happens if someone tries to tamper with it - could you answer that clearly?</p><p>This blog covers all of that. Not just how to implement JWT, but how it actually works underneath, so you can use it confidently and understand the security decisions you're making.</p><hr><h2>The Problem JWT Solves</h2><p>Before getting into what JWT is, it helps to understand why it exists.</p><p>HTTP is stateless. Every request your server receives is completely independent - the server has no memory of previous requests by default. So when a user logs in and then makes a request to a protected route, the server has no built-in way to know that this request is coming from someone who already authenticated.</p><p>The traditional solution to this is <strong>sessions</strong>. When a user logs in, the server creates a session, stores it in memory or a database, and sends back a session ID in a cookie. On every subsequent request, the browser sends that cookie, the server looks up the session ID in storage, finds the user, and proceeds.</p><p>This works, but it has a real limitation. Every request requires a database lookup to validate the session. At scale, this adds up. It also gets complicated when you have multiple servers - if a user's session is stored on Server A, requests that hit Server B won't find it.</p><p>JWT takes a different approach. Instead of storing session data on the server, the server gives the client a token that contains the user information itself. The client sends this token on every request, and the server can verify it and read the user data from it - without touching a database at all.</p><hr><h2>What a JWT Actually Is</h2><p>JWT stands for JSON Web Token. It's a string that looks like this:</p><pre><code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJyYWh1bEBleGFtcGxlLmNvbSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzE2MDAwMDAwLCJleHAiOjE3MTYwODY0MDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
</code></pre><p>It looks like gibberish but it's actually three distinct parts separated by dots. Each part is Base64URL encoded - meaning it's just regular data encoded into a URL-safe string format. It's not encrypted. Anyone can decode it and read what's inside.</p><p>The three parts are the <strong>header</strong>, the <strong>payload</strong>, and the <strong>signature</strong>.</p><p><strong>Part 1 - The Header</strong></p><pre><code class="language-json">{
  "alg": "HS256",
  "typ": "JWT"
}
</code></pre><p>The header tells you what type of token this is and which algorithm was used to create the signature. <code>HS256</code> means HMAC with SHA-256, which is the most common signing algorithm for JWTs.</p><p><strong>Part 2 - The Payload</strong></p><pre><code class="language-json">{
  "id": 1,
  "email": "rahul@example.com",
  "role": "user",
  "iat": 1716000000,
  "exp": 1716086400
}
</code></pre><p>This is the actual data. The fields you put here are called <strong>claims</strong>. Some are standard - <code>iat</code> is "issued at" (a Unix timestamp of when the token was created) and <code>exp</code> is "expires at" (when the token stops being valid). Everything else is custom data you add based on what your application needs.</p><p><code>iat</code> and <code>exp</code> are Unix timestamps - seconds since January 1, 1970. The difference between those two timestamps above is 86400 seconds, which is exactly 24 hours. So this token is valid for one day.</p><p><strong>Part 3 - The Signature</strong></p><p>This is the part that makes JWT secure. The signature is created by taking the encoded header, the encoded payload, a secret key that only your server knows, and running them through the hashing algorithm specified in the header.</p><pre><code>HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  your-secret-key
)
</code></pre><p>The resulting signature is what gets appended as the third part of the token.</p><p>When your server receives a token, it takes the header and payload from that token, runs the same HMAC operation with its secret key, and compares the result to the signature in the token. If they match, the token is valid and hasn't been tampered with. If someone changed even a single character in the payload, the signature would no longer match.</p><p>This is the key insight: <strong>the server never needs to store the token or look it up in a database.</strong> It just needs its secret key to verify any token it receives. The math does the work.</p><hr><h2>The JWT Flow - How It Works in Practice</h2><p>Here's the complete flow from login to accessing a protected route:</p><pre><code>1. User sends email and password to POST /api/auth/login

2. Server checks credentials against the database
   - If wrong: return 401 Unauthorized
   - If correct: continue

3. Server creates a JWT containing the user's id, email, role
   Signs it with the secret key
   Sets an expiry time

4. Server sends the JWT back to the client

5. Client stores the token (localStorage or a cookie)
   Sends it in the Authorization header on future requests:
   Authorization: Bearer &lt;token&gt;

6. Server receives the request, extracts the token from the header
   Verifies the signature using the secret key
   Checks the expiry time
   If valid: reads the user data from the payload, processes the request
   If invalid or expired: return 401 Unauthorized
</code></pre><p>Notice that step 6 involves zero database calls for the verification itself. The server just does cryptographic verification. That's the performance advantage of JWT over session-based auth.</p><hr><h2>Building JWT Auth in Express - Step by Step</h2><p>If you've set up an Express server following our <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express blog</a>, this is how you add JWT authentication on top of it.</p><p>First install the <code>jsonwebtoken</code> package:</p><pre><code>npm install jsonwebtoken bcryptjs
</code></pre><p>We're also installing <code>bcryptjs</code> because you should never store plain text passwords. We'll hash them before saving and compare hashes on login.</p><p><strong>Setting up your secret key:</strong></p><p>Your JWT secret key should be a long random string that only your server knows. Store it in an environment variable, never hardcode it.</p><pre><code>JWT_SECRET=your-very-long-random-secret-key-here-make-it-at-least-32-characters
</code></pre><p><strong>The login route - generating a token:</strong></p><pre><code class="language-js">const express = require("express");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
const router = express.Router();

// Simulated user database - in a real app this comes from your actual database
const users = [
  {
    id: 1,
    email: "rahul@example.com",
    // this is the bcrypt hash of "password123"
    password: "$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi"
  }
];

router.post("/login", async (req, res) =&gt; {
  try {
    const { email, password } = req.body;

    // find the user
    const user = users.find(u =&gt; u.email === email);
    if (!user) {
      return res.status(401).json({ error: "Invalid credentials" });
    }

    // compare the provided password with the stored hash
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(401).json({ error: "Invalid credentials" });
    }

    // create the token
    const token = jwt.sign(
      {
        id: user.id,
        email: user.email
      },
      process.env.JWT_SECRET,
      { expiresIn: "24h" }
    );

    res.json({
      message: "Login successful",
      token
    });

  } catch (err) {
    res.status(500).json({ error: "Internal server error" });
  }
});

module.exports = router;
</code></pre><p>A few things worth explaining here. We return the same error message <code>"Invalid credentials"</code> whether the email doesn't exist or the password is wrong. This is intentional - if you returned different messages for each case, an attacker could use your login endpoint to figure out which emails are registered in your system. Keeping the error generic prevents that.</p><p><code>jwt.sign()</code> takes three arguments - the payload (what to put in the token), the secret key, and options. The <code>expiresIn</code> option accepts a string like <code>"24h"</code>, <code>"7d"</code>, or <code>"15m"</code>, or a number in seconds.</p><p><strong>The authentication middleware:</strong></p><p>This is what protects your routes. It runs before your route handler and either allows the request through or rejects it. If you want to understand middleware more deeply, we covered it in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express blog</a>.</p><pre><code class="language-js">const jwt = require("jsonwebtoken");

function authenticate(req, res, next) {
  // get the token from the Authorization header
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    return res.status(401).json({ error: "No token provided" });
  }

  const token = authHeader.split(" ")[1]; // extract the token after "Bearer "

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // attach the decoded user data to the request
    next();
  } catch (err) {
    if (err.name === "TokenExpiredError") {
      return res.status(401).json({ error: "Token has expired" });
    }
    return res.status(401).json({ error: "Invalid token" });
  }
}

module.exports = authenticate;
</code></pre><p><code>jwt.verify()</code> does two things at once - it verifies the signature and checks the expiry. If either fails, it throws an error. We catch that error and return an appropriate response. The <code>TokenExpiredError</code> check is useful because it lets the client know specifically that the token expired, so it can redirect to login rather than showing a generic error.</p><p>When verification succeeds, <code>decoded</code> contains the payload we put in the token - the user's id and email. We attach it to <code>req.user</code> so any route handler after this middleware can access it.</p><p><strong>Using the middleware on protected routes:</strong></p><pre><code class="language-js">const express = require("express");
const authenticate = require("../middleware/authenticate");
const router = express.Router();

// public route - no middleware
router.get("/public", (req, res) =&gt; {
  res.json({ message: "Anyone can see this" });
});

// protected route - requires valid token
router.get("/profile", authenticate, (req, res) =&gt; {
  // req.user is available here because authenticate attached it
  res.json({
    message: "This is your profile",
    user: req.user
  });
});

// protected route - only admins
router.delete("/users/:id", authenticate, authorizeAdmin, (req, res) =&gt; {
  res.json({ message: `User ${req.params.id} deleted` });
});

module.exports = router;
</code></pre><p>The <code>authenticate</code> middleware is passed directly as an argument before the route handler. Express calls them in order - first <code>authenticate</code> runs, and if it calls <code>next()</code>, then the route handler runs. If <code>authenticate</code> returns a 401, the route handler never runs at all.</p><p><strong>Role-based authorization:</strong></p><p>Authentication answers "who are you?" Authorization answers "what are you allowed to do?" They're different things. Here's a simple authorization middleware that checks for an admin role:</p><pre><code class="language-js">function authorizeAdmin(req, res, next) {
  if (req.user.role !== "admin") {
    return res.status(403).json({ error: "Admin access required" });
  }
  next();
}
</code></pre><p>Notice it returns 403 (Forbidden) not 401 (Unauthorized). The user is authenticated - we know who they are. They're just not permitted to do this specific thing. If you want a refresher on the difference between 401 and 403, we covered exactly this in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/http-status-codes-explained">HTTP status codes blog</a>.</p><hr><h2>Where to Store the Token on the Client Side</h2><p>This is a security decision worth thinking about properly. You have two main options.</p><p><strong>localStorage</strong></p><p>Easy to use from JavaScript, persists across browser sessions. The downside is that it's accessible to any JavaScript running on your page. If your app has an XSS (Cross-Site Scripting) vulnerability - where an attacker can inject JavaScript into your page - they can steal tokens straight out of localStorage.</p><p><strong>HttpOnly Cookies</strong></p><p>Cookies with the <code>HttpOnly</code> flag cannot be accessed by JavaScript at all. They're automatically sent with every request to the same domain, and attackers cannot read them through XSS because JavaScript simply has no access to <code>HttpOnly</code> cookies. This makes them safer for storing auth tokens.</p><p>The tradeoff is that cookies require more setup and you need to handle CORS correctly - which we covered in detail in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/what-is-cors-and-how-to-fix-it-in-express">CORS blog</a>. Specifically, you need <code>credentials: true</code> in your CORS configuration and <code>Access-Control-Allow-Credentials: true</code> in your response headers for cookies to be sent on cross-origin requests.</p><p>For most applications, <code>HttpOnly</code> cookies are the more secure choice. For simpler APIs where XSS is well-controlled, localStorage is commonly used and works fine. Both have tradeoffs - the important thing is making the choice consciously, not just going with whatever the tutorial used.</p><hr><h2>Token Expiry and Refresh Tokens</h2><p>Short-lived tokens are more secure. If a token gets stolen somehow, a 15-minute expiry means the attacker only has a 15-minute window to use it. But asking users to log in every 15 minutes is a bad experience.</p><p>The solution is a <strong>refresh token system</strong>. Here's how it works:</p><p>When a user logs in, the server issues two tokens:</p><ol><li><p>An <strong>access token</strong> - short-lived (15 minutes to 1 hour), used for API requests</p></li><li><p>A <strong>refresh token</strong> - long-lived (7 to 30 days), used only to get new access tokens</p></li></ol><p>The access token is what you send in the <code>Authorization</code> header on every request. When it expires, instead of redirecting to login, the client sends the refresh token to a special endpoint like <code>POST /api/auth/refresh</code>. The server validates the refresh token and issues a new access token.</p><pre><code class="language-js">router.post("/refresh", async (req, res) =&gt; {
  const { refreshToken } = req.body;

  if (!refreshToken) {
    return res.status(401).json({ error: "Refresh token required" });
  }

  try {
    const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);

    // issue a new short-lived access token
    const newAccessToken = jwt.sign(
      { id: decoded.id, email: decoded.email },
      process.env.JWT_SECRET,
      { expiresIn: "15m" }
    );

    res.json({ accessToken: newAccessToken });

  } catch (err) {
    return res.status(401).json({ error: "Invalid or expired refresh token" });
  }
});
</code></pre><p>Refresh tokens use a different secret key (<code>JWT_REFRESH_SECRET</code>) from access tokens. This way, even if one key is somehow compromised, the other isn't affected.</p><p>This pattern is used by most production authentication systems and gives you the security of short-lived tokens without constantly asking users to log in again.</p><hr><h2>What JWT Does Not Solve</h2><p>JWT is not a complete authentication solution on its own and it's worth being clear about its limitations.</p><p><strong>You cannot invalidate a JWT before it expires.</strong> This is the most cited limitation. Since the server doesn't store tokens, there's no way to say "this token is no longer valid" without waiting for it to expire naturally. If a user logs out, the token still technically works until it expires. If an account is compromised and you want to revoke access immediately, you can't without either maintaining a token blocklist (which adds back the database lookup you were trying to avoid) or using very short expiry times with refresh tokens.</p><p><strong>The payload is readable by anyone.</strong> Base64URL encoding is not encryption. Anyone who has the token can decode and read its payload. Never put sensitive information in a JWT payload - no passwords, no credit card numbers, no private data. Only put what the server needs to identify and authorize the user.</p><p><strong>The secret key must stay secret.</strong> If your <code>JWT_SECRET</code> is exposed, anyone can generate valid tokens for any user. Keep it in environment variables, rotate it if you think it's been compromised, and make it long and random.</p><hr><h2>Putting It All Together</h2><p>Here's how your Express app structure looks with JWT auth added:</p><pre><code>src/
- middleware/
    authenticate.js     (verifies token, adds req.user)
    authorizeAdmin.js   (checks role)
- routes/
    auth.js            (login, refresh)
    users.js           (protected routes using authenticate middleware)
- index.js             (main server file)
</code></pre><pre><code class="language-js">// index.js
const express = require("express");
const cors = require("cors");
const authRoutes = require("./routes/auth");
const userRoutes = require("./routes/users");

const app = express();

app.use(cors({ origin: "http://localhost:5173", credentials: true }));
app.use(express.json());

app.use("/api/auth", authRoutes);
app.use("/api/users", userRoutes);

app.use((err, req, res, next) =&gt; {
  console.error(err.stack);
  res.status(500).json({ error: "Internal server error" });
});

app.listen(3000, () =&gt; console.log("Server running on port 3000"));
</code></pre><p>Clean, organized, and each piece has a clear job. The CORS setup here directly uses what we covered in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/what-is-cors-and-how-to-fix-it-in-express">CORS blog</a> - specific origin and credentials enabled for cookie support.</p><hr><h2>The Mental Model Worth Keeping</h2><p>JWT works because of cryptographic signatures, not storage. The server signs a token with a secret key. Anyone can read the token, but nobody can change it without invalidating the signature. The server verifies that signature on every request using the same secret key.</p><p>That's the whole system. The header says what algorithm was used. The payload carries the data. The signature proves nothing was tampered with.</p><p>Use short expiry times. Store tokens in HttpOnly cookies when security matters. Never put sensitive data in the payload. Keep your secret key actually secret.</p><p>Once you understand the mechanism, JWT stops feeling like magic and starts feeling like a sensible solution to a straightforward problem.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/how-jwt-authentication-works">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>backend</category>
      <category>nodejs</category>
      <category>expressjs</category>
      <category>jwt</category>
      <category>authentication</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1783307380/uifindr/components/cpq4wj17o59pthswvrxl.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>Synchronous and Asynchronous in Node.js - How JavaScript Actually Handles Tasks</title>
      <link>https://www.codekk.dev/blogs/sync-async-nodejs-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/sync-async-nodejs-explained</guid>
      <pubDate>Thu, 02 Jul 2026 02:58:31 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[One of the most important concepts in Node.js and one of the most misunderstood. Covers the event loop, callbacks, Promises, and async/await with real examples.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1781664161/uifindr/components/oq7pvzxwk0ve0puy9h7e.jpg" alt="Synchronous and Asynchronous in Node.js - How JavaScript Actually Handles Tasks" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>If you have been writing JavaScript or Node.js for any amount of time, you have come across words like async, await, callbacks, and Promises. Maybe you understood them well enough to get your code working. Maybe you just copied a pattern from Stack Overflow and moved on.</p><p>But if someone asked you to explain what asynchronous actually means, or why Node.js handles things differently from other languages - could you answer that clearly?</p><p>This blog is about building that understanding properly. Not just how to write async code, but why it works the way it does.</p><hr><h2>Start Here - What Synchronous Actually Means</h2><p>Synchronous means one thing at a time, in order. Each line of code runs, finishes completely, and only then does the next line start.</p><pre><code class="language-js">console.log("First");
console.log("Second");
console.log("Third");
</code></pre><p>Output:</p><pre><code>First
Second
Third
</code></pre><p>Simple. Predictable. Each line waits for the one before it.</p><p>Now here is where it becomes a problem. Some tasks take time - reading a file from disk, fetching data from an API, querying a database. If JavaScript ran these synchronously, it would stop everything and wait for each task to finish before moving to the next one.</p><pre><code class="language-js">// Imagine this is a synchronous database call
const users = database.getUsers(); // waits 500ms
const orders = database.getOrders(); // waits 500ms after users finishes
const products = database.getProducts(); // waits 500ms after orders finishes

console.log("Done"); // only runs after 1500ms total
</code></pre><p>In a server handling hundreds of requests, this would be a disaster. Every request would have to wait in line while the previous one finishes. One slow database query would block everything else.</p><p>This is exactly the problem Node.js was designed to solve.</p><hr><h2>JavaScript is Single Threaded - And That is Fine</h2><p>Here is something that surprises a lot of developers: JavaScript runs on a single thread. It can only do one thing at a time.</p><p>But then how does it handle multiple requests without blocking? How does it run code after a delay without freezing everything?</p><p>The answer is the <strong>Event Loop</strong> - and understanding it changes how you think about asynchronous code entirely.</p><hr><h2>The Event Loop - The Heart of Node.js</h2><p>Think of the event loop like a chef in a kitchen. The chef cannot cook two dishes at the exact same time with their own hands. But they can put something in the oven, set a timer, and while that is baking, start chopping vegetables. When the timer goes off, they go back to the oven.</p><p>The chef is single threaded. But they are not blocked waiting for the oven.</p><p>Node.js works the same way. When it hits a task that will take time - like a file read or a network request - it hands that task off to the operating system and moves on to the next thing. When the operating system finishes the task, it puts the result in a queue. The event loop keeps checking that queue and runs the callback when it is ready.</p><pre><code>Your Code -&gt; Hits async task -&gt; Hands it off -&gt; Moves to next line
                                     |
                        OS handles the slow task
                                     |
                        Task finishes -&gt; Result goes into queue
                                     |
                        Event loop picks it up -&gt; Runs your callback
</code></pre><p>This is why Node.js can handle thousands of concurrent connections without needing thousands of threads. It is not doing multiple things at once - it is managing its waiting time efficiently.</p><hr><h2>Synchronous Code in Node.js</h2><p>Node.js has both synchronous and asynchronous versions of most built-in operations. The synchronous ones block everything until they finish.</p><pre><code class="language-js">const fs = require("fs");

// Synchronous - blocks everything until the file is read
const data = fs.readFileSync("users.txt", "utf8");
console.log(data);
console.log("This only runs after the file is fully read");
</code></pre><p>The <code>Sync</code> at the end of <code>readFileSync</code> is the signal. It means - stop here, wait for this to finish, then continue.</p><p>This is fine in some situations - like a startup script that reads a config file before your server starts. But inside a request handler that thousands of users hit? Never use synchronous file or network operations there. You will block every other user while one person's request is being handled.</p><hr><h2>Asynchronous Code - Three Ways to Write It</h2><p>JavaScript has gone through three different patterns for writing async code. You will see all three in the wild so it is worth understanding each one.</p><h3>1. Callbacks - The Original Way</h3><p>A callback is a function you pass into another function, to be called when the async task finishes.</p><pre><code class="language-js">const fs = require("fs");

fs.readFile("users.txt", "utf8", function(err, data) {
  if (err) {
    console.error("Error reading file:", err);
    return;
  }
  console.log(data);
});

console.log("This runs immediately, before the file is read");
</code></pre><p>Output:</p><pre><code>This runs immediately, before the file is read
(file contents appear here after the file is read)
</code></pre><p>The <code>readFile</code> call does not block. Node.js hands the file reading to the OS, moves to the next line, and when the file is ready, calls your callback function with the result.</p><p>The <code>err</code> parameter comes first - this is a Node.js convention called <strong>error-first callbacks</strong>. Always check for errors before using the data.</p><p><strong>The problem with callbacks - Callback Hell:</strong></p><p>When you need to do multiple async tasks in sequence, callbacks start nesting inside each other:</p><pre><code class="language-js">fs.readFile("users.txt", "utf8", function(err, users) {
  if (err) return console.error(err);

  fs.readFile("orders.txt", "utf8", function(err, orders) {
    if (err) return console.error(err);

    fs.readFile("products.txt", "utf8", function(err, products) {
      if (err) return console.error(err);

      // Finally do something with all three files
      console.log(users, orders, products);
    });
  });
});
</code></pre><p>This is called callback hell - code that keeps nesting deeper and deeper to the right. Hard to read, hard to maintain, hard to handle errors properly. This pattern led to Promises.</p><hr><h3>2. Promises - A Cleaner Way</h3><p>A Promise is an object that represents a value that will be available in the future. It is either pending, resolved (success), or rejected (failure).</p><pre><code class="language-js">const fs = require("fs").promises;

fs.readFile("users.txt", "utf8")
  .then(data =&gt; {
    console.log(data);
  })
  .catch(err =&gt; {
    console.error("Error:", err);
  });

console.log("This still runs immediately");
</code></pre><p>Promises let you chain <code>.then()</code> calls instead of nesting callbacks:</p><pre><code class="language-js">fs.readFile("users.txt", "utf8")
  .then(users =&gt; {
    console.log("Got users");
    return fs.readFile("orders.txt", "utf8"); // return next promise
  })
  .then(orders =&gt; {
    console.log("Got orders");
    return fs.readFile("products.txt", "utf8");
  })
  .then(products =&gt; {
    console.log("Got all three files");
  })
  .catch(err =&gt; {
    console.error("Something failed:", err);
  });
</code></pre><p>One <code>.catch()</code> at the end handles errors from any step in the chain. Much cleaner than nested callbacks.</p><p><strong>Running multiple async tasks at the same time:</strong></p><p>If the tasks do not depend on each other, you can run them in parallel using <code>Promise.all</code>:</p><pre><code class="language-js">Promise.all([
  fs.readFile("users.txt", "utf8"),
  fs.readFile("orders.txt", "utf8"),
  fs.readFile("products.txt", "utf8")
])
  .then(([users, orders, products]) =&gt; {
    console.log("All three files ready at the same time");
  })
  .catch(err =&gt; {
    console.error("One of them failed:", err);
  });
</code></pre><p>Instead of waiting 500ms + 500ms + 500ms in sequence, all three run at the same time and you wait for whichever takes the longest. Significantly faster.</p><hr><h3>3. Async/Await - The Modern Way</h3><p>Async/await is built on top of Promises. It does not replace them - it is just a cleaner syntax for working with them. Code that uses async/await looks almost like synchronous code, which makes it much easier to read.</p><pre><code class="language-js">const fs = require("fs").promises;

async function readFiles() {
  try {
    const users = await fs.readFile("users.txt", "utf8");
    console.log("Got users:", users);

    const orders = await fs.readFile("orders.txt", "utf8");
    console.log("Got orders:", orders);

    return { users, orders };
  } catch (err) {
    console.error("Something went wrong:", err);
  }
}

readFiles();
console.log("This still runs immediately - readFiles is async");
</code></pre><p>The <code>await</code> keyword pauses execution inside the <code>async</code> function until the Promise resolves - but it does not block the event loop. Other code can still run while this function is waiting.</p><p>The <code>async</code> keyword before a function means it always returns a Promise. You cannot use <code>await</code> inside a function that is not marked <code>async</code>.</p><p><strong>Async/await with parallel execution:</strong></p><p>You can still use <code>Promise.all</code> with async/await:</p><pre><code class="language-js">async function readAllFiles() {
  try {
    const [users, orders, products] = await Promise.all([
      fs.readFile("users.txt", "utf8"),
      fs.readFile("orders.txt", "utf8"),
      fs.readFile("products.txt", "utf8")
    ]);

    console.log("All files ready");
    return { users, orders, products };
  } catch (err) {
    console.error("Error:", err);
  }
}
</code></pre><p>Clean, readable, and all three files are fetched in parallel.</p><hr><h2>A Real Express Example - Async in Practice</h2><p>This is what async/await looks like in a real Node.js and Express API. Imagine you are fetching a user from a database:</p><pre><code class="language-js">const express = require("express");
const app = express();

// Simulating a database call that takes time
function getUserFromDB(id) {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; {
      if (id === 1) {
        resolve({ id: 1, name: "Rahul", email: "rahul@example.com" });
      } else {
        reject(new Error("User not found"));
      }
    }, 300); // simulates 300ms database delay
  });
}

app.get("/users/:id", async (req, res) =&gt; {
  try {
    const user = await getUserFromDB(parseInt(req.params.id));
    res.json(user);
  } catch (err) {
    res.status(404).json({ error: err.message });
  }
});

app.listen(3000, () =&gt; console.log("Server running on port 3000"));
</code></pre><p>While this route handler is waiting for the database, Node.js is free to handle other incoming requests. That is the event loop at work. No blocking, no waiting in line.</p><hr><h2>Common Mistakes With Async Code</h2><h3>Forgetting await</h3><pre><code class="language-js">async function getUser() {
  const user = getUserFromDB(1); // missing await
  console.log(user); // logs Promise { &lt;pending&gt; } not the actual user
}
</code></pre><p>Without <code>await</code>, you get the Promise object itself, not the resolved value. Always check you have <code>await</code> in front of async calls.</p><h3>Using await in a loop inefficiently</h3><pre><code class="language-js">// Slow - each request waits for the previous one
async function getUsersSequentially(ids) {
  const users = [];
  for (const id of ids) {
    const user = await getUserFromDB(id); // waits for each one
    users.push(user);
  }
  return users;
}

// Fast - all requests run at the same time
async function getUsersInParallel(ids) {
  const users = await Promise.all(ids.map(id =&gt; getUserFromDB(id)));
  return users;
}
</code></pre><p>If the tasks inside a loop do not depend on each other, run them in parallel with <code>Promise.all</code>. The difference in speed can be significant with multiple database calls.</p><h3>Not handling errors</h3><pre><code class="language-js">// No error handling - if this fails, your server might crash
async function riskyOperation() {
  const data = await fetch("https://api.example.com/data").then(r =&gt; r.json());
  return data;
}

// Always wrap async operations in try/catch
async function safeOperation() {
  try {
    const data = await fetch("https://api.example.com/data").then(r =&gt; r.json());
    return data;
  } catch (err) {
    console.error("Fetch failed:", err);
    return null;
  }
}
</code></pre><p>Unhandled Promise rejections can crash your Node.js server. Always use try/catch in async functions.</p><hr><h2>Synchronous vs Asynchronous - When to Use Which</h2><p><strong>Use synchronous code when:</strong></p><ul><li><p>Running setup tasks before your server starts (reading config files, checking environment variables)</p></li><li><p>Writing simple scripts that run once and exit</p></li><li><p>The task is instant and involves no I/O (calculations, string manipulation, array operations)</p></li></ul><p><strong>Use asynchronous code when:</strong></p><ul><li><p>Reading or writing files during a request</p></li><li><p>Making database queries</p></li><li><p>Calling external APIs</p></li><li><p>Anything that involves waiting for data from outside your application</p></li></ul><p>The rule of thumb: if it involves the network, the file system, or a database - make it async.</p><hr><h2>The Mental Model Worth Keeping</h2><p>Synchronous code runs line by line and blocks everything while waiting.</p><p>Asynchronous code starts a task, moves on, and comes back when the result is ready - without blocking anything else.</p><p>Node.js is built around this model. The event loop is what makes it work. Callbacks, Promises, and async/await are just different ways of writing code that works with this model - each one cleaner than the one before it.</p><p>If you are writing Node.js today, async/await is the pattern to default to. It is the most readable, the easiest to debug, and the one you will see in most modern codebases. Just remember it is still Promises underneath - and knowing that will help you when things get more complex.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/sync-async-nodejs-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>backend</category>
      <category>nodejs</category>
      <category>async-await</category>
      <category>event-loop</category>
      <category>callbacks</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1781664161/uifindr/components/oq7pvzxwk0ve0puy9h7e.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>How React&apos;s useEffect Actually Works - And Why Developers Misuse It</title>
      <link>https://www.codekk.dev/blogs/how-react-useeffect-actually-works</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/how-react-useeffect-actually-works</guid>
      <pubDate>Thu, 02 Jul 2026 02:58:31 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[useEffect is one of the most used hooks in React and also one of the most misunderstood. This guide covers the dependency array, cleanup functions, the double render, and common mistakes that cause bugs.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1781054019/uifindr/components/ffgeuyhc1ignzzfz2fzq.jpg" alt="How React&apos;s useEffect Actually Works - And Why Developers Misuse It" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>If you've been writing React for a while, you've used <code>useEffect</code>. Probably a lot. But if someone asked you to explain exactly when it runs, why it sometimes runs twice, or when you should avoid it entirely - could you answer confidently?</p><p>Most developers can't. And that's not because they're bad developers. It's because <code>useEffect</code> is one of those things that's easy to use but genuinely tricky to understand. You can build a lot of stuff with it while still having a fuzzy mental model of what's actually happening.</p><p>This blog is about fixing that.</p><hr><h2>Start Here - What useEffect Is Actually For</h2><p>React components have one job: take some data (props and state) and return UI. That's it. The function runs, returns JSX, done.</p><p>But real apps need to do things beyond just rendering UI. Fetching data from an API. Setting up an event listener. Starting a timer. Connecting to a WebSocket. These are called <strong>side effects</strong> - things that happen outside of the normal render flow and interact with the world beyond your component.</p><p><code>useEffect</code> is React's way of saying: "run this code after the render, as a side effect."</p><pre><code class="language-jsx">import { useEffect } from "react";

function ProfilePage({ userId }) {
  useEffect(() =&gt; {
    console.log("Component rendered");
  });

  return &lt;div&gt;Profile Page&lt;/div&gt;;
}
</code></pre><p>This effect runs after every render. But that's rarely what you want, which brings us to the dependency array.</p><hr><h2>The Dependency Array - The Part Everyone Gets Wrong</h2><p><code>useEffect</code> takes two arguments. The first is the function you want to run. The second is the <strong>dependency array</strong> - and this is where most of the confusion lives.</p><pre><code class="language-jsx">useEffect(() =&gt; {
  // your effect code
}, [dependency1, dependency2]);
</code></pre><p>The dependency array tells React when to re-run the effect. There are three cases:</p><p><strong>No dependency array - runs after every render:</strong></p><pre><code class="language-jsx">useEffect(() =&gt; {
  console.log("Runs after every single render");
});
</code></pre><p>Every time your component re-renders for any reason - state change, parent re-render, anything - this effect runs. This is almost never what you want.</p><p><strong>Empty dependency array - runs only once after the first render:</strong></p><pre><code class="language-jsx">useEffect(() =&gt; {
  console.log("Runs once when component mounts");
}, []);
</code></pre><p>This runs exactly once - right after the component first appears on the screen. Common use case: fetching initial data.</p><p><strong>With dependencies - runs when those specific values change:</strong></p><pre><code class="language-jsx">useEffect(() =&gt; {
  console.log("userId changed, fetching new profile");
  fetchUserProfile(userId);
}, [userId]);
</code></pre><p>This runs after the first render and then again any time <code>userId</code> changes. If <code>userId</code> stays the same between renders, the effect is skipped.</p><p>The mental model worth keeping: <strong>React compares the current dependency values with the previous ones. If any of them changed, the effect runs again.</strong></p><hr><h2>The Cleanup Function - Most Developers Skip This</h2><p>Here's something a lot of developers don't know: <code>useEffect</code> can return a function. That returned function is the <strong>cleanup function</strong> and React calls it before running the effect again and when the component unmounts.</p><pre><code class="language-jsx">useEffect(() =&gt; {
  const intervalId = setInterval(() =&gt; {
    console.log("tick");
  }, 1000);

  // This is the cleanup function
  return () =&gt; {
    clearInterval(intervalId);
  };
}, []);
</code></pre><p>Without the cleanup here, every time this component mounts a new interval starts - and when the component unmounts, the interval keeps running in the background. Memory leak.</p><p>Another classic example with event listeners:</p><pre><code class="language-jsx">useEffect(() =&gt; {
  function handleResize() {
    setWindowWidth(window.innerWidth);
  }

  window.addEventListener("resize", handleResize);

  return () =&gt; {
    window.removeEventListener("resize", handleResize);
  };
}, []);
</code></pre><p>If you add an event listener inside <code>useEffect</code> and don't remove it in the cleanup, you end up with multiple listeners stacking up every time the component mounts. The cleanup function is not optional when you're working with timers, listeners, or subscriptions.</p><hr><h2>Why Does useEffect Run Twice in Development?</h2><p>This catches almost every React developer off guard the first time they see it.</p><p>You write a <code>useEffect</code> with an empty dependency array. You expect it to run once. But in development, you check the console and it runs twice. What's going on?</p><p>This is intentional behavior introduced in React 18. In development mode, React's <strong>Strict Mode</strong> deliberately mounts your component, unmounts it, and mounts it again. The purpose is to help you find bugs - specifically, effects that don't clean up properly.</p><p>React is essentially doing this on purpose: "Let's simulate what happens if this component mounts, unmounts, and mounts again. Does the app still work correctly?"</p><p>If your effect breaks when run twice, that's a signal that you're missing a cleanup function.</p><pre><code class="language-jsx">// This will cause issues if run twice
useEffect(() =&gt; {
  fetchUserData(); // called twice in dev - not ideal but usually fine
}, []);

// This will definitely cause issues if run twice
useEffect(() =&gt; {
  const socket = connectToWebSocket();
  // No cleanup - two connections open in dev
}, []);

// This is correct
useEffect(() =&gt; {
  const socket = connectToWebSocket();
  return () =&gt; socket.disconnect(); // cleaned up properly
}, []);
</code></pre><p>This double-invocation only happens in development. In production, effects run exactly once when the dependency array is empty. So if your app behaves differently in prod vs dev, check your cleanup functions.</p><hr><h2>The Mistakes Developers Actually Make</h2><h3>1. Fetching Data Without Handling Cleanup</h3><p>This is probably the most common one:</p><pre><code class="language-jsx">// Problematic version
useEffect(() =&gt; {
  fetch(`/api/users/${userId}`)
    .then(res =&gt; res.json())
    .then(data =&gt; setUser(data)); // can cause "state update on unmounted component" warning
}, [userId]);
</code></pre><p>If the user navigates away before the fetch completes, the component unmounts. But the fetch is still running and when it finishes, it tries to call <code>setUser</code> on a component that no longer exists.</p><p>The fix is to use an abort controller:</p><pre><code class="language-jsx">useEffect(() =&gt; {
  const controller = new AbortController();

  fetch(`/api/users/${userId}`, { signal: controller.signal })
    .then(res =&gt; res.json())
    .then(data =&gt; setUser(data))
    .catch(err =&gt; {
      if (err.name === "AbortError") return; // ignore aborted requests
      console.error(err);
    });

  return () =&gt; controller.abort();
}, [userId]);
</code></pre><p>Now when the component unmounts, the fetch is cancelled cleanly.</p><h3>2. Missing Dependencies</h3><p>React has an ESLint rule called <code>exhaustive-deps</code> that warns you when you're using a value inside <code>useEffect</code> but not including it in the dependency array. A lot of developers ignore or suppress this warning - which often causes subtle bugs.</p><pre><code class="language-jsx">// Bug waiting to happen
useEffect(() =&gt; {
  document.title = `Hello, ${userName}`; // uses userName
}, []); // but userName is not in the dependency array

// If userName changes, the title never updates
</code></pre><pre><code class="language-jsx">// Correct
useEffect(() =&gt; {
  document.title = `Hello, ${userName}`;
}, [userName]); // now it updates whenever userName changes
</code></pre><p>Don't ignore the exhaustive-deps warning. It exists for a reason.</p><h3>3. Putting Everything in useEffect</h3><p>This one is more of a design problem. A lot of developers reach for <code>useEffect</code> as the default place to "do stuff" - but a lot of things that end up in <code>useEffect</code> don't need to be there at all.</p><p>Transforming data for display? Do it during render, not in an effect.</p><pre><code class="language-jsx">// Unnecessary effect
const [fullName, setFullName] = useState("");

useEffect(() =&gt; {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

// Just do this instead
const fullName = `${firstName} ${lastName}`;
</code></pre><p>Calculating derived state from existing state is not a side effect. Effects are for interacting with things outside React - APIs, the DOM, browser APIs, subscriptions.</p><h3>4. Infinite Loops</h3><p>This one causes the most panic:</p><pre><code class="language-jsx">// Infinite loop
const [count, setCount] = useState(0);

useEffect(() =&gt; {
  setCount(count + 1); // triggers re-render, effect runs again, triggers re-render...
}, [count]);
</code></pre><p>Every time <code>count</code> changes the effect runs, which changes <code>count</code>, which runs the effect again. If you ever see your app freezing or a "too many re-renders" error, check if you're updating state inside an effect that depends on that same state.</p><hr><h2>When You Should Not Use useEffect</h2><p>The React team themselves have said this clearly in their documentation: a lot of <code>useEffect</code> usage is unnecessary and makes code harder to understand.</p><p>Here are cases where you should skip <code>useEffect</code>:</p><p><strong>Transforming data for rendering</strong> - Just compute it directly in the component body.</p><p><strong>Handling user events</strong> - Use event handlers, not effects. If you're writing <code>useEffect</code> that reacts to a button click, something is off.</p><p><strong>Initializing app-level state</strong> - If you need something to run once when the app loads, put it outside the component entirely, not inside an effect.</p><p>The honest rule is: if you find yourself writing <code>useEffect</code> and thinking "this feels complicated", stop and ask whether it actually needs to be an effect at all.</p><hr><h2>A Clean useEffect Looks Like This</h2><p>When you do need <code>useEffect</code>, here's what a clean, correct implementation looks like:</p><pre><code class="language-jsx">function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() =&gt; {
    const controller = new AbortController();
    setLoading(true);

    fetch(`/api/users/${userId}`, { signal: controller.signal })
      .then(res =&gt; res.json())
      .then(data =&gt; {
        setUser(data);
        setLoading(false);
      })
      .catch(err =&gt; {
        if (err.name === "AbortError") return;
        setLoading(false);
      });

    return () =&gt; controller.abort();
  }, [userId]);

  if (loading) return &lt;p&gt;Loading...&lt;/p&gt;;
  if (!user) return &lt;p&gt;User not found&lt;/p&gt;;

  return &lt;h1&gt;{user.name}&lt;/h1&gt;;
}
</code></pre><p>Clear dependency array. Cleanup function. Error handling. Aborts stale requests. This is what using <code>useEffect</code> correctly actually looks like.</p><hr><h2>The Mental Model Worth Keeping</h2><p>After all of this, here's the simplest way to think about <code>useEffect</code>:</p><ul><li><p>It runs <strong>after</strong> the render, not during</p></li><li><p>The dependency array controls <strong>when</strong> it runs again</p></li><li><p>Always ask: does this need to be cleaned up?</p></li><li><p>If you don't need to interact with something outside React, you probably don't need <code>useEffect</code> at all</p></li></ul><p>Once these four things are second nature, you'll write fewer bugs, your components will be easier to reason about, and you'll stop reaching for <code>useEffect</code> as the solution to every problem.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/how-react-useeffect-actually-works">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>react</category>
      <category>useeffect</category>
      <category>react-hooks</category>
      <category>javascript</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1781054019/uifindr/components/ffgeuyhc1ignzzfz2fzq.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>SSR, CSR, SSG, and ISR in Next.js - What They Are and When to Use Each One</title>
      <link>https://www.codekk.dev/blogs/ssr-csr-ssg-isr-nextjs</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/ssr-csr-ssg-isr-nextjs</guid>
      <pubDate>Thu, 02 Jul 2026 02:58:31 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Next.js gives you four ways to render pages and each solves a different problem. This guide explains all four with real code examples and a practical decision guide.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1780807175/uifindr/components/xlvjko1djwbudyv8woi0.jpg" alt="SSR, CSR, SSG, and ISR in Next.js - What They Are and When to Use Each One" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/ssr-csr-ssg-isr-nextjs">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>nextjs</category>
      <category>rendering</category>
      <category>client-side-generation</category>
      <category>server-side-generation</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1780807175/uifindr/components/xlvjko1djwbudyv8woi0.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>HTTP Status Codes Explained - What They Actually Mean and Why Developers Should Know Them</title>
      <link>https://www.codekk.dev/blogs/http-status-codes-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/http-status-codes-explained</guid>
      <pubDate>Thu, 02 Jul 2026 02:58:31 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Most developers know 200 and 404. But understanding the full range - and when to use them in your own APIs - makes you a noticeably better backend developer.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1780629080/uifindr/components/u8h25ipw90iswbf0f1g9.jpg" alt="HTTP Status Codes Explained - What They Actually Mean and Why Developers Should Know Them" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/http-status-codes-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>status-codes</category>
      <category>http</category>
      <category>api</category>
      <category>backend</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1780629080/uifindr/components/u8h25ipw90iswbf0f1g9.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>TypeScript vs JavaScript - What Is Actually Different and Should You Learn TypeScript</title>
      <link>https://www.codekk.dev/blogs/typescript-vs-javascript</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/typescript-vs-javascript</guid>
      <pubDate>Thu, 02 Jul 2026 02:58:29 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[TypeScript and JavaScript are more related than most people think. This guide covers the real difference, core TypeScript features you need to know, and helps you decide whether to make the switch.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1781144020/uifindr/components/dtwtyx1pplgdgn0zlgyh.jpg" alt="TypeScript vs JavaScript - What Is Actually Different and Should You Learn TypeScript" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/typescript-vs-javascript">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>typescript</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>ts-vs-js</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1781144020/uifindr/components/dtwtyx1pplgdgn0zlgyh.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>Node.js and Express.js - What They Are, How They Differ, and How They Work Together</title>
      <link>https://www.codekk.dev/blogs/nodejs-and-expressjs-explained</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/nodejs-and-expressjs-explained</guid>
      <pubDate>Thu, 02 Jul 2026 02:58:29 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[Node.js and Express.js are not the same thing. This guide explains what each does, how they work together, and covers routing, middleware, and error handling with real code.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1781144516/uifindr/components/oeueevyklnfqjthz6lmm.jpg" alt="Node.js and Express.js - What They Are, How They Differ, and How They Work Together" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/nodejs-and-expressjs-explained">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>backend</category>
      <category>javascript</category>
      <category>nodejs</category>
      <category>expressjs</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1781144516/uifindr/components/oeueevyklnfqjthz6lmm.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>What is CORS and How to Fix It in Express - Properly Explained</title>
      <link>https://www.codekk.dev/blogs/what-is-cors-and-how-to-fix-it-in-express</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/what-is-cors-and-how-to-fix-it-in-express</guid>
      <pubDate>Thu, 02 Jul 2026 02:58:29 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[CORS errors are one of the most searched JavaScript errors. This blog explains what CORS actually is, why browsers enforce it, what preflight requests are, and how to configure it correctly in Express.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1782959337/uifindr/components/ihzrincz15nzwljegiju.jpg" alt="What is CORS and How to Fix It in Express - Properly Explained" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>If you've built a frontend app that talks to a backend API, you've probably seen this error at some point:</p><pre><code>Access to fetch at 'http://localhost:3000/api/users' from origin 
'http://localhost:5173' has been blocked by CORS policy: No 
'Access-Control-Allow-Origin' header is present on the requested resource.
</code></pre><p>Most developers at this point Google "how to fix CORS", find a Stack Overflow answer, copy a few lines of code, and move on. The error goes away and they never really understand what just happened or why.</p><p>That's a problem - because CORS errors show up again in production in ways that are harder to debug, and if you don't understand the mechanism, you end up either breaking things trying to fix them or making your API insecure without realizing it.</p><p>This blog explains what CORS actually is, why browsers enforce it, what happens under the hood when a request gets blocked, and how to handle it correctly in an Express API.</p><hr><h2>Start Here - What is an Origin</h2><p>Before understanding CORS you need to understand what an "origin" is, because that's the core concept everything else builds on.</p><p>An origin is the combination of three things: the <strong>protocol</strong>, the <strong>domain</strong>, and the <strong>port</strong>.</p><pre><code>http://localhost:5173

protocol: http
domain:   localhost
port:     5173
</code></pre><p>Two URLs have the same origin only if all three of these match exactly. If any one of them is different, they are considered different origins.</p><pre><code>http://localhost:5173   and   http://localhost:3000   -&gt; different origins (different port)
http://localhost:5173   and   https://localhost:5173  -&gt; different origins (different protocol)
http://localhost:5173   and   http://codekk.dev       -&gt; different origins (different domain)
http://localhost:5173   and   http://localhost:5173   -&gt; same origin
</code></pre><p>This distinction matters because of something called the Same-Origin Policy.</p><hr><h2>The Same-Origin Policy - Why This Exists at All</h2><p>Browsers have a built-in security rule called the <strong>Same-Origin Policy</strong>. It says that JavaScript running on one origin cannot read responses from a different origin by default.</p><p>This is not a bug or an accident. It is a deliberate security feature and understanding why it exists makes everything else make sense.</p><p>Imagine you are logged into your bank at <code>https://mybank.com</code>. Your browser has a session cookie stored for that bank. Now imagine you visit a malicious website at <code>https://evil.com</code>. That site has JavaScript that silently makes a request to <code>https://mybank.com/api/transfer-money</code> using your cookies - because your browser automatically sends cookies for any domain they belong to.</p><p>Without the Same-Origin Policy, that request would succeed and the bank's API would process it as if you made it yourself, because your cookies are attached. The malicious site's JavaScript could then read the response and steal your data.</p><p>The Same-Origin Policy prevents this. It blocks <code>https://evil.com</code>'s JavaScript from reading responses from <code>https://mybank.com</code>. The request might go through, but the browser refuses to give the response to the JavaScript that made it if the origins don't match.</p><p>So CORS errors are not the browser being annoying. The browser is actively protecting users from a real class of attacks.</p><hr><h2>So What is CORS Then</h2><p><strong>CORS stands for Cross-Origin Resource Sharing.</strong> It is a mechanism that lets servers tell browsers "it's okay, this other origin is allowed to read my responses."</p><p>It works through HTTP headers. When a browser makes a cross-origin request, the server can include special headers in its response that say which origins, methods, and headers are permitted. If those headers are present and allow the requesting origin, the browser lets the JavaScript read the response. If they're not present or don't allow the requesting origin, the browser blocks it.</p><p>CORS is not a restriction added by the server. It's a relaxation of the browser's default restriction, controlled by the server.</p><p>That's an important distinction. The server decides who gets to talk to it across origins. The browser enforces that decision.</p><hr><h2>Why Your Localhost Setup Triggers CORS</h2><p>This is the most common scenario. You have a React or Vite frontend running on <code>http://localhost:5173</code> - if you read our blog on <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/dark-light-mode-react-vite-tailwind-typescript">dark and light mode in React Vite</a>, you know Vite runs on port 5173 by default.</p><p>Your Express backend is running on <code>http://localhost:3000</code> - as we set up in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express blog</a>.</p><p>Even though both are on localhost, they're on different ports. Different ports mean different origins. So when your frontend tries to call your backend API, the browser sees a cross-origin request and checks if the server allows it. If your Express server doesn't send back CORS headers, the browser blocks the response.</p><p>The request actually reaches your server. Your server processes it and sends a response. But the browser intercepts that response and refuses to give it to your JavaScript because the CORS headers are missing. This is why you'll sometimes see the request in your server logs even when the browser shows a CORS error.</p><hr><h2>Simple Requests vs Preflight Requests</h2><p>Not all cross-origin requests work the same way. There are two types you need to know about.</p><p><strong>Simple Requests</strong></p><p>Some requests are considered "simple" and the browser just makes them directly. A simple request is a GET or POST with basic content types like <code>text/plain</code> or <code>application/x-www-form-urlencoded</code>, with no custom headers.</p><p>The browser makes the request, the server responds, and the browser checks the CORS headers. If they're missing or wrong, the browser blocks the JavaScript from reading the response.</p><p><strong>Preflight Requests</strong></p><p>For anything more complex - like a POST with <code>Content-Type: application/json</code>, a PUT, a DELETE, or a request with custom headers like <code>Authorization</code> - the browser first sends a preliminary request using the HTTP OPTIONS method. This is called a <strong>preflight request</strong>.</p><p>The browser is essentially asking the server: "Before I send this actual request, is this kind of request allowed from this origin?"</p><pre><code>OPTIONS /api/users HTTP/1.1
Origin: http://localhost:5173
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
</code></pre><p>The server needs to respond to this preflight with the appropriate CORS headers saying what's allowed. Only if the preflight response says "yes, that's allowed" will the browser then send the actual request.</p><p>This is why sometimes a single API call in your browser's network tab shows two requests - an OPTIONS request followed by the actual one. That's the preflight happening.</p><p>If your server doesn't handle the OPTIONS method, the preflight fails, and your actual request never goes through. This trips up a lot of developers who handle <code>GET</code> and <code>POST</code> in their Express routes but forget about <code>OPTIONS</code>.</p><hr><h2>What the CORS Headers Actually Mean</h2><p>Let's look at the headers involved so you know what you're actually configuring.</p><p><strong>Access-Control-Allow-Origin</strong></p><p>This is the most important one. It tells the browser which origin is allowed to read responses.</p><pre><code>Access-Control-Allow-Origin: http://localhost:5173
</code></pre><p>This allows only your frontend origin. Or you can use a wildcard:</p><pre><code>Access-Control-Allow-Origin: *
</code></pre><p>This allows any origin. Convenient for public APIs, but you should never use this for APIs that handle authenticated requests or sensitive data - because the wildcard cannot be combined with <code>Access-Control-Allow-Credentials: true</code>.</p><p><strong>Access-Control-Allow-Methods</strong></p><p>Tells the browser which HTTP methods are permitted.</p><pre><code>Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
</code></pre><p><strong>Access-Control-Allow-Headers</strong></p><p>Tells the browser which request headers are permitted.</p><pre><code>Access-Control-Allow-Headers: Content-Type, Authorization
</code></pre><p>If you're sending a JSON body (<code>Content-Type: application/json</code>) or an auth token (<code>Authorization: Bearer ...</code>), these headers need to be listed here.</p><p><strong>Access-Control-Allow-Credentials</strong></p><p>This one is specifically for requests that include cookies or HTTP authentication. If you set this to <code>true</code>, the browser will include cookies in cross-origin requests and allow the JavaScript to read responses to those requests.</p><pre><code>Access-Control-Allow-Credentials: true
</code></pre><p>Important: when <code>Access-Control-Allow-Credentials</code> is <code>true</code>, you cannot use <code>*</code> for <code>Access-Control-Allow-Origin</code>. You must specify the exact origin. This is a browser security rule that cannot be bypassed.</p><p><strong>Access-Control-Max-Age</strong></p><p>Tells the browser how long it can cache the preflight response, in seconds. This avoids sending an OPTIONS request before every single API call.</p><pre><code>Access-Control-Max-Age: 86400
</code></pre><hr><h2>Fixing CORS in Express - The Right Way</h2><p>Now that you understand what's happening, let's fix it properly. If you've set up an Express server following our <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express blog</a>, this is where you add CORS support.</p><p>The standard approach is to use the <code>cors</code> npm package, which handles all the header setting for you.</p><pre><code>npm install cors
</code></pre><p><strong>Basic setup - allow all origins:</strong></p><pre><code class="language-js">const express = require("express");
const cors = require("cors");

const app = express();

app.use(cors());

app.get("/api/users", (req, res) =&gt; {
  res.json({ users: [] });
});

app.listen(3000, () =&gt; console.log("Server running on port 3000"));
</code></pre><p><code>app.use(cors())</code> with no configuration allows all origins. This is fine for a public API that has no authentication and serves non-sensitive data. Do not use this for authenticated APIs.</p><p><strong>Specific origin - the correct approach for most apps:</strong></p><pre><code class="language-js">const corsOptions = {
  origin: "http://localhost:5173",
  methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 86400
};

app.use(cors(corsOptions));
</code></pre><p>This locks CORS down to exactly your frontend origin, permits the methods and headers you actually need, and allows credentials if you're using cookies or auth tokens.</p><p><strong>Multiple allowed origins:</strong></p><p>In real projects you often have more than one frontend - a local development URL, a staging URL, and a production URL. Here's how to handle that:</p><pre><code class="language-js">const allowedOrigins = [
  "http://localhost:5173",
  "https://staging.codekk.dev",
  "https://codekk.dev"
];

const corsOptions = {
  origin: function (origin, callback) {
    // allow requests with no origin (like mobile apps or Postman)
    if (!origin) return callback(null, true);

    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error(`CORS policy: origin ${origin} is not allowed`));
    }
  },
  methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true
};

app.use(cors(corsOptions));
</code></pre><p>The <code>origin</code> option here takes a function instead of a string. The function receives the incoming origin and a callback. If the origin is in the allowed list, you call <code>callback(null, true)</code> to allow it. If not, you call <code>callback(new Error(...))</code> to reject it.</p><p>The <code>!origin</code> check handles tools like Postman, curl, or mobile app clients that don't send an <code>Origin</code> header. Without this, those requests would also get blocked.</p><p><strong>Handling preflight requests explicitly:</strong></p><p>The <code>cors()</code> middleware handles OPTIONS requests automatically when you use <code>app.use(cors())</code>. But if you're applying CORS to specific routes only and have complex routing, you might need to handle OPTIONS explicitly:</p><pre><code class="language-js">app.options("*", cors(corsOptions)); // handle preflight for all routes
app.use(cors(corsOptions));
</code></pre><p>The <code>app.options("*", cors())</code> line tells Express to respond to OPTIONS requests on any route using the CORS middleware. This makes sure preflight requests get proper responses.</p><hr><h2>Doing It Without the cors Package</h2><p>It's worth knowing how to set these headers manually, so you understand what the package is doing under the hood. This is also useful for situations where you're writing middleware yourself, similar to what we covered in the <a target="_blank" rel="noopener noreferrer nofollow" class="text-[#0068a3] underline hover:text-[#005a8f]" href="https://www.codekk.dev/blog/nodejs-and-expressjs-explained">Node.js and Express blog</a>.</p><pre><code class="language-js">app.use((req, res, next) =&gt; {
  res.setHeader("Access-Control-Allow-Origin", "http://localhost:5173");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  res.setHeader("Access-Control-Allow-Credentials", "true");

  // handle preflight
  if (req.method === "OPTIONS") {
    return res.status(200).end();
  }

  next();
});
</code></pre><p>This does exactly what the <code>cors</code> package does - sets the headers on every response and returns a 200 for OPTIONS preflight requests. Using the package is cleaner and handles edge cases for you, but knowing this manual version means you're never confused about what CORS middleware is actually doing.</p><hr><h2>Common Mistakes That Keep the Error Coming Back</h2><p><strong>Putting CORS after your routes</strong></p><p>Middleware order matters in Express. If you register your routes before <code>app.use(cors())</code>, the CORS headers won't be on those responses. Always put <code>app.use(cors())</code> before your route definitions.</p><pre><code class="language-js">// Wrong
app.get("/api/users", handler);
app.use(cors()); // too late

// Correct
app.use(cors()); // before routes
app.get("/api/users", handler);
</code></pre><p><strong>Using wildcard with credentials</strong></p><pre><code class="language-js">// This will not work - browser will throw an error
app.use(cors({
  origin: "*",
  credentials: true
}));
</code></pre><p>When credentials are involved, the browser requires a specific origin, not a wildcard. This is a hard browser rule. Specify the exact origin instead.</p><p><strong>Forgetting to allow the Authorization header</strong></p><p>If you're sending a JWT token in your request headers like <code>Authorization: Bearer &lt;token&gt;</code>, and you haven't listed <code>Authorization</code> in <code>allowedHeaders</code>, the preflight will fail and your request won't go through.</p><p><strong>Only fixing the frontend</strong></p><p>CORS is a server-side fix. You cannot solve CORS errors by changing your frontend code. The headers have to come from the server. If you see someone suggesting you add a proxy or change fetch options to fix CORS, that's a workaround for development only - not a real fix for production.</p><hr><h2>CORS in Production is Different From Development</h2><p>In development, your frontend and backend are both on localhost with different ports. In production, they might be on completely different domains - your frontend on <code>https://codekk.dev</code> and your API on <code>https://api.codekk.dev</code>.</p><p>Make sure your production Express server's allowed origins list includes your actual production frontend URL, not just localhost. A common mistake is fixing CORS for development and then forgetting to update the configuration for production, which leads to a working local app and a broken production app.</p><p>Using environment variables for your allowed origins is the clean way to handle this:</p><pre><code class="language-js">const allowedOrigins = process.env.ALLOWED_ORIGINS
  ? process.env.ALLOWED_ORIGINS.split(",")
  : ["http://localhost:5173"];

const corsOptions = {
  origin: function (origin, callback) {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
  credentials: true
};
</code></pre><p>Set <code>ALLOWED_ORIGINS=https://codekk.dev</code> in your production environment and <code>http://localhost:5173</code> locally. No code changes needed between environments.</p><hr><h2>The One Line Summary</h2><p>CORS errors happen because your browser's Same-Origin Policy blocked a cross-origin response. You fix them by having your server send the right headers telling the browser which origins, methods, and headers are allowed. The <code>cors</code> package in Express handles this cleanly, but knowing the manual version means you understand what's actually happening and can debug it confidently when things go wrong.</p><p>Next time you see that red CORS error in the console, you'll know exactly what caused it, exactly what the browser is protecting you from, and exactly what to change on the server to fix it properly.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/what-is-cors-and-how-to-fix-it-in-express">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>backend</category>
      <category>nodejs</category>
      <category>expressjs</category>
      <category>cors</category>
      <category>rest-api</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1782959337/uifindr/components/ihzrincz15nzwljegiju.jpg" type="image/jpeg" />
    </item>

    <item>
      <title>How to Build Dark and Light Mode in React Vite with Tailwind CSS and TypeScript (Step by Step)</title>
      <link>https://www.codekk.dev/blogs/dark-light-mode-react-vite-tailwind-typescript</link>
      <guid isPermaLink="true">https://www.codekk.dev/blogs/dark-light-mode-react-vite-tailwind-typescript</guid>
      <pubDate>Thu, 02 Jul 2026 02:58:29 GMT</pubDate>
      <author>ssadie0416@gmail.com (Sadie Sink)</author>
      <description><![CDATA[A complete step by step guide to building dark and light mode in React Vite using Tailwind CSS v4 and TypeScript, explaining the Context API, localStorage persistence, and why each part works the way it does.]]></description>
      <content:encoded><![CDATA[
        <img src="https://res.cloudinary.com/dyswu0gin/image/upload/v1781664521/uifindr/components/od7jj65eoht4jhvgdvku.jpg" alt="How to Build Dark and Light Mode in React Vite with Tailwind CSS and TypeScript (Step by Step)" style="max-width: 100%; height: auto; margin-bottom: 20px;" />
        <p>I searched a lot before writing this. Almost every tutorial out there either skips the reasoning and just hands you a code dump, or explains the theory without showing you a working result. You end up copying code that works, but you still don't actually understand why it works.</p><p>So this blog is built differently. Every step explains what we're doing and why we're doing it that way, not just what to type. By the end, you won't just have a dark mode toggle - you'll understand exactly how it works under the hood, and you'll be able to build it again from memory without copying anything.</p><p>Let's go.</p><hr><h2>What We're Actually Building</h2><p>A simple product card component that switches between light and dark themes, with a toggle button that remembers your choice even after you refresh the page. We'll use React with Vite, TypeScript, and Tailwind CSS v4.</p><p>Before writing a single line of code, it helps to understand what "dark mode" actually means technically. It's not magic - it's just CSS classes changing based on a condition, and that condition being remembered somewhere. That's the entire concept. Everything else is just implementation detail.</p><hr><h2>Step 1: Setting Up the Project</h2><p>Start by creating a new Vite project. We're using TypeScript here, but if you prefer plain JavaScript, the same logic applies - just remove the type annotations.</p><pre><code>npm create vite@latest
</code></pre><p>Choose React as the framework and TypeScript as the variant. Once it's created, move into the folder and install dependencies as usual.</p><p><strong>Why Vite instead of Create React App?</strong> Vite is significantly faster during development because it doesn't bundle your entire app every time you save a file. It uses native ES modules in the browser during development, so changes show up almost instantly. For a small project like this, you'll notice the speed difference immediately.</p><hr><h2>Step 2: Installing Tailwind CSS</h2><p>This part trips a lot of people up because Tailwind changed how it integrates with Vite in version 4. If you've used Tailwind before through <code>tailwind.config.js</code> and PostCSS, this is different.</p><pre><code>npm install tailwindcss @tailwindcss/vite
</code></pre><p>This installs Tailwind itself, along with a dedicated Vite plugin built specifically for Tailwind v4. This plugin handles the CSS processing directly inside Vite's build pipeline instead of going through a separate PostCSS configuration step like before.</p><p><strong>Why does this matter?</strong> In older Tailwind setups, you needed a <code>tailwind.config.js</code> file, a <code>postcss.config.js</code> file, and content paths configured manually so Tailwind knew which files to scan for class names. With the Vite plugin approach in v4, a lot of that configuration overhead goes away. Vite and Tailwind talk to each other directly.</p><hr><h2>Step 3: Connecting Tailwind to Vite</h2><p>Open <code>vite.config.ts</code> and add the Tailwind plugin alongside the React plugin.</p><pre><code class="language-ts">import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite"; // add this line

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    tailwindcss(), // this too
    react()
  ], 
});
</code></pre><p><strong>Why does the order or presence of these plugins matter?</strong> Vite plugins process your code in a pipeline. The Tailwind plugin needs to be registered so Vite knows to process Tailwind's CSS directives during build and development. Without this, Tailwind classes in your components simply won't generate any actual CSS - your <code>className="text-lg"</code> would just be a string with no styling behind it.</p><hr><h2>Step 4: Importing Tailwind in Your CSS</h2><p>Open <code>src/index.css</code>, delete whatever default styles Vite generated, and replace it with just this one line:</p><pre><code class="language-css">@import "tailwindcss";
</code></pre><p>That's it. In older Tailwind versions you'd write three separate <code>@tailwind</code> directives for base, components, and utilities. Version 4 simplified this into a single import.</p><p><strong>Why does this single line matter so much?</strong> This import is what actually pulls in Tailwind's entire utility class system into your project. Without it, none of your <code>bg-white</code>, <code>dark:bg-gray-800</code>, or any other Tailwind class will do anything - they'll just be unrecognized strings sitting in your JSX.</p><p>Run your dev server now to confirm everything is wired correctly:</p><pre><code>npm run dev
</code></pre><p>If Tailwind classes are working in a quick test component, you're ready to move to the actual dark mode logic.</p><hr><h2>Step 5: Understanding How Dark Mode Actually Works in Tailwind</h2><p>Before writing the toggle logic, you need to understand the mechanism Tailwind uses for dark mode. This is the part most tutorials skip past too quickly.</p><p>Tailwind's dark mode works through a special class prefix: <code>dark:</code>. When you write something like:</p><pre><code class="language-jsx">&lt;div className="bg-white dark:bg-gray-800"&gt;
</code></pre><p>This tells Tailwind: "use <code>bg-white</code> normally, but if dark mode is active, use <code>bg-gray-800</code> instead."</p><p>But here's the important part - <strong>how does Tailwind know if dark mode is active?</strong> By default, Tailwind checks if the <code>&lt;html&gt;</code> element has a class called <code>dark</code> on it. If that class exists, every <code>dark:</code> variant in your app activates. If it doesn't exist, your app shows the light mode styles.</p><p>So the entire dark mode system comes down to one simple question: <strong>is there a class called </strong><code>dark</code><strong> on the html tag or not?</strong></p><p>That's the whole mechanism. Everything we build from here is just about adding and removing that one class, and remembering the user's choice.</p><hr><h2>Step 6: Building the Theme Context</h2><p>This is the core logic of the whole tutorial. We need a way to:</p><ol><li><p>Store the current theme (light or dark) somewhere accessible to the whole app</p></li><li><p>Provide a function to toggle between them</p></li><li><p>Apply the actual <code>dark</code> class to the html element when the theme changes</p></li><li><p>Remember the user's choice even after they close and reopen the browser</p></li></ol><p><strong>Why use Context API instead of just useState in the App component?</strong> If you only used <code>useState</code> in your top-level <code>App</code> component, you'd have to pass <code>theme</code> and <code>toggleTheme</code> down as props to every component that needs them. If your <code>ToggleTheme</code> button is three components deep, you'd be passing props through every layer in between just to reach it. This is called prop drilling, and it gets messy fast.</p><p>Context API solves this by letting any component in your app access the theme value directly, no matter how deeply nested it is, without passing props through every layer.</p><p>Create a file at <code>src/context/ThemeContext.tsx</code>:</p><pre><code class="language-tsx">import { createContext, useContext, useEffect, useState } from "react";

type Theme = "light" | "dark";

type ThemeContextType = {
  theme: Theme;
  toggleTheme: () =&gt; void;
};

const ThemeContext = createContext&lt;ThemeContextType | null&gt;(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState&lt;Theme&gt;(() =&gt; {
    const storedTheme = localStorage.getItem("theme");

    return storedTheme === "dark" ? "dark" : "light";
  });

  useEffect(() =&gt; {
    const html = document.documentElement;

    if (theme === "dark") {
      html.classList.add("dark");
    } else {
      html.classList.remove("dark");
    }

    localStorage.setItem("theme", theme);
  }, [theme]);

  const toggleTheme = () =&gt; {
    setTheme((prev) =&gt; (prev === "dark" ? "light" : "dark"));
  };

  return (
    &lt;ThemeContext.Provider
      value={{
        theme,
        toggleTheme,
      }}
    &gt;
      {children}
    &lt;/ThemeContext.Provider&gt;
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);

  if (!context) {
    throw new Error("useTheme must be used inside ThemeProvider");
  }

  return context;
}
</code></pre><p>Let's go through this piece by piece because every part here is doing something specific.</p><p><strong>The type definitions at the top:</strong></p><pre><code class="language-ts">type Theme = "light" | "dark";
</code></pre><p>This is a TypeScript union type. It says theme can only ever be exactly one of these two string values, nothing else. If you accidentally typed <code>"Dark"</code> with a capital D somewhere later in your code, TypeScript would catch that mistake immediately because it doesn't match either allowed value.</p><p><strong>The initial state with a function:</strong></p><pre><code class="language-tsx">const [theme, setTheme] = useState&lt;Theme&gt;(() =&gt; {
  const storedTheme = localStorage.getItem("theme");
  return storedTheme === "dark" ? "dark" : "light";
});
</code></pre><p>Notice that we're passing a function into <code>useState</code>, not a direct value. This matters. If we wrote <code>useState(localStorage.getItem("theme"))</code> directly, that <code>localStorage.getItem</code> call would run on every single re-render of this component, even though we only need it once when the component first mounts. By wrapping it in a function, React only runs this logic once, during the initial render. This is called lazy initial state, and it's a small but meaningful performance habit.</p><p>The logic itself checks if anything was previously saved in <code>localStorage</code>. If the user had picked dark mode before, that string <code>"dark"</code> is still sitting there from their last visit, and we restore it. If nothing is saved, we default to light mode.</p><p><strong>The useEffect that does the actual work:</strong></p><pre><code class="language-tsx">useEffect(() =&gt; {
  const html = document.documentElement;

  if (theme === "dark") {
    html.classList.add("dark");
  } else {
    html.classList.remove("dark");
  }

  localStorage.setItem("theme", theme);
}, [theme]);
</code></pre><p>This effect runs every time <code>theme</code> changes. Remember what we learned in step 5 - Tailwind's entire dark mode system depends on whether the html element has a class called <code>dark</code>. This is exactly what's happening here. <code>document.documentElement</code> refers to the actual <code>&lt;html&gt;</code> tag in your page. When theme becomes <code>"dark"</code>, we add the <code>dark</code> class to it. When it becomes <code>"light"</code>, we remove that class.</p><p>The moment that class gets added or removed, every single <code>dark:</code> variant across your entire app instantly responds, because Tailwind is constantly watching for that class to exist or not exist on the html tag.</p><p>The last line, <code>localStorage.setItem("theme", theme)</code>, saves the current choice to the browser's local storage. This is what makes the theme persist - so if the user refreshes the page or comes back tomorrow, their preference is still there waiting to be read back in step one.</p><p><strong>The toggle function:</strong></p><pre><code class="language-tsx">const toggleTheme = () =&gt; {
  setTheme((prev) =&gt; (prev === "dark" ? "light" : "dark"));
};
</code></pre><p>This simply flips the current value. If it was dark, make it light. If it was light, make it dark. We're using the functional form of <code>setTheme</code> here (passing a function instead of a direct value) because it guarantees we're always working with the most current state value, rather than a potentially stale one from a previous render.</p><p><strong>The custom hook at the bottom:</strong></p><pre><code class="language-tsx">export function useTheme() {
  const context = useContext(ThemeContext);

  if (!context) {
    throw new Error("useTheme must be used inside ThemeProvider");
  }

  return context;
}
</code></pre><p>This wraps <code>useContext</code> in a custom hook. Why bother with this extra step instead of just calling <code>useContext(ThemeContext)</code> directly wherever needed? Two reasons. First, it's shorter to write <code>useTheme()</code> everywhere instead of repeating <code>useContext(ThemeContext)</code>. Second, and more importantly, it adds a safety check. If somehow a component tries to use this hook outside of where <code>ThemeProvider</code> actually wraps the app, it throws a clear error immediately telling you exactly what went wrong, instead of silently failing or giving you a confusing undefined error somewhere else.</p><hr><h2>Step 7: Wrapping the App With the Provider</h2><p>Context only works for components that are nested inside the Provider component. So we need to wrap our whole app with <code>ThemeProvider</code>. Open <code>src/main.tsx</code> and wrap your <code>App</code> component:</p><pre><code class="language-tsx">import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { ThemeProvider } from "./context/ThemeContext.tsx";
import "./index.css";

createRoot(document.getElementById("root")!).render(
  &lt;StrictMode&gt;
    &lt;ThemeProvider&gt;
      &lt;App /&gt;
    &lt;/ThemeProvider&gt;
  &lt;/StrictMode&gt;,
);
</code></pre><p><strong>Why wrap it here at the very top level?</strong> Because now every single component anywhere inside <code>App</code> - no matter how deeply nested - can call <code>useTheme()</code> and get access to the current theme and the toggle function. This is the entire point of using Context in the first place.</p><hr><h2>Step 8: Building the Toggle Button</h2><p>Now we build the actual UI piece the user interacts with. Create <code>src/components/ui/ToggleTheme.tsx</code>:</p><pre><code class="language-tsx">import { Moon, SunDim } from "lucide-react";
import { useTheme } from "../../context/ThemeContext";
import { motion, AnimatePresence } from "framer-motion";

export default function ToggleTheme() {
  const { theme, toggleTheme } = useTheme();

  return (
    &lt;button
      onClick={toggleTheme}
      className={`
        flex items-center gap-2 px-4 py-2 rounded-lg border cursor-pointer
        transition-colors duration-200
        ${
          theme === "dark"
            ? "bg-white text-black border-gray-200"
            : "bg-black/80 text-white border-black/90"
        }
      `}
    &gt;
      &lt;AnimatePresence mode="wait" initial={false}&gt;
        &lt;motion.div
          key={theme}
          className="flex items-center gap-2"
          initial={{ opacity: 0, scale: 0.9 }}
          animate={{ opacity: 1, scale: 1 }}
          exit={{ opacity: 0, scale: 0.9 }}
          transition={{ duration: 0.15 }}
        &gt;
          {theme === "dark" ? (
            &lt;SunDim className="w-4 h-4" /&gt;
          ) : (
            &lt;Moon className="w-4 h-4" /&gt;
          )}
          &lt;span className="text-sm font-medium"&gt;
            {theme === "dark" ? "Light Mode" : "Dark Mode"}
          &lt;/span&gt;
        &lt;/motion.div&gt;
      &lt;/AnimatePresence&gt;
    &lt;/button&gt;
  );
}
</code></pre><p><strong>Why is this button's own background flipped opposite to the theme?</strong> Look closely - when <code>theme === "dark"</code>, the button itself becomes white with black text. When the theme is light, the button becomes dark. This is an intentional design choice, not a bug. It makes the button stand out clearly against the page background in both modes, since the page itself goes dark when theme is dark, and the button does the opposite to maintain contrast.</p><p><strong>Why use </strong><code>lucide-react</code><strong> for the icons?</strong> We're showing a sun icon when in dark mode (suggesting "click to switch to light") and a moon icon when in light mode (suggesting "click to switch to dark"). This is a common UX pattern - the icon represents what will happen when clicked, not the current state.</p><p><strong>What is the AnimatePresence and motion.div doing here?</strong> This comes from Framer Motion, an animation library. Without it, the icon and text would just instantly swap when you click, which feels abrupt. With <code>AnimatePresence</code>, when the <code>theme</code> value changes, the <code>key={theme}</code> on the motion div changes too, which tells Framer Motion "this is a different element now, animate the transition." The old icon and text fade and shrink out, the new ones fade and grow in. The <code>mode="wait"</code> ensures the exit animation finishes before the next one starts, so they don't overlap awkwardly.</p><p>This isn't required for dark mode to function - it's a polish detail. If you want to skip Framer Motion entirely, you can remove <code>AnimatePresence</code> and <code>motion.div</code> and just keep the plain conditional icon and text. The toggle will still work exactly the same, just without the smooth transition.</p><hr><h2>Step 9: Building a Component That Actually Uses the Theme</h2><p>A toggle button is pointless without something on the page that visibly changes. Create <code>src/components/card/Card.tsx</code>:</p><pre><code class="language-tsx">export default function Card() {
  return (
    &lt;&gt;
      &lt;div className="max-w-md p-4 bg-white dark:bg-gray-800 shadow-md rounded-lg"&gt;
        &lt;figure className="max-w-sm"&gt;
          &lt;img
            src="https://www.apple.com/assets-www/en_IN/watch/product_tile/large/u3_6b7e98fe2_2x.png"
            alt="Apple Watch Ultra 3"
            className="w-full h-auto object-cover rounded-lg"
          /&gt;
        &lt;/figure&gt;
        &lt;div className="mt-4"&gt;
          &lt;h2 className="text-lg font-bold text-gray-800 dark:text-white"&gt;
            Apple watch ultra 3
          &lt;/h2&gt;
          &lt;p className="text-sm text-gray-600 dark:text-gray-300"&gt;
            Black strap, medium
          &lt;/p&gt;

          &lt;div className="mt-4 flex items-center space-x-4"&gt;
            &lt;p className="text-base font-semibold text-gray-800 dark:text-white"&gt;
              ₹ 85,999
            &lt;/p&gt;
            &lt;p className="text-sm font-semibold text-gray-800 dark:text-white line-through"&gt;
              ₹ 89,999
            &lt;/p&gt;
            &lt;p className="text-xs font-semibold text-green-500"&gt;4% off&lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
}
</code></pre><p><strong>Notice something important here.</strong> This component does not import <code>useTheme</code> at all. It doesn't know or care whether the theme is light or dark. It just has Tailwind classes like <code>bg-white dark:bg-gray-800</code> written directly in the JSX.</p><p>This is the actual payoff of everything we set up earlier. Because the dark mode mechanism works purely through the <code>dark</code> class on the html tag, any component anywhere in your app can simply write <code>dark:</code> variants in its className and it will automatically respond to theme changes - without ever needing to know about Context, state, or any toggle logic at all. The component just declares "here's what I look like in light mode, here's what I look like in dark mode," and the rest is handled globally.</p><p>This is a powerful separation. Your UI components stay simple and focused only on appearance. The theme logic lives in one place - the Context - and everything else just reacts to it through CSS classes.</p><hr><h2>Step 10: Putting It All Together</h2><p>Now bring everything into <code>App.tsx</code>:</p><pre><code class="language-tsx">import "./App.css";
import Card from "./components/card/Card";
import ToggleTheme from "./components/ui/ToggleTheme";

function App() {
  return (
    &lt;main className="flex flex-col items-center justify-center min-h-screen gap-4"&gt;
      &lt;Card /&gt;
      &lt;ToggleTheme /&gt;
    &lt;/main&gt;
  );
}

export default App;
</code></pre><p>At this point, run your dev server and test it:</p><pre><code>npm run dev
</code></pre><p>Click the toggle button. You should see the card background switch between white and dark gray, the text colors invert, and the button itself swap appearance with a smooth animation. Refresh the page after toggling - the theme you picked should still be there. That's the <code>localStorage</code> persistence working correctly.</p><hr><h2>What's Actually Happening When You Click the Button</h2><p>Let's trace through the entire flow one more time, now that every piece is built, so the full picture is clear.</p><p>You click the button. This calls <code>toggleTheme()</code>, which flips the <code>theme</code> state from <code>"light"</code> to <code>"dark"</code> (or vice versa) inside <code>ThemeContext</code>. Because state changed, React re-renders any component using <code>useTheme()</code>.</p><p>At the same time, the <code>useEffect</code> inside <code>ThemeProvider</code> detects that <code>theme</code> changed, and runs its logic - it either adds or removes the <code>dark</code> class on <code>document.documentElement</code>, and saves the new theme value into <code>localStorage</code>.</p><p>The moment that <code>dark</code> class appears or disappears on the html tag, every <code>dark:</code> prefixed Tailwind class across your entire app responds instantly, because that's literally how Tailwind's dark mode variant works under the hood. No component needs to manually re-check anything - it's pure CSS responding to a class change.</p><p>Next time the page loads, the <code>useState</code> initializer reads back whatever was saved in <code>localStorage</code>, and the correct theme is restored before the user even sees anything.</p><p>That's the complete system. Five moving parts, all of them simple on their own, working together.</p><hr><h2>Why This Approach and Not Just CSS Media Queries</h2><p>You might be wondering - CSS already has a <code>prefers-color-scheme</code> media query that detects if the user's operating system is set to dark mode. Why not just use that instead of building all this?</p><p>The honest answer is that media queries only detect the system preference - they don't let the user manually override it inside your app. If someone's OS is set to dark mode but they want your specific website in light mode, a pure CSS media query approach can't give them that choice. The Context and class-based approach we built here gives the user full control, independent of their system settings, and remembers their specific choice for your site.</p><p>If you wanted to be extra thorough, you could combine both - default to the system preference on first visit using the media query, then let the user override it with the toggle, which is exactly what <code>localStorage</code> allows for after that first choice is made.</p><hr><h2>A Quick Note on Folder Structure</h2><p>Notice how the project separates concerns into folders - <code>context</code> for state logic, <code>components/ui</code> for reusable interface pieces like buttons, and <code>components/card</code> for feature-specific pieces. This isn't required for a small project like this one to work, but it's a habit worth building early. As your app grows, having theme logic, UI primitives, and feature components clearly separated makes it much easier to find things and avoid one giant messy file holding everything.</p><hr><h2>The Bottom Line</h2><p>Dark mode isn't complicated once you see what's actually happening underneath. It's one class on the html tag, a place to store and read that choice, and Tailwind's <code>dark:</code> variants reacting to whether that class exists. Everything else - Context API, the toggle button, the animation - is just making that one simple mechanism convenient and pleasant to use.</p><p>If you understood every step here, you didn't just copy a dark mode toggle. You understand exactly how to build theme switching into any future project, with or without Tailwind, because you know the actual mechanism instead of just the code that happened to work.</p>
        <hr style="margin: 30px 0;" />
        <p><a href="https://www.codekk.dev/blogs/dark-light-mode-react-vite-tailwind-typescript">Read full article on CodeKK</a></p>
      ]]></content:encoded>
      <category>dark-mode</category>
      <category>vite</category>
      <category>context-api</category>
      <category>tailwindcss</category>
      <category>tutorial</category>
      <enclosure url="https://res.cloudinary.com/dyswu0gin/image/upload/v1781664521/uifindr/components/od7jj65eoht4jhvgdvku.jpg" type="image/jpeg" />
    </item>
  </channel>
</rss>