<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pau Dang</title>
    <description>The latest articles on DEV Community by Pau Dang (@paudang).</description>
    <link>https://dev.to/paudang</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3778555%2F0cf1e817-ad3e-4643-a257-b03aa03652f0.jpg</url>
      <title>DEV Community: Pau Dang</title>
      <link>https://dev.to/paudang</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paudang"/>
    <language>en</language>
    <item>
      <title>Playwright vs Cucumber: The Ultimate Developer's Guide to Modern Testing</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Sat, 13 Jun 2026 02:29:22 +0000</pubDate>
      <link>https://dev.to/paudang/playwright-vs-cucumber-the-ultimate-developers-guide-to-modern-testing-1j3n</link>
      <guid>https://dev.to/paudang/playwright-vs-cucumber-the-ultimate-developers-guide-to-modern-testing-1j3n</guid>
      <description>&lt;p&gt;Let's be real: Behavior-Driven Development (BDD) looked great on paper. The idea of writing tests in plain English (&lt;code&gt;Given&lt;/code&gt;, &lt;code&gt;When&lt;/code&gt;, &lt;code&gt;Then&lt;/code&gt;) using Cucumber so that your Product Owner could read them was a beautiful dream. &lt;/p&gt;

&lt;p&gt;But if we are honest, how often does your PO &lt;em&gt;actually&lt;/em&gt; read those &lt;code&gt;.feature&lt;/code&gt; files?&lt;/p&gt;

&lt;p&gt;For developers, Cucumber often feels like an unnecessary abstraction. You have to maintain two layers of files, context sharing between steps gets weird, and debugging can be a pain. &lt;/p&gt;

&lt;p&gt;If you just want to ship code and know that your app works without the BDD overhead, say hello to &lt;strong&gt;Playwright&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Aha!" Moment: Let's Look at the Code
&lt;/h2&gt;

&lt;p&gt;Let's say we want to test a simple login page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Cucumber Way (Painful):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="c"&gt;# login.feature&lt;/span&gt;
&lt;span class="kd"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Login
  &lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; User can login
    &lt;span class="nf"&gt;Given &lt;/span&gt;I navigate to the login page
    &lt;span class="nf"&gt;When &lt;/span&gt;I type &lt;span class="s"&gt;"admin"&lt;/span&gt; in the username field
    &lt;span class="nf"&gt;And &lt;/span&gt;I click the submit button
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// steps.js&lt;/span&gt;
&lt;span class="nc"&gt;Given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I navigate to the login page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* code */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nc"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I type {string} in the username field&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* code */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Imagine doing this for 500 different actions...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Playwright Way (Beautiful):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User can login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://myapp.com/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One file. Strong typing. Auto-completion in VS Code. It just makes sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Playwright is Technically Superior
&lt;/h2&gt;

&lt;p&gt;Here is a deep dive into why Playwright is dominating the testing space right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Complete Workflow: From Engine to Report
&lt;/h3&gt;

&lt;p&gt;Before we dive into the specific features, let's look at the entire lifecycle of a Playwright test. Understanding this flow is key to understanding why it's so fast and reliable:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ohjuw719fxyxb8m6d8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ohjuw719fxyxb8m6d8m.png" alt=" " width="800" height="2352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Bi-directional Architecture (No more HTTP latency!)
&lt;/h3&gt;

&lt;p&gt;Older frameworks like Selenium use the WebDriver protocol. Every command (like clicking a button) is sent as an HTTP request. This is slow and prone to timing issues.&lt;/p&gt;

&lt;p&gt;Playwright connects directly to the browser engine using &lt;strong&gt;WebSockets&lt;/strong&gt; (via Chrome DevTools Protocol - CDP). Playwright instantly knows when a network request fires or when the DOM changes. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Auto-Waiting (Goodbye &lt;code&gt;sleep()&lt;/code&gt;... mostly)
&lt;/h3&gt;

&lt;p&gt;Flaky tests are the enemy of CI/CD. &lt;br&gt;
Playwright has built-in &lt;strong&gt;Actionability Checks&lt;/strong&gt;. Before it clicks a button, it automatically ensures the button is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attached to the DOM&lt;/li&gt;
&lt;li&gt;Visible&lt;/li&gt;
&lt;li&gt;Stable (not jumping around due to CSS animations)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This eliminates 99% of the need for hardcoded &lt;code&gt;sleep&lt;/code&gt; statements. While Playwright still provides &lt;code&gt;await page.waitForTimeout()&lt;/code&gt; for specific edge cases (like waiting for an unpredictable third-party API or backend cron job), for standard UI interactions, it just waits intelligently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Browser Contexts (Fast Isolation)
&lt;/h3&gt;

&lt;p&gt;Launching a browser is expensive. Playwright solves this with &lt;strong&gt;Browser Contexts&lt;/strong&gt;. &lt;br&gt;
Think of a Context as a lightning-fast Incognito window. Playwright launches the browser binary &lt;em&gt;once&lt;/em&gt;, and then spins up a new isolated Context for each test case in milliseconds. Clean cookies, clean local storage, zero cross-test pollution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Game Changer: Trace Viewer
&lt;/h2&gt;

&lt;p&gt;If you take away one thing from this article, let it be this. &lt;a href="https://playwright.dev/docs/trace-viewer" rel="noopener noreferrer"&gt;Trace Viewer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When your test fails on GitHub Actions, Playwright outputs a &lt;strong&gt;Trace&lt;/strong&gt; file. You download it, run &lt;code&gt;npx playwright show-trace&lt;/code&gt;, and you get a full time-travel GUI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A DOM snapshot (the actual DOM you can inspect!) for every single action.&lt;/li&gt;
&lt;li&gt;Network requests, console logs, and source code mapping.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's literally a DVR for your test failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict: Cucumber vs Playwright
&lt;/h2&gt;

&lt;p&gt;It's important to remember that Cucumber isn't a bad tool—it just solves a different problem. Cucumber is a &lt;em&gt;collaboration&lt;/em&gt; tool designed to bridge the gap between business stakeholders and developers using plain English. If your Product Owners are actively involved in writing and reviewing &lt;code&gt;.feature&lt;/code&gt; files, Cucumber is doing exactly what it was built for.&lt;/p&gt;

&lt;p&gt;However, if your E2E tests are strictly an engineering endeavor—written by devs, for devs—that Gherkin layer becomes an unnecessary translation burden. Playwright removes that friction, offering incredible speed, modern architecture, and debugging tools that actually make sense to engineers.&lt;/p&gt;

&lt;p&gt;Are you team Cucumber or team Playwright? Drop your thoughts in the comments below!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>playwright</category>
      <category>automation</category>
    </item>
    <item>
      <title>How We Prevented Cascading Failures in our Node.js Microservices (Timeout, Retry, Circuit Breaker)</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Mon, 08 Jun 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/paudang/how-we-prevented-cascading-failures-in-our-nodejs-microservices-timeout-retry-circuit-breaker-3a3l</link>
      <guid>https://dev.to/paudang/how-we-prevented-cascading-failures-in-our-nodejs-microservices-timeout-retry-circuit-breaker-3a3l</guid>
      <description>&lt;p&gt;Have you ever seen a massive, scalable architecture crash just because an internal email service took too long to reply? &lt;/p&gt;

&lt;p&gt;In the world of backend engineering, we often focus on SQL Injection, XSS, or zero-day exploits. However, one of the most devastating issues you'll face isn't a complex buffer overflow—it's an &lt;strong&gt;Application-Layer Denial of Service (DoS)&lt;/strong&gt; that turns your own microservices against each other.&lt;/p&gt;

&lt;p&gt;Welcome to the concept of &lt;strong&gt;Cascading Failures&lt;/strong&gt;. Today, we're diving into how this happens in Node.js, and how you can implement the "Resilience Trio" (Timeout, Retry, Circuit Breaker) to stop it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Scenario: The Weakest Link
&lt;/h2&gt;

&lt;p&gt;Imagine an e-commerce platform built on a robust Node.js microservices architecture. The front-facing API Gateway is protected by a WAF and Rate Limiting. &lt;/p&gt;

&lt;p&gt;However, deep inside the system, the core &lt;code&gt;OrderService&lt;/code&gt; communicates with a low-priority, internal &lt;code&gt;EmailNotificationService&lt;/code&gt; to send order receipts. &lt;/p&gt;

&lt;p&gt;An attacker (or even just a massive spike in traffic) hits the API. The API Gateway handles it fine. But the &lt;code&gt;EmailNotificationService&lt;/code&gt; gets overwhelmed and starts slowing down.&lt;/p&gt;

&lt;h3&gt;
  
  
  The System Collapse
&lt;/h3&gt;

&lt;p&gt;Here is where the vulnerability lies: &lt;strong&gt;Unbounded Promises and Infinite Waits.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Node.js handles non-blocking I/O brilliantly. But if the &lt;code&gt;OrderService&lt;/code&gt; calls a dying &lt;code&gt;EmailNotificationService&lt;/code&gt; and awaits a response without a strict &lt;strong&gt;Timeout&lt;/strong&gt;, the connection hangs. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe33mu0k1owyd5u319t4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe33mu0k1owyd5u319t4.png" alt=" " width="799" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connection Exhaustion&lt;/strong&gt;: Hundreds of requests to the &lt;code&gt;OrderService&lt;/code&gt; are stuck in a &lt;code&gt;PENDING&lt;/code&gt; state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Starvation&lt;/strong&gt;: Memory usage spikes. The connection pool to the database is tied up by pending transactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Domino Effect&lt;/strong&gt;: &lt;code&gt;OrderService&lt;/code&gt; runs out of RAM, stops responding to the &lt;code&gt;API Gateway&lt;/code&gt;. The Gateway queues up requests until it too crashes. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The entire e-commerce platform goes offline just because an email service was slow.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Defense: The Resilience Trio
&lt;/h2&gt;

&lt;p&gt;To prevent your system from being its own worst enemy, you must adopt a "Zero Trust" mindset regarding internal network calls: &lt;em&gt;assume every downstream service will eventually fail or hang&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;We implement defense-in-depth using three core patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Prevention Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6k07zsr0zn30qaxvpp0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6k07zsr0zn30qaxvpp0.png" alt=" " width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Shield: Timeout
&lt;/h3&gt;

&lt;p&gt;Never trust the default HTTP timeout (which can be minutes). You must enforce strict application-level timeouts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;withTimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;race&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Operation Timed Out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Impact:&lt;/em&gt; When the Email service hangs, &lt;code&gt;withTimeout&lt;/code&gt; forcefully severs the connection after a few seconds, freeing up memory and DB connections.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Shock Absorber: Exponential Backoff
&lt;/h3&gt;

&lt;p&gt;When a timeout occurs, naive systems immediately retry the request. This leads to a &lt;strong&gt;Retry Storm&lt;/strong&gt; (The Thundering Herd Problem), effectively DDoS-ing the recovering downstream service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Conceptual Exponential Backoff with Jitter&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Impact:&lt;/em&gt; By adding exponential backoff and &lt;em&gt;jitter&lt;/em&gt; (randomness), the retry traffic is smoothed out, giving the downstream service breathing room to recover.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Kill Switch: Circuit Breaker
&lt;/h3&gt;

&lt;p&gt;If a service is under attack, sending &lt;em&gt;any&lt;/em&gt; traffic to it is dangerous. The Circuit Breaker monitors the failure rate. If it crosses a critical threshold, the circuit "Trips" (opens).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;circuitBreaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Circuit is OPEN - Fast Failing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Impact:&lt;/em&gt; When the Circuit Breaker trips, the &lt;code&gt;OrderService&lt;/code&gt; instantly returns a "Graceful Degradation" response (e.g., "Order placed, email will be sent later") without even attempting a TCP connection. &lt;/p&gt;




&lt;h2&gt;
  
  
  Stop Writing Boilerplate
&lt;/h2&gt;

&lt;p&gt;Implementing this "Resilience Trio" correctly from scratch is tedious. One wrong configuration in the Retry logic, and you've built a DDoS cannon into your own system.&lt;/p&gt;

&lt;p&gt;That's why we integrated these enterprise-grade patterns directly into our open-source CLI: &lt;strong&gt;&lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;Nodejs Quickstart Generator&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With our AI-ready CLI, you can automatically generate over &lt;strong&gt;1.06M unique project architectures&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architecture&lt;/strong&gt;: Clean Architecture or MVC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: TypeScript or JavaScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Databases&lt;/strong&gt;: MySQL, PostgreSQL, MongoDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security &amp;amp; Resilience&lt;/strong&gt;: Helmet, CORS, Snyk integration, and the full &lt;strong&gt;Timeout, Retry, Circuit Breaker&lt;/strong&gt; trio out-of-the-box!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure&lt;/strong&gt;: Terraform configurations (Single EC2 up to High Availability WAF + ALB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it out today and generate an enterprise-grade, resilient backend in under 60 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nodejs-quickstart-structure@latest init &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"nodejs-service"&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"TypeScript"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"Clean Architecture"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"MySQL"&lt;/span&gt; &lt;span class="nt"&gt;--db-name&lt;/span&gt; &lt;span class="s2"&gt;"demo"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"REST APIs"&lt;/span&gt; &lt;span class="nt"&gt;--caching&lt;/span&gt; &lt;span class="s2"&gt;"None"&lt;/span&gt; &lt;span class="nt"&gt;--ci-provider&lt;/span&gt; &lt;span class="s2"&gt;"GitHub Actions"&lt;/span&gt; &lt;span class="nt"&gt;--auth&lt;/span&gt; JWT &lt;span class="nt"&gt;--terraform&lt;/span&gt; None &lt;span class="nt"&gt;--no-include-security&lt;/span&gt; &lt;span class="nt"&gt;--resilience&lt;/span&gt; Timeout Retry CircuitBreaker &lt;span class="nt"&gt;--advanced-options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this tool saves you hours of setup, &lt;strong&gt;&lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;please give us a Star on GitHub! ⭐&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stay secure, build resiliently, and never let the weakest link bring you down.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>microservices</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>I Replaced a Costly LLM API with a 100% Offline NLP Engine (And Achieved 0ms Latency)</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Tue, 02 Jun 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/paudang/i-replaced-a-costly-llm-api-with-a-100-offline-nlp-engine-and-achieved-0ms-latency-5d2k</link>
      <guid>https://dev.to/paudang/i-replaced-a-costly-llm-api-with-a-100-offline-nlp-engine-and-achieved-0ms-latency-5d2k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Why building a Domain-Specific Rule-Based Engine natively in JavaScript was the best architectural decision for my open-source project.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://nodejs-quickstart-generator.netlify.app/" rel="noopener noreferrer"&gt;nodejs-quickstart-generator.netlify.app&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/TYWBlbLXdL8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you are building an open-source project in 2024, there is an unspoken pressure to slap an OpenAI API key on it and call it "AI-powered." &lt;/p&gt;

&lt;p&gt;For my latest project—an advanced Node.js architecture scaffolding tool, I wanted users to be able to configure their microservices using natural language. For example: &lt;em&gt;"Give me a Clean Architecture project using TypeScript, PostgreSQL, and Kafka, but I don't need any CI/CD."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The industry standard solution? Send that prompt to an LLM, wait 3-5 seconds, and parse the JSON response. &lt;/p&gt;

&lt;p&gt;But when I looked at the realities of deploying this to thousands of users, I hit a massive wall:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Cost of "Hype AI"&lt;/strong&gt;: As an open-source maintainer, swallowing API costs for every user prompt is financial suicide. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Latency&lt;/strong&gt;: Waiting 3-5 seconds for an API call destroys the instantaneous, snappy feel of a modern web tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Hallucination Danger&lt;/strong&gt;: When configuring software architecture (like Terraform scripts or database orchestrations), a single hallucinated setting can break the entire project. I needed &lt;strong&gt;100% deterministic outputs&lt;/strong&gt;, not creative guesses.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, I threw away the LLM approach. Instead, I built a &lt;strong&gt;Domain-Specific Rule-Based AI Engine&lt;/strong&gt; that runs entirely on the Client-side (Browser). &lt;/p&gt;

&lt;p&gt;Here is how I achieved 0ms latency and $0 operating costs without sacrificing natural language understanding.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Mechanism: Moving from Cloud to Browser
&lt;/h2&gt;

&lt;p&gt;Instead of relying on deep learning models, the engine operates on highly optimized, mathematically verified principles. It is built natively in JavaScript and ships directly to the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Lean Regex &amp;amp; The "Code-Switching" Beast
&lt;/h3&gt;

&lt;p&gt;My user base is global. They don't just speak English; they mix English with Vietnamese, Japanese, Chinese, and Hindi—often in the same sentence (a phenomenon known as code-switching).&lt;/p&gt;

&lt;p&gt;Instead of training a model on 50 languages, I mapped the technical intents and negative patterns across multiple languages using highly refined Regular Expressions. &lt;/p&gt;

&lt;p&gt;Look at how the engine handles negations: &lt;a href="https://github.com/paudang/nodejs-quickstart-structure/blob/82b9150c6f16d4c6f794e88486c686f271fbafb7/docs/.vitepress/theme/composables/nlp.js#L10" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Forward-facing negation check (Up to 30 chars before the match)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isNegativeBefore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;no|without|không|khong|don't|dont|skip|remove|ko|đừng|khỏi|不要|不|无|没|bina&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*$/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beforeText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Backward-facing negation check (Needed for Japanese 'なし' / Hindi 'nahi')&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isNegativeAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;は|が|を&lt;/span&gt;&lt;span class="se"&gt;)?\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;nahi|なし|ない|いらない&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;afterText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whether a user types &lt;em&gt;"không dùng db"&lt;/em&gt; (Vietnamese), &lt;em&gt;"no database"&lt;/em&gt; (English), or &lt;em&gt;"不要 db"&lt;/em&gt; (Japanese), the engine parses it identically. It acts as a relentless technical tokenizer that extracts precisely what is needed while actively sanitizing inputs to neutralize XSS risks.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Deterministic State Machine (Mapping 1 Million States)
&lt;/h3&gt;

&lt;p&gt;Asking an AI to generate code from scratch is risky. Instead, my NLP engine maps the user's intent directly into a &lt;strong&gt;Deterministic State Machine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The underlying scaffolding system has exactly &lt;code&gt;1,064,448&lt;/code&gt; mathematically verified architectural states (combinations of Databases, Message Brokers, Cloud Providers, etc.). &lt;/p&gt;

&lt;p&gt;The NLP engine evaluates the extracted technical keywords against this pre-defined logical matrix. It securely selects the exact, verified configuration. There are no "maybe"s, no hallucinations—only absolute precision.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Ultimate Result: Fast, Free, and Flawless
&lt;/h2&gt;

&lt;p&gt;By stepping off the LLM hype train and engineering a Local Heuristic NLP Engine, the results were staggering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0ms Response Time&lt;/strong&gt;: Parsing happens locally in the user's browser instantaneously. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0 Permanent Operating Cost&lt;/strong&gt;: No API keys, no server computing costs, no rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Absolute Privacy&lt;/strong&gt;: No user prompts or architectural choices are ever sent to a third-party server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100% Offline-Capable&lt;/strong&gt;: You can literally unplug your router, load the cached PWA, and the natural language engine will continue to parse prompts flawlessly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Takeaway
&lt;/h3&gt;

&lt;p&gt;Large Language Models are incredible tools for generative and creative tasks. But for domain-specific, deterministic routing, we often over-engineer. Sometimes, the most elegant, scalable, and cost-effective AI is the one you build with strict rules, clever regex, and zero server dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Experience the 0ms offline NLP engine yourself&lt;/strong&gt;: Try out the newly released &lt;code&gt;v2.6.0&lt;/code&gt; Web UI at the &lt;a href="https://nodejs-quickstart-generator.netlify.app/" rel="noopener noreferrer"&gt;Node.js Quickstart Generator&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>Production-Ready Logging: An Agnostic ELK Stack Setup for Node.js (with a 512MB RAM Local Constraint)</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Mon, 01 Jun 2026 00:53:34 +0000</pubDate>
      <link>https://dev.to/paudang/production-ready-logging-an-agnostic-elk-stack-setup-for-nodejs-with-a-512mb-ram-local-49nd</link>
      <guid>https://dev.to/paudang/production-ready-logging-an-agnostic-elk-stack-setup-for-nodejs-with-a-512mb-ram-local-49nd</guid>
      <description>&lt;h2&gt;
  
  
  The Logging Nightmare
&lt;/h2&gt;

&lt;p&gt;Deploying microservices across Multi-Cloud environments using tools like Terraform is an exhilarating milestone. But the moment something breaks, that excitement quickly turns into a nightmare. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The SSH Grind&lt;/strong&gt;: If you find yourself SSH-ing into disparate instances just to run &lt;code&gt;tail -f&lt;/code&gt; and grep through scattered log files, you're doing it wrong. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Agnostic Approach&lt;/strong&gt;: The industry standard demands Centralized Logging, but chaining your application to vendor-specific solutions like AWS CloudWatch or GCP Cloud Logging limits your architectural freedom. Implementing a true "Cloud-Agnostic" ELK stack gives you back control over your observability data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Clean Architecture &amp;amp; The Non-Blocking Logger Factory
&lt;/h2&gt;

&lt;p&gt;Building this robust observability pipeline requires adhering to Clean Architecture principles, specifically through a Non-Blocking Logger Factory. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardized Interface&lt;/strong&gt;: By wrapping modern logging libraries like &lt;code&gt;Winston&lt;/code&gt; or &lt;code&gt;Pino&lt;/code&gt;, we standardize our application's logging interface. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Secret Sauce&lt;/strong&gt;: The &lt;code&gt;winston-elasticsearch&lt;/code&gt; transport module buffers your logs and pushes them directly to your Elasticsearch cluster in the background. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-Blocking&lt;/strong&gt;: This architectural choice is crucial: it ensures that high-volume log streaming happens &lt;strong&gt;without blocking the Node.js event loop&lt;/strong&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how the data flows through the system:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghcxzfq2yw22mjdg52zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghcxzfq2yw22mjdg52zm.png" alt=" " width="625" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resilience Fallback (The Failsafe)
&lt;/h2&gt;

&lt;p&gt;A centralized system introduces a dangerous dependency. &lt;strong&gt;Your logging infrastructure must never be the reason your application crashes.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Threat&lt;/strong&gt;: If the remote Elasticsearch cluster is unreachable due to network partitions or rate limits, a poorly configured logger will throw uncaught exceptions, bringing down the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Solution&lt;/strong&gt;: We implement a strict Resilience Fallback (Failsafe) mechanism. The transport module safely catches the connection errors and seamlessly falls back to standard output (console), guaranteeing continuous operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 512MB Local-Test Challenge
&lt;/h2&gt;

&lt;p&gt;While this setup is a powerhouse in production, it presents a massive Developer Experience (DX) challenge locally. Elasticsearch is notorious for claiming 4GB to 8GB of RAM. Spinning up ELK locally on standard laptops is a guaranteed way to freeze the OS. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "Aha!" Moment for Local Dev:&lt;/strong&gt; We can tame the JVM by strictly limiting its memory footprint in the &lt;code&gt;docker-compose.elk.yml&lt;/code&gt; configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;elasticsearch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.elastic.co/elasticsearch/elasticsearch:8.10.0&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;discovery.type=single-node&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;xpack.security.enabled=false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ES_JAVA_OPTS="-Xms512m -Xmx512m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limiting Heap Size&lt;/strong&gt;: &lt;code&gt;ES_JAVA_OPTS="-Xms512m -Xmx512m"&lt;/code&gt; limits the Elasticsearch heap size to exactly 512MB, keeping your local dev environment snappy. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frictionless Auth&lt;/strong&gt;: Setting &lt;code&gt;xpack.security.enabled=false&lt;/code&gt; removes the friction of managing certificates and passwords on your local machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cloud Deployment Strategy
&lt;/h2&gt;

&lt;p&gt;When transitioning to Production, your strategy must shift dramatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Managed Services&lt;/strong&gt;: The strongly recommended approach is to leverage Managed Services like AWS OpenSearch or Elastic Cloud. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-Hosted Reality&lt;/strong&gt;: If you must Self-Host on EC2 or GCP, you need at least 4GB of RAM (e.g., t3.medium/e2-medium). You &lt;strong&gt;must&lt;/strong&gt; enable &lt;code&gt;xpack.security&lt;/code&gt;, enforce TLS, and strictly firewall port 9200 from the public internet.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Achieving a balance between robust production observability and a frictionless, lightweight developer experience is the hallmark of mature software engineering. &lt;/p&gt;

&lt;p&gt;I'd love to hear how you handle logging in your own microservices—drop a comment below! If you want to explore a project structure that supports this architecture out-of-the-box, check out the official docs for the &lt;strong&gt;&lt;a href="https://nodejs-quickstart-generator.netlify.app/infrastructure/elk-stack.html" rel="noopener noreferrer"&gt;Node.js Quickstart Generator - Observability (ELK Stack)&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it yourself:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;YouTube Guide&lt;/strong&gt;: &lt;a href="https://youtu.be/Cxbb54T0uo8" rel="noopener noreferrer"&gt;Watch the walkthrough&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;nodejs-quickstart-structure&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author&lt;/strong&gt;: Pau Dang (Senior SE).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>backend</category>
      <category>monitoring</category>
      <category>elk</category>
    </item>
    <item>
      <title>How We Built a Node.js Scaffolding Engine with Over 1 Million Mathematically Verified Architecture States</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Sat, 30 May 2026 04:04:26 +0000</pubDate>
      <link>https://dev.to/paudang/how-we-built-a-nodejs-scaffolding-engine-with-over-1-million-mathematically-verified-architecture-481e</link>
      <guid>https://dev.to/paudang/how-we-built-a-nodejs-scaffolding-engine-with-over-1-million-mathematically-verified-architecture-481e</guid>
      <description>&lt;h2&gt;
  
  
  The Scaffolding Fatigue
&lt;/h2&gt;

&lt;p&gt;Setting up an Enterprise-grade Node.js project from scratch is notoriously time-consuming. Development teams often spend days or weeks on configuration before writing any business logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Pain Point&lt;/strong&gt;: Configuring linting, testing frameworks, CI/CD pipelines, Docker environments, and logging systems repeatedly for every new microservice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Old Solution&lt;/strong&gt;: Traditional "boilerplates". These quickly become outdated, bloated, or overly opinionated, forcing developers to untangle pre-written code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The New Approach&lt;/strong&gt;: We shifted away from static repositories and built a &lt;strong&gt;Dynamic Scaffolding Engine&lt;/strong&gt;—a system capable of generating tailored, up-to-date architectures on demand.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Mathematical Matrix
&lt;/h2&gt;

&lt;p&gt;The true scale of this engine lies in the underlying mathematics of architectural combinations. Our system operates on a precise formula:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Core Stack (6,048) x DevOps Multiplier (176) = 1,064,448 States&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't an arbitrary number. Here is how it breaks down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core Stack&lt;/strong&gt;: Represents the foundational application layer. This includes choices between TypeScript/JavaScript, MVC or Clean Architecture patterns, various ORMs (Prisma, TypeORM, Sequelize), and Message Brokers (RabbitMQ, Kafka).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevOps Multiplier&lt;/strong&gt;: Layers on infrastructure configurations, covering cloud providers (AWS, GCP, Azure), containerization (Docker, Kubernetes), and Infrastructure as Code (Terraform).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The crucial distinction is that these 1,064,448 combinations are not random, unverified pairings. Every single state represents a mathematically verified architecture where the chosen components integrate flawlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Integrity &amp;amp; Decoupling
&lt;/h2&gt;

&lt;p&gt;Managing a million potential architectural states presents a monumental technical challenge: preventing dependency hell and avoiding broken code. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decoupled Modules&lt;/strong&gt;: We organized our templating system so that complex modules (e.g., Kafka event streaming, Multi-Cloud Terraform configurations, ELK stack observability) are completely isolated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Injection&lt;/strong&gt;: When a developer selects advanced options, the engine intelligently injects the required configurations into the base template. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Silent Crashes&lt;/strong&gt;: This dynamic injection ensures variables, dependencies, and environment files are orchestrated perfectly, eliminating the risk of silent code crashes ("crash code ngầm").&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 80% Coverage Policy
&lt;/h2&gt;

&lt;p&gt;Generating code is the easy part; generating &lt;em&gt;production-ready&lt;/em&gt; code is significantly harder. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict Quality Control&lt;/strong&gt;: We enforce a strict 80% minimum test coverage policy across all generated scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run-Ready Assets&lt;/strong&gt;: This ensures the engine doesn't merely spit out an empty skeleton. The output is a clean, run-ready production asset complete with passing tests, giving teams the confidence to deploy immediately.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;By transitioning from manual, error-prone setups to automated, scalable architecture generation, development teams can reclaim hundreds of engineering hours. The engine transforms a tedious week-long task into a few clicks start &lt;a href="https://nodejs-quickstart-generator.netlify.app" rel="noopener noreferrer"&gt;Generator&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Try it yourself:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;YouTube Guide&lt;/strong&gt;: &lt;a href="https://youtu.be/Cxbb54T0uo8" rel="noopener noreferrer"&gt;Watch the walkthrough&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;nodejs-quickstart-structure&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author&lt;/strong&gt;: Pau Dang (Senior SE).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>backend</category>
      <category>node</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Stop Wrestling with AWS: I Built a Tool to Generate Production-Ready Node.js &amp; Terraform</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Thu, 21 May 2026 03:31:19 +0000</pubDate>
      <link>https://dev.to/paudang/i-built-a-nodejs-generator-with-production-ready-aws-terraform-so-you-dont-have-to-1ne7</link>
      <guid>https://dev.to/paudang/i-built-a-nodejs-generator-with-production-ready-aws-terraform-so-you-dont-have-to-1ne7</guid>
      <description>&lt;h2&gt;
  
  
  A deep dive into how I automated setting up Node.js backends and AWS infrastructure using Terraform.
&lt;/h2&gt;

&lt;p&gt;If you’re anything like me, starting a new project is the most exciting part of development. You get to choose your architecture, set up your folders, and start writing code. &lt;/p&gt;

&lt;p&gt;But then comes the deployment phase...&lt;/p&gt;

&lt;p&gt;Suddenly, you're wrestling with the AWS Console. You need a VPC, public/private subnets, an Internet Gateway, Route Tables, NAT Gateways, Security Groups, an ALB, EC2 instances, and an RDS database. &lt;/p&gt;

&lt;p&gt;It takes hours, and if you miss a single route table entry, nothing works.&lt;/p&gt;

&lt;p&gt;I got tired of repeating this painful process. So, I decided to solve it. I built a CLI tool that not only generates a robust Node.js backend (Clean Architecture, TypeScript, etc.) but also includes &lt;strong&gt;production-ready AWS infrastructure as code (IaC) using Terraform&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is how it works, and how you can use it to spin up an AWS environment locally for free.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Everything is a Click" Problem
&lt;/h2&gt;

&lt;p&gt;ClickOps (configuring infrastructure manually via the AWS UI) is fine for learning, but terrible for repeatability. If you want to replicate a setup or tear it down, you have to click through 20 different screens.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;Node.js Quickstart Structure&lt;/strong&gt;, you can generate a complete backend with the infrastructure already wired up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nodejs-quickstart-structure@latest init &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"nodejs-service"&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"TypeScript"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"Clean Architecture"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"MySQL"&lt;/span&gt; &lt;span class="nt"&gt;--db-name&lt;/span&gt; &lt;span class="s2"&gt;"demo"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"REST APIs"&lt;/span&gt; &lt;span class="nt"&gt;--caching&lt;/span&gt; &lt;span class="s2"&gt;"None"&lt;/span&gt; &lt;span class="nt"&gt;--ci-provider&lt;/span&gt; &lt;span class="s2"&gt;"GitHub Actions"&lt;/span&gt; &lt;span class="nt"&gt;--auth&lt;/span&gt; None &lt;span class="nt"&gt;--terraform&lt;/span&gt; &lt;span class="s2"&gt;"Production"&lt;/span&gt; &lt;span class="nt"&gt;--no-include-security&lt;/span&gt; &lt;span class="nt"&gt;--advanced-options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;( Pro tip: Insert a cool GIF or screenshot of your CLI running here!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The CLI asks you a few questions (TypeScript? Clean Architecture? Postgres?) and then creates a complete repository. But the real magic happens in the &lt;code&gt;terraform/&lt;/code&gt; folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Flavors of Infrastructure (Because Cost Matters)
&lt;/h2&gt;

&lt;p&gt;Not every project is a massive enterprise app. Side projects need to be cheap, while production apps need to be highly available. I designed two Terraform templates to match these needs:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Standard Tier (The "Keep it Cheap" Setup)
&lt;/h3&gt;

&lt;p&gt;Perfect for side projects, MVPs, and dev environments. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single EC2 instance&lt;/li&gt;
&lt;li&gt;Single-AZ RDS database&lt;/li&gt;
&lt;li&gt;Shared NAT Gateway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Keep your AWS bill under $30/month (or even within the Free Tier).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Snippet of what gets generated:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ec2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/ec2"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;
  &lt;span class="c1"&gt;# ... automatically wired to your VPC and DB&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Production Tier (High Availability)
&lt;/h3&gt;

&lt;p&gt;When you're ready for the big leagues. This tier provisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-AZ Architecture&lt;/strong&gt; for zero-downtime failover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt; to distribute traffic smoothly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS WAF (Web Application Firewall)&lt;/strong&gt; to block SQLi and XSS automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Air-gapped RDS&lt;/strong&gt; in isolated subnets for maximum security.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Best Part: Testing Locally for FREE with LocalStack
&lt;/h2&gt;

&lt;p&gt;I know what you're thinking: &lt;em&gt;"Terraform is great, but running &lt;code&gt;terraform apply&lt;/code&gt; on AWS costs money. How do I test this without going broke?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This was a major pain point, so I integrated &lt;a href="https://localstack.cloud/" rel="noopener noreferrer"&gt;LocalStack&lt;/a&gt; support right out of the box. You can spin up the entire AWS architecture on your local machine using Docker—completely free. Setup &lt;a href="https://nodejs-quickstart-generator.netlify.app/infrastructure/aws-terraform.html#local-testing-localstack" rel="noopener noreferrer"&gt;Local Testing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just start LocalStack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;localstack start &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;localstack.tf&lt;/code&gt; file to point Terraform to your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
  &lt;span class="nx"&gt;access_key&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
  &lt;span class="nx"&gt;secret_key&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
  &lt;span class="nx"&gt;skip_credentials_validation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;skip_requesting_account_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;skip_metadata_api_check&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;s3_use_path_style&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ec2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:4566"&lt;/span&gt;
    &lt;span class="nx"&gt;rds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:4566"&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom. You now have a local VPC, EC2 instance, and RDS database running on your laptop. You can validate your entire infrastructure setup without giving Amazon your credit card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out!
&lt;/h2&gt;

&lt;p&gt;I built this tool to save developers time and enforce best practices from day one. Whether you're building a quick weekend hackathon project or a serious enterprise application, the foundation is ready for you.&lt;/p&gt;

&lt;p&gt;Give it a spin, and let me know what you think!&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://nodejs-quickstart-generator.netlify.app/#configurator" rel="noopener noreferrer"&gt;Nodejs Quickstart Generator&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://youtu.be/Cxbb54T0uo8" rel="noopener noreferrer"&gt;How to use the tool and video demo&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://github.com/paudang/nodejs-service-terraform/tree/main/terraform" rel="noopener noreferrer"&gt;Sample Project: nodejs-service-terraform&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If this saves you a few hours of AWS headaches, dropping a ⭐ on the repo would mean the world to me. I'd love to hear your feedback in the comments!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>aws</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Stop Blindly Trusting Passport.js: How to Implement Secure OAuth CSRF Protection Manually</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Mon, 18 May 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/paudang/stop-blindly-trusting-passportjs-how-to-implement-secure-oauth-csrf-protection-manually-4fh2</link>
      <guid>https://dev.to/paudang/stop-blindly-trusting-passportjs-how-to-implement-secure-oauth-csrf-protection-manually-4fh2</guid>
      <description>&lt;p&gt;OAuth 2.0 is the backbone of modern authentication. But many developers treat it as a "set it and forget it" feature by using libraries like Passport.js. While these libraries are great, they often hide the critical security handshake happening under the hood—leaving your app vulnerable to &lt;strong&gt;OAuth CSRF attacks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how we implemented a Zero-Trust Social Login flow in &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;nodejs-quickstart-structure v2.2.1&lt;/a&gt; using plain Node.js and Axios.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Attack: How a Hacker Steals Your Identity
&lt;/h2&gt;

&lt;p&gt;Most developers know about standard CSRF (Cross-Site Request Forgery). OAuth CSRF is a specific variant where an attacker tricks a victim into linking the &lt;em&gt;attacker's&lt;/em&gt; social account to the &lt;em&gt;victim's&lt;/em&gt; application account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here is exactly how the attack happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Attacker's Setup&lt;/strong&gt;: A hacker visits your site and clicks "Link Google." They log into &lt;em&gt;their own&lt;/em&gt; Google account, but when Google redirects them back to your site, they &lt;strong&gt;stop&lt;/strong&gt; and copy the &lt;code&gt;code&lt;/code&gt; from the URL.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Phish&lt;/strong&gt;: The hacker sends a victim a link: &lt;code&gt;https://your-app.com/api/auth/google/callback?code=HACKER_CODE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Click&lt;/strong&gt;: The victim (already logged into your app) clicks the link.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Link&lt;/strong&gt;: Your server receives the &lt;code&gt;HACKER_CODE&lt;/code&gt;, validates it with Google, and sees it's a valid account. Since the victim is the one who sent the request, your server links the &lt;strong&gt;hacker's Google ID&lt;/strong&gt; to the &lt;strong&gt;victim's user profile&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Takeover&lt;/strong&gt;: The hacker can now simply click "Login with Google" on your site and they are instantly logged in as the victim.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The result?&lt;/strong&gt; A quiet, total account takeover.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Attack Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35f3a6xttvetx0gn0u4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35f3a6xttvetx0gn0u4n.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: The &lt;code&gt;state&lt;/code&gt; Parameter
&lt;/h2&gt;

&lt;p&gt;The OAuth2 spec provides a &lt;code&gt;state&lt;/code&gt; parameter specifically to prevent this. Here is how it looks:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Secure Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskt5y84e330kgai95o6p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskt5y84e330kgai95o6p.png" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Generate and Store a Secure State
&lt;/h3&gt;

&lt;p&gt;When the user clicks "Login with Google," don't just redirect them. Generate a random string, store it in a secure cookie, and pass it to the provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// authController.js&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;googleLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Store state in an HttpOnly, SameSite=Lax cookie&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;oauth_state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="na"&gt;httpOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Always use HTTPS in production&lt;/span&gt;
    &lt;span class="na"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="c1"&gt;// Valid for 10 minutes&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://accounts.google.com/o/oauth2/v2/auth?`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CALLBACK_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;response_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;--- The magic happens here&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Verify the State on Callback
&lt;/h3&gt;

&lt;p&gt;When Google redirects the user back to your site, the first thing you must do is check if the &lt;code&gt;state&lt;/code&gt; in the URL matches the &lt;code&gt;state&lt;/code&gt; in your cookie.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;googleCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;savedState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;oauth_state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Always clear the cookie immediately to prevent reuse&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;oauth_state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;savedState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Security Alert: State mismatch detected!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Now it's safe to exchange the code for a token&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth2.googleapis.com/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CALLBACK_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle user data...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Manual Approach Wins
&lt;/h2&gt;

&lt;p&gt;By ditching "black-box" libraries and using a deterministic flow with Axios, you get:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Full Auditability&lt;/strong&gt;: You can log exactly what was sent and received.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Explicit Security&lt;/strong&gt;: You can't "forget" to validate the state because you are writing the logic yourself.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Leaner App&lt;/strong&gt;: No need for heavy middleware if you only need social login.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Want a Battle-Tested Template?
&lt;/h2&gt;

&lt;p&gt;Implementing this correctly across every project is tedious. That's why I built the &lt;strong&gt;&lt;a href="https://nodejs-quickstart-generator.netlify.app/#configurator" rel="noopener noreferrer"&gt;Node.js Quickstart Generator&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you want to see this implementation in action, check out these resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sample Project&lt;/strong&gt;: &lt;a href="https://github.com/paudang/nodejs-social-auth" rel="noopener noreferrer"&gt;paudang/nodejs-social-auth&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework&lt;/strong&gt;: &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;paudang/nodejs-quickstart-structure&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://systemweakness.com/the-oauth-integration-debt-why-your-social-login-is-a-csrf-risk-c2008099c05e" rel="noopener noreferrer"&gt;The OAuth Integration Debt: Why Your Social Login Is a CSRF Risk&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you found this helpful, check out the repo and give it a ⭐!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;GitHub Repo: nodejs-quickstart-structure&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>backend</category>
      <category>security</category>
    </item>
    <item>
      <title>OAuth2 Account Takeovers: Building a Bulletproof Social Login Architecture</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Wed, 06 May 2026 15:45:54 +0000</pubDate>
      <link>https://dev.to/paudang/oauth2-account-takeovers-building-a-bulletproof-social-login-architecture-973</link>
      <guid>https://dev.to/paudang/oauth2-account-takeovers-building-a-bulletproof-social-login-architecture-973</guid>
      <description>&lt;p&gt;When implementing Social Login (Google, GitHub), many developers assume that the heavy lifting is handled by the provider. The truth is: &lt;strong&gt;the integration layer is where your system is most vulnerable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To tackle these vulnerabilities head-on, we must rethink the integration. Here is how to build a bulletproof, Zero-Trust social login architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pitfall 1: The Black Box Dependency
&lt;/h3&gt;

&lt;p&gt;Libraries like Passport.js are incredibly popular, but they wrap the OAuth flow into a "black box." In enterprise environments, you need total auditability. We opted for a custom &lt;code&gt;Axios&lt;/code&gt; implementation. This reduces the attack surface and allows precise domain-level error handling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// https://github.com/paudang/nodejs-social-auth/blob/main/src/infrastructure/auth/socialAuthService.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GoogleProvider&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ISocialProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ISocialProfile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redirect_uri&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grant_type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Deterministic Token Exchange&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth2.googleapis.com/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;access_token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tokenResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profileResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/oauth2/v2/userinfo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Pitfall 2: Blind Account Linking
&lt;/h3&gt;

&lt;p&gt;What happens if an attacker registers on a secondary OAuth provider using your email address, and your system automatically links it? You just gave them an Account Takeover (ATO) vector.&lt;/p&gt;

&lt;p&gt;Our system prevents this by intelligently linking social profiles and nullifying passwords for OAuth-created accounts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// https://github.com/paudang/nodejs-social-auth/blob/main/src/usecases/auth/socialLoginUseCase.ts&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Disable traditional login&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GitHub&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Controlled linking&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;googleId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;googleId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Update user...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bridging the Zero-Trust Gap
&lt;/h3&gt;

&lt;p&gt;Once authenticated via Google, we &lt;strong&gt;do not&lt;/strong&gt; trust their session indefinitely. We immediately bridge the user into our internal JWT system, fortified by a Redis "Nuclear Revoke" mechanism.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aqzot0dnrtajqoeafyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aqzot0dnrtajqoeafyl.png" alt=" " width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;  The Verify 'state' mechanism (CSRF protection) shown in the diagram represents the ideal architecture target. Cryptographic state validation is actively in development and will be codified in the next version of the generator.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can generate this exact, secure architecture for your next project using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nodejs-quickstart-structure@latest init &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"my-secure-app"&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"TypeScript"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"Clean Architecture"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"PostgreSQL"&lt;/span&gt; &lt;span class="nt"&gt;--db-name&lt;/span&gt; &lt;span class="s2"&gt;"demo"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"REST APIs"&lt;/span&gt; &lt;span class="nt"&gt;--caching&lt;/span&gt; &lt;span class="s2"&gt;"Redis"&lt;/span&gt; &lt;span class="nt"&gt;--ci-provider&lt;/span&gt; &lt;span class="s2"&gt;"GitHub Actions"&lt;/span&gt; &lt;span class="nt"&gt;--auth&lt;/span&gt; JWT &lt;span class="nt"&gt;--social-auth&lt;/span&gt; Google GitHub &lt;span class="nt"&gt;--no-include-security&lt;/span&gt; &lt;span class="nt"&gt;--advanced-options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the CLI tool at &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;Nodejs Quickstart Generator&lt;/a&gt; and the reference implementation code at &lt;a href="https://github.com/paudang/nodejs-social-auth" rel="noopener noreferrer"&gt;nodejs-social-auth&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you missed the first part of this architectural series on JWT Revocation, you can read it here: &lt;a href="https://systemweakness.com/the-illusion-of-stateless-security-rethinking-jwt-revocation-at-scale-8426472c5022" rel="noopener noreferrer"&gt;The Illusion of Stateless Security: Rethinking JWT Revocation at Scale&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>node</category>
      <category>oauth</category>
      <category>security</category>
    </item>
    <item>
      <title>Slashed My Automation Suite from 9 Hours to 1 Hour with This Simple Caching Trick</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Thu, 30 Apr 2026 01:21:32 +0000</pubDate>
      <link>https://dev.to/paudang/slashed-my-automation-suite-from-9-hours-to-1-hour-with-this-simple-caching-trick-22n9</link>
      <guid>https://dev.to/paudang/slashed-my-automation-suite-from-9-hours-to-1-hour-with-this-simple-caching-trick-22n9</guid>
      <description>&lt;p&gt;We've all been there: you build an amazing automation suite, hit "Run", and realize it's going to take until next Tuesday to finish. &lt;/p&gt;

&lt;p&gt;Last week, I faced a massive bottleneck in my CI/CD pipeline. Here’s how I optimized a &lt;strong&gt;9-hour test suite down to just 1 hour&lt;/strong&gt; using a "Base Cache" strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: The &lt;code&gt;npm install&lt;/code&gt; Nightmare
&lt;/h2&gt;

&lt;p&gt;I'm building a &lt;strong&gt;Node.js Quickstart Generator&lt;/strong&gt;. To ensure quality, I have to validate &lt;strong&gt;720 different combinations&lt;/strong&gt; of technologies (Clean Architecture, MVC, TypeScript, JavaScript, MySQL, MongoDB, Kafka, etc.).&lt;/p&gt;

&lt;p&gt;For every single test run, the script was doing a fresh &lt;code&gt;npm install&lt;/code&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Result:&lt;/strong&gt; 9 hours of total execution time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Culprit:&lt;/strong&gt; Redundant network fetches and disk I/O for the same packages over and over again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudbnbmfms6d8ojrl3agl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudbnbmfms6d8ojrl3agl.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Smart "Base Caching"
&lt;/h2&gt;

&lt;p&gt;The mindset shift was simple: &lt;strong&gt;Stop treating every project as a unique snowflake.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Even with 720 combinations, they share 95% of the same core dependencies. Here is the 3-step workflow I implemented:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Identify the "Base"
&lt;/h3&gt;

&lt;p&gt;I grouped projects by their heaviest dependencies: &lt;strong&gt;Language + Database&lt;/strong&gt;. This resulted in only &lt;strong&gt;8 unique Base Caches&lt;/strong&gt; (e.g., &lt;code&gt;TypeScript_PostgreSQL&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Bootstrap via Copy
&lt;/h3&gt;

&lt;p&gt;Instead of running &lt;code&gt;npm install&lt;/code&gt; from scratch, the script now uses this optimized logic, code example: &lt;a href="https://github.com/paudang/nodejs-quickstart-structure/blob/feature/oauht2-google-github/scripts/lib/validation-core.js#L500" rel="noopener noreferrer"&gt;https://github.com/paudang/nodejs-quickstart-structure/blob/feature/oauht2-google-github/scripts/lib/validation-core.js#L500&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Define the Base Cache key (Language + DB)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseHashKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cachePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`node_modules_base_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseHashKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. If a base cache exists, copy it in seconds&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;usedCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pathExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachePath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;usedCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Run npm install (Use --prefer-offline for ultra-fast delta updates)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;npmCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;usedCache&lt;/span&gt; 
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;npm install --prefer-offline --no-audit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;npm install --no-audit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;npmCmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;projectPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Save the cache if it's the first run&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;usedCache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;cachePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because most of the packages are already in the &lt;code&gt;node_modules&lt;/code&gt; folder, npm just performs a quick delta update.&lt;/p&gt;




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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wesrr7smokm3374kwok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wesrr7smokm3374kwok.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The optimization was a game-changer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Execution Time:&lt;/strong&gt; 9 Hours ➡️ &lt;strong&gt;1 Hour&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage Efficiency:&lt;/strong&gt; 80GB (if full-cached) ➡️ &lt;strong&gt;1.2GB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev Experience:&lt;/strong&gt; I can now run the full validation suite multiple times a day instead of once a week.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaways for Developers
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find the Real Bottleneck:&lt;/strong&gt; Don't micro-optimize your code if your Network/IO is the one killing your productivity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Think in "Deltas":&lt;/strong&gt; If you can't cache everything, cache the 90% and compute the rest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automate the Automation:&lt;/strong&gt; Always monitor your scripts. If it's slow, it’s a bug.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;How do you handle heavy node_modules in your CI/CD?&lt;/strong&gt; I'd love to hear your tricks in the comments! 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you found this helpful, feel free to give it a ❤️ and follow for more DevOps and Performance tips!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>backend</category>
      <category>node</category>
      <category>automation</category>
    </item>
    <item>
      <title>When Logout is not enough: Defending against Token Theft with Big Tech-grade Rotation.</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Mon, 20 Apr 2026 05:47:58 +0000</pubDate>
      <link>https://dev.to/paudang/when-logout-is-not-enough-defending-against-token-theft-with-big-tech-grade-rotation-3c28</link>
      <guid>https://dev.to/paudang/when-logout-is-not-enough-defending-against-token-theft-with-big-tech-grade-rotation-3c28</guid>
      <description>&lt;p&gt;Hi, I’m Pau Dang.&lt;/p&gt;

&lt;p&gt;Imagine a silent intruder. They steal a single &lt;strong&gt;Refresh Token&lt;/strong&gt; and maintain persistence in your system for months. Your user changes their password, logs out, and feels safe—but the intruder remains. This is the reality of systems that lack &lt;strong&gt;Stateless Invalidation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m tired of boilerplates that ignore this &lt;strong&gt;Attack Vector.&lt;/strong&gt; In this post, I want to discuss the architectural blueprint for Enterprise-grade JWT security.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Revocation Gap: The Silent Crisis
&lt;/h2&gt;

&lt;p&gt;A signed JWT is a rogue agent if you cannot kill it. Pure statelessness is a high-risk gamble. Most developers treat JWT as a "set and forget" solution, but if you cannot revoke a session instantly, your security is an illusion. &lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;Revocation Gap&lt;/strong&gt;—the space between a user's intent to logout and the token's hardcoded expiration.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Solution: Redis Blacklisting &amp;amp; JTI
&lt;/h2&gt;

&lt;p&gt;To mitigate this without introducing a database bottleneck, we use &lt;strong&gt;JTI (JWT ID)&lt;/strong&gt; and &lt;strong&gt;Redis&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JTI&lt;/strong&gt;: Every token must carry a unique identifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revocation List&lt;/strong&gt;: On logout, we don't "delete" the token; we add its JTI to a high-speed Redis blacklist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL Precision&lt;/strong&gt;: By calculating the exact remaining life of the token, we ensure the Redis list remains lean and performant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. The Climax: "Nuclear Revoke" (Reuse Detection)
&lt;/h2&gt;

&lt;p&gt;Standard token rotation is not enough. We need proactive defense. In my v2.1.0 implementation, we use &lt;strong&gt;Refresh Token Rotation&lt;/strong&gt; with an integrated tripwire.&lt;/p&gt;

&lt;p&gt;If the system receives an old Refresh Token that has already been rotated =&amp;gt; This is a definitive sign of an attack (&lt;strong&gt;Reuse&lt;/strong&gt;). Instead of just rejecting the request, the system triggers a &lt;strong&gt;"Nuclear Revoke"&lt;/strong&gt;: every active session for that user is instantly purged. It represents the ultimate trade-off: forcing a logout for the real user to ensure the absolute eviction of the intruder.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Implementation: Knowledge-as-Code
&lt;/h3&gt;

&lt;p&gt;View the full production-ready logic (automatically generated by &lt;code&gt;nodejs-quickstart-structure&lt;/code&gt;) on &lt;a href="https://github.com/paudang/nodejs-service/blob/main/src/controllers/authController.ts#L46" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AuthController.ts - Enterprise Refresh Logic&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JwtService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyRefreshToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. Integrity Check (Redis Whitelist)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`refresh_tokens:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;activeTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cacheService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="c1"&gt;// --- THE TRIPWIRE: REUSE DETECTION ---&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;activeTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jti&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Security Breach: Session revoking for user &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cacheService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "NUCLEAR REVOKE" - Kill all sessions&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Critical: Session compromised&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// --- ROTATION ---&lt;/span&gt;
    &lt;span class="nx"&gt;activeTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jti&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JwtService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateTokens&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;activeTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshJti&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cacheService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activeTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Scaffolding as an Architectural Mitigation
&lt;/h2&gt;

&lt;p&gt;I’ve automated this entire "Engineering Soul" into my open-source project, &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;nodejs-quickstart-structure&lt;/a&gt;. It supports &lt;strong&gt;5,280 combinations&lt;/strong&gt; of architecture and databases, but the core security blueprint remains rock-solid in all of them.&lt;/p&gt;

&lt;p&gt;Don't settle for "Hello World" security. Automate the excellence.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visual Configurator&lt;/strong&gt;: &lt;a href="https://nodejs-quickstart-generator.netlify.app/" rel="noopener noreferrer"&gt;Nodejs Quickstart Generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stats&lt;/strong&gt;: 4,000+ downloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video Demo&lt;/strong&gt;: &lt;a href="https://youtu.be/NiWs-r2Ml78" rel="noopener noreferrer"&gt;Advanced JWT Security: Refresh Token Rotation &amp;amp; Nuclear Revoke Demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author&lt;/strong&gt;: Pau Dang (Senior SE).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://systemweakness.com/the-illusion-of-stateless-security-rethinking-jwt-revocation-at-scale-8426472c5022" rel="noopener noreferrer"&gt;The Illusion of Stateless Security: Rethinking JWT Revocation at Scale.&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>security</category>
      <category>architecture</category>
    </item>
    <item>
      <title>I Tested 5 CI/CD Providers for 2,640 Node.js Projects. Here’s What I Learned.</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Thu, 09 Apr 2026 08:48:29 +0000</pubDate>
      <link>https://dev.to/paudang/i-tested-5-cicd-providers-for-2640-nodejs-projects-heres-what-i-learned-7c6</link>
      <guid>https://dev.to/paudang/i-tested-5-cicd-providers-for-2640-nodejs-projects-heres-what-i-learned-7c6</guid>
      <description>&lt;p&gt;Hi DEV community,&lt;/p&gt;

&lt;p&gt;Stop manually configuring your &lt;code&gt;.yaml&lt;/code&gt; files. After benchmarking 5 major CI providers across &lt;strong&gt;2,640 unique project scaffolding permutations&lt;/strong&gt;, I’ve gathered the ultimate list of "Gotchas," fixes, and rankings to help you pick the right one for your enterprise Node.js app.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 The Ultimate CI/CD Ranking (2026 Edition)
&lt;/h2&gt;

&lt;p&gt;Based on stability, ease of networking, and resource management across 2,640 repositories.&lt;/p&gt;

&lt;h3&gt;
  
  
  🥇 #1. GitHub Actions (The "It Just Works" Choice)
&lt;/h3&gt;

&lt;p&gt;If your code is on GitHub, this is the winner. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developer Experience&lt;/strong&gt;: 10/10. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret Management&lt;/strong&gt;: Seamless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro-Tip&lt;/strong&gt;: Use &lt;code&gt;actions/cache&lt;/code&gt; aggressively. It cuts E2E startup time for databases and Kafka by nearly 40%.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🥈 #2. GitLab CI (The "Total Control" Engine)
&lt;/h3&gt;

&lt;p&gt;Powerful, but requires more networking knowledge than GitHub.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The "Wait-On" Trick&lt;/strong&gt;: When running healthchecks in GitLab CI, the hostname is almost always &lt;code&gt;docker&lt;/code&gt;. Standardize your testing URLs to &lt;code&gt;http://docker:3001&lt;/code&gt; to save hours of debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🥉 #3. Jenkins (The "Swiss Army Knife" of Enterprises)
&lt;/h3&gt;

&lt;p&gt;The hardest to set up, but the most rewarding for complex, private infra.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The "Network Wall"&lt;/strong&gt;: Jenkins running in a container often can't see the app it just "upped" via Docker Compose. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Fix&lt;/strong&gt;: Use &lt;code&gt;host.docker.internal&lt;/code&gt; as your &lt;code&gt;WAIT_ON_HOST&lt;/code&gt;. It allows the Jenkins container to bridge out to the host-mapped ports.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🥉 #4. CircleCI (The Speed Demon)
&lt;/h3&gt;

&lt;p&gt;Fast builds, but stay alert on memory limits.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OOM Prevention&lt;/strong&gt;: Don't let Jest eat your RAM. We found that adding &lt;code&gt;--maxWorkers=2&lt;/code&gt; to your test script prevents the dreaded &lt;code&gt;SIGKILL&lt;/code&gt; on free tier runners.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🥉 #5. Bitbucket Pipelines (The Team Connector)
&lt;/h3&gt;

&lt;p&gt;Great for Atlassian-heavy workflows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Legacy Stability&lt;/strong&gt;: If your builds fail with weird gRPC transport errors, add &lt;code&gt;DOCKER_BUILDKIT: "0"&lt;/code&gt; to your environment. It fixes compatibility issues with older pipeline runners.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📊 The Comparison Matrix (Developer Experience focus)
&lt;/h2&gt;

&lt;p&gt;I tracked these metrics across &lt;strong&gt;2,640 builds&lt;/strong&gt; to see which one makes life easiest for us.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;GitHub Actions&lt;/th&gt;
&lt;th&gt;GitLab CI&lt;/th&gt;
&lt;th&gt;Jenkins&lt;/th&gt;
&lt;th&gt;CircleCI&lt;/th&gt;
&lt;th&gt;Bitbucket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ease of Setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡ Instant&lt;/td&gt;
&lt;td&gt;✅ Fast&lt;/td&gt;
&lt;td&gt;🏗️ Complex&lt;/td&gt;
&lt;td&gt;✅ Fast&lt;/td&gt;
&lt;td&gt;✅ Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;"Cool" Factor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Market Actions&lt;/td&gt;
&lt;td&gt;DinD Sidecars&lt;/td&gt;
&lt;td&gt;Plugins&lt;/td&gt;
&lt;td&gt;Orbs&lt;/td&gt;
&lt;td&gt;Pipes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debug Mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Good Logs&lt;/td&gt;
&lt;td&gt;Great Logs&lt;/td&gt;
&lt;td&gt;Hard to Read&lt;/td&gt;
&lt;td&gt;SSH Debug&lt;/td&gt;
&lt;td&gt;Good Logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free Tier Limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,000 mins&lt;/td&gt;
&lt;td&gt;400 mins&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;2,500 mins&lt;/td&gt;
&lt;td&gt;50 mins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Verdict&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Best for OSS&lt;/td&gt;
&lt;td&gt;Best for DevOps&lt;/td&gt;
&lt;td&gt;Best for Pros&lt;/td&gt;
&lt;td&gt;Best for Speed&lt;/td&gt;
&lt;td&gt;Best for Jira&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🛠️ How we validated this at scale
&lt;/h2&gt;

&lt;p&gt;We built an automation tool that generates entire project structures (Clean Architecture, Typescript, Kafka, MySQL, etc.) and &lt;strong&gt;injects the perfect CI configuration&lt;/strong&gt; for any of these 5 providers. &lt;/p&gt;

&lt;p&gt;We didn't just write these configs—we refined them through 2,640 builds. We solved the hard stuff so you don't have to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐳 &lt;strong&gt;Docker-Out-Of-Docker&lt;/strong&gt;: Fixed the Jenkins permission and networking walls.&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Wait-On Master&lt;/strong&gt;: Standardized healthchecks across host and container bridges.&lt;/li&gt;
&lt;li&gt;📉 &lt;strong&gt;OOM Prevention&lt;/strong&gt;: Added &lt;code&gt;--maxWorkers=2&lt;/code&gt; to keep CircleCI from killing your build.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 The Verdict
&lt;/h2&gt;

&lt;p&gt;Pick the provider that matches your code hosting platform first. But if you have complex, multi-service testing needs, &lt;strong&gt;GitHub Actions&lt;/strong&gt; and &lt;strong&gt;GitLab CI&lt;/strong&gt; are currently pulling ahead in the "Enterprise Free" space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which one are you using? Drop a comment below with your biggest CI/CD headache!&lt;/strong&gt; 👇&lt;/p&gt;




&lt;h3&gt;
  
  
  🛠️ Try it yourself:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  🚀 &lt;strong&gt;Live Web UI&lt;/strong&gt;: &lt;a href="https://paudang.github.io/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;Node.js Quickstart Generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  🎬 &lt;strong&gt;YouTube Guide&lt;/strong&gt;: &lt;a href="https://youtu.be/Cxbb54T0uo8" rel="noopener noreferrer"&gt;Watch the walkthrough&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  📂 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;paudang/nodejs-quickstart-structure&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Love automation? Check out our Node.js Quickstart generator to get these CI configs out-of-the-box.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cicd</category>
      <category>microservices</category>
      <category>automation</category>
    </item>
    <item>
      <title>Stop Wasting Time on Boilerplate: Real-world Kafka &amp; PostgreSQL Demo in 8 minutes</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Tue, 07 Apr 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/paudang/stop-wasting-time-on-boilerplate-real-world-kafka-postgresql-demo-24bp</link>
      <guid>https://dev.to/paudang/stop-wasting-time-on-boilerplate-real-world-kafka-postgresql-demo-24bp</guid>
      <description>&lt;p&gt;After releasing the v2.0.0 Web UI for &lt;strong&gt;Node.js Quickstart Generator&lt;/strong&gt;, the most common question was: &lt;em&gt;"How does it handle real-world complexity?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, I decided to record a full, 8-minute implementation demo building a &lt;strong&gt;Payment Service&lt;/strong&gt; from scratch. &lt;/p&gt;

&lt;h3&gt;
  
  
  📺 Watch: UI to Production Code in 8 Minutes
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://youtu.be/PmmxJLloZ1Q" rel="noopener noreferrer"&gt;https://youtu.be/PmmxJLloZ1Q&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ The Tech Stack (Zero-Prompt Setup)
&lt;/h2&gt;

&lt;p&gt;Instead of answering 20 CLI prompts, I used our new &lt;a href="https://paudang.github.io/nodejs-quickstart-structure/" rel="noopener noreferrer"&gt;Web Configurator&lt;/a&gt; to generate this exact stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture&lt;/strong&gt;: Clean Architecture (Domain, UseCase, Infra)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: PostgreSQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: Redis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messaging&lt;/strong&gt;: Kafka&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Snyk Verified&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🏗️ Clean Architecture in Action
&lt;/h2&gt;

&lt;p&gt;The video shows exactly how the folder structure reflects a production-grade system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;src/domain&lt;/code&gt;&lt;/strong&gt;: Pure entities with no dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;src/usecases&lt;/code&gt;&lt;/strong&gt;: Where the transaction logic lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;src/infrastructure&lt;/code&gt;&lt;/strong&gt;: Concrete implementations for Postgres connections and Kafka producers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📡 Live Kafka Flow
&lt;/h2&gt;

&lt;p&gt;The "Wow" moment is at &lt;strong&gt;05:00&lt;/strong&gt; in the video. We trigger a REST API call that producers a Kafka event, which is then picked up by a separate consumer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero configuration required&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-mapped events&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready for microservices&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛡️ Enterprise-Grade Security
&lt;/h2&gt;

&lt;p&gt;We don't skip security. I ran &lt;code&gt;npm run security:check&lt;/code&gt; in the video to show how &lt;strong&gt;Snyk&lt;/strong&gt; is integrated from the start. Clean code is nothing if it isn't secure.&lt;/p&gt;




&lt;h3&gt;
  
  
  💻 Try it yourself
&lt;/h3&gt;

&lt;p&gt;Want to generate the exact same project as in the video? Run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nodejs-quickstart-structure@latest init &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"payment-service-nodejs"&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"TypeScript"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"Clean Architecture"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"PostgreSQL"&lt;/span&gt; &lt;span class="nt"&gt;--db-name&lt;/span&gt; &lt;span class="s2"&gt;"payment-db"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"Kafka"&lt;/span&gt; &lt;span class="nt"&gt;--caching&lt;/span&gt; &lt;span class="s2"&gt;"Redis"&lt;/span&gt; &lt;span class="nt"&gt;--ci-provider&lt;/span&gt; &lt;span class="s2"&gt;"GitHub Actions"&lt;/span&gt; &lt;span class="nt"&gt;--include-security&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out our &lt;strong&gt;&lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/strong&gt; and let's end boilerplate fatigue together! 🌟&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
