<?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.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>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://paudang.github.io/nodejs-quickstart-structure/" 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>
    <item>
      <title>Introducing Node.js Quickstart Generator v2.0.0: Automated Clean Architecture with a Sleek New UI</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Mon, 06 Apr 2026 12:24:35 +0000</pubDate>
      <link>https://dev.to/paudang/boilerplate-scaffold-pro-nodejs-apps-with-kafka-graphql-redis-in-30s-1ji9</link>
      <guid>https://dev.to/paudang/boilerplate-scaffold-pro-nodejs-apps-with-kafka-graphql-redis-in-30s-1ji9</guid>
      <description>&lt;p&gt;Hi everyone,&lt;/p&gt;

&lt;p&gt;How many times have you set up a new Node.js microservice by copying a folder from your last project? We all do it out of necessity. But after releasing v1.1.0, the scale of "boilerplate fatigue" became clear: &lt;strong&gt;over 4,000 developers&lt;/strong&gt; joined the journey in just one month.&lt;/p&gt;

&lt;p&gt;That growth sparked a realization: we needed more than just a template. Previously known as &lt;strong&gt;nodejs-quickstart-structure&lt;/strong&gt;, we have officially evolved into a full-scale Generator in v2.0.0.&lt;/p&gt;

&lt;p&gt;I built this tool to help ourselves: &lt;strong&gt;Why can't our starting points be enterprise-ready by default?&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🎥 The Evolution: From CLI (v1.1.0) to Web UI (v2.0.0)
&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%2F91vv5s809rqmznkvny5r.gif" 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%2F91vv5s809rqmznkvny5r.gif" alt=" " width="400" height="294"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;How it started: A powerful CLI that 4,000 of you loved (v1.1.0).&lt;/em&gt;&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%2Ff7l88uefp8knkqs3iljw.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%2Ff7l88uefp8knkqs3iljw.png" alt=" " width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=Cxbb54T0uo8" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=Cxbb54T0uo8&lt;/a&gt;&lt;br&gt;
&lt;em&gt;How it’s going: A full-blown Web UI with 1:1 structure parity (v2.0.0).&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing: Node.js Quickstart Generator 2.0.0
&lt;/h2&gt;

&lt;p&gt;I built this tool to solve my own frustration: &lt;strong&gt;Why can't I just have a professional-grade starting point in one command?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ What’s in the Box?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flexible Architecture&lt;/strong&gt;: Toggle between &lt;strong&gt;MVC&lt;/strong&gt; and &lt;strong&gt;Clean Architecture&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern Stack&lt;/strong&gt;: Pick &lt;strong&gt;JavaScript&lt;/strong&gt; or &lt;strong&gt;TypeScript&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communication Protocols&lt;/strong&gt;: Built-in support for &lt;strong&gt;REST&lt;/strong&gt;, &lt;strong&gt;GraphQL (Apollo)&lt;/strong&gt;, and &lt;strong&gt;Kafka&lt;/strong&gt; (Event-driven).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching Layer&lt;/strong&gt;: Pre-configured &lt;strong&gt;Redis&lt;/strong&gt; or In-memory cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise Standards&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Multi-stage Dockerfiles.&lt;/li&gt;
&lt;li&gt;Snyk &amp;amp; SonarCloud security hardening.&lt;/li&gt;
&lt;li&gt;Global error handling (ApiError, NotFoundError, etc.).&lt;/li&gt;
&lt;li&gt;Jest &amp;amp; Supertest with &lt;strong&gt;80% unit test coverage&lt;/strong&gt; out of the box.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  🌐 Next-Gen Web UI Configurator (v2.0.0)
&lt;/h3&gt;

&lt;p&gt;Type no more! We have completely evolved the configuration experience in our new &lt;a href="https://paudang.github.io/nodejs-quickstart-structure/" rel="noopener noreferrer"&gt;Web UI Configurator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It includes a &lt;strong&gt;Real-time Folder Simulation&lt;/strong&gt;. As you toggle between MVC and Clean Architecture, you see exactly what the project will look like. Once you're happy, just copy the &lt;strong&gt;Zero-Prompt CLI command&lt;/strong&gt; and run it in your terminal. No more prompts!&lt;/p&gt;




&lt;h3&gt;
  
  
  🦾 AI-Native Foundation
&lt;/h3&gt;

&lt;p&gt;We designed v2.0.0 for the future. If you use &lt;strong&gt;Cursor&lt;/strong&gt; or other AI coding agents, our built-in &lt;code&gt;.cursorrules&lt;/code&gt; will make your AI assistant 10x smarter about your specific architecture. No more hallucinations about where controllers or repos belong!&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 Get Started NOW
&lt;/h3&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. No global install required.&lt;/p&gt;




&lt;h3&gt;
  
  
  🗺️ Road to 10k Downloads
&lt;/h3&gt;

&lt;p&gt;We just crossed &lt;strong&gt;4,000+ downloads&lt;/strong&gt; and want to reach 10k by the end of the year. If you love open-source tools that actually save you time, please:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give us a ⭐ on &lt;strong&gt;&lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Share this with your team.&lt;/li&gt;
&lt;li&gt;Drop a comment below—what feature should we add next? (gRPC? NestJS-style?)&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>node</category>
      <category>opensource</category>
    </item>
    <item>
      <title>24 Hours of Chaos: Saving My Open Source Project from a Supply Chain Attack (plain-crypto-js)</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Wed, 01 Apr 2026 01:01:37 +0000</pubDate>
      <link>https://dev.to/paudang/24-hours-of-chaos-saving-my-open-source-project-from-a-supply-chain-attack-dm8</link>
      <guid>https://dev.to/paudang/24-hours-of-chaos-saving-my-open-source-project-from-a-supply-chain-attack-dm8</guid>
      <description>&lt;p&gt;Hello world, &lt;/p&gt;

&lt;p&gt;I'm a Senior SE. Today, I want to share a "battle-tested" experience that just happened to my open-source project: &lt;strong&gt;nodejs-quickstart-structure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This isn't just about code; it’s a lesson in &lt;strong&gt;Incident Response&lt;/strong&gt; when facing professional malware designed to hijack npm, GitHub, and sensitive developer credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Threat: Axios &amp;amp; plain-crypto-js
&lt;/h2&gt;

&lt;p&gt;While developing version &lt;strong&gt;v2.0.0&lt;/strong&gt;, I fell victim to a &lt;strong&gt;Typosquatting&lt;/strong&gt; attack. A malicious package or a "shell" dependency injected malware into my local environment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Suspect:&lt;/strong&gt; Linked to the &lt;code&gt;plain-crypto-js&lt;/code&gt; incident (a malware variant targeting devs using Axios).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Behavior:&lt;/strong&gt; It didn't just break my system; it silently exfiltrated:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser Cookies:&lt;/strong&gt; Hijacking active sessions for Gmail, GitHub, and LinkedIn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH Keys:&lt;/strong&gt; Gaining unauthorized access to push code to repositories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm Tokens:&lt;/strong&gt; Attempting to publish malicious releases under my name.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. 0h00: Detection &amp;amp; Containment
&lt;/h2&gt;

&lt;p&gt;Immediately after noticing suspicious logs and file modifications, I followed the "Security Textbook" or you can check at &lt;a href="https://snyk.io/blog/axios-npm-package-compromised-supply-chain-attack-delivers-cross-platform" rel="noopener noreferrer"&gt;Axios npm Package Compromised: Supply Chain Attack Delivers Cross-Platform RAT&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Deleted Local Repos:&lt;/strong&gt; Wiped the execution environment of the malware.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Revoked All Sessions:&lt;/strong&gt; Used a clean device (mobile) to remotely sign out of Google, GitHub, Microsoft, and LinkedIn.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Untrusted Devices:&lt;/strong&gt; Removed my current machine from the "Trusted Devices" list of all critical accounts.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. The Battle for npm (The Support Battle)
&lt;/h2&gt;

&lt;p&gt;The worst-case scenario: The attacker hijacked the session and invalidated my 2FA (my stored Recovery Codes returned &lt;strong&gt;Invalid&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;I immediately contacted &lt;strong&gt;npm Support&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ticket ID: 4223695&lt;/strong&gt; was created.&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%2Fg8zbkud61jjc6erurqg8.jpeg" 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%2Fg8zbkud61jjc6erurqg8.jpeg" alt=" " width="750" height="1028"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Strategy:&lt;/strong&gt; Providing proof of ownership through my GitHub account (which I still control) and the project's long-standing commit history.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. The Decision: Eradication (Wipe &amp;amp; Rebuild)
&lt;/h2&gt;

&lt;p&gt;As an Architect, I know that if an OS is compromised by a Rootkit/Trojan, no antivirus can guarantee a 100% clean state. The only solution: &lt;strong&gt;Wipe &amp;amp; Rebuild&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Method:&lt;/strong&gt; Reset PC &amp;gt; Remove everything &amp;gt; &lt;strong&gt;Cloud download Windows&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why Cloud Download?&lt;/strong&gt; To ensure a fresh installation image directly from Microsoft, avoiding any malware lurking in the local Recovery partition.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Lessons Learned for Developers
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Dependency Vigilance:&lt;/strong&gt; Always double-check new packages, especially those with names similar to popular libraries.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;2FA is Not Enough:&lt;/strong&gt; Attackers can bypass 2FA via Session Hijacking. Always be ready to &lt;strong&gt;Revoke Sessions&lt;/strong&gt; remotely.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Offline Recovery Codes:&lt;/strong&gt; Don't just store them on your computer. Print them or use a decoupled password manager.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Incident Response Mindset:&lt;/strong&gt; When hacked, stay calm and follow: &lt;strong&gt;Containment -&amp;gt; Asset Protection -&amp;gt; Eradication -&amp;gt; Recovery.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Currently, I am in the process of restoring a "sterile" environment to finalize &lt;strong&gt;v2.0.0&lt;/strong&gt; for &lt;code&gt;nodejs-quickstart-structure&lt;/code&gt;. You can check out the &lt;strong&gt;v2.0.0&lt;/strong&gt; beta details here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://paudang.github.io/nodejs-quickstart-structure/guide/browser-generator.html" rel="noopener noreferrer"&gt;Next gen Web UI - Browser Generator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project will return with a higher security standard. I hope this story helps fellow developers protect their "digital children"!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>security</category>
    </item>
    <item>
      <title>How I Built a Node.js Generator with 1,680+ Combinations, Clean Architecture or MVC (Kafka)</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Thu, 26 Mar 2026 10:01:34 +0000</pubDate>
      <link>https://dev.to/paudang/how-i-built-a-nodejs-generator-with-1680-combinations-clean-architecture-or-mvc-kafka-38j9</link>
      <guid>https://dev.to/paudang/how-i-built-a-nodejs-generator-with-1680-combinations-clean-architecture-or-mvc-kafka-38j9</guid>
      <description>&lt;p&gt;Hi all,&lt;/p&gt;

&lt;p&gt;We’ve all been there: starting a new Node.js microservice from scratch. You spend 4 hours setting up Express, another 2 hours arguing over folder structure (MVC or Clean Architecture?), 3 hours configuring Prisma or Mongoose, and half a day trying to get a Kafka KRaft container to talk to your local app. &lt;/p&gt;

&lt;p&gt;By the time you're ready to write your first line of business logic, you're already exhausted.&lt;/p&gt;

&lt;p&gt;I decided to solve this once and for all. I built &lt;strong&gt;&lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;nodejs-quickstart-structure&lt;/a&gt;&lt;/strong&gt;—a CLI that doesn't just give you a "hello world," but scaffolds a production-ready engine tailored to your exact needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: The "Boilerplate exhaustion"
&lt;/h2&gt;

&lt;p&gt;Most generators give you a fixed stack. But in the real world, requirements vary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"This needs to be a simple MVC app."&lt;/li&gt;
&lt;li&gt;"This needs to be a Clean Architecture domain-driven service."&lt;/li&gt;
&lt;li&gt;"We need GraphQL, not REST."&lt;/li&gt;
&lt;li&gt;"We need Kafka for event-driven messaging."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: 1,680+ Scenarios in One CLI
&lt;/h2&gt;

&lt;p&gt;The core of this tool is its flexibility. By combining different technologies, it supports &lt;strong&gt;over 1,680 unique project combinations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Languages&lt;/strong&gt;: TypeScript (Recommended) / JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architectures&lt;/strong&gt;: MVC or Clean Architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Databases&lt;/strong&gt;: MySQL, PostgreSQL, MongoDB, or None.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communications&lt;/strong&gt;: REST APIs, GraphQL, or Kafka (Event-Driven).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: Redis or Memory Cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Hardened with Helmet, HPP, and automated Snyk/SonarCloud configs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD&lt;/strong&gt;: Ready-to-use workflows for GitHub Actions, GitLab CI, or Jenkins.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why "Clean Architecture"?
&lt;/h2&gt;

&lt;p&gt;For many projects, MVC starts fast but becomes a nightmare to test. I’ve implemented a robust Clean Architecture template where:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Domain&lt;/strong&gt;: Pure business logic (Entities/Use Cases).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure&lt;/strong&gt;: Database, Messaging (Kafka Client), Caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interfaces&lt;/strong&gt;: Controllers, Express Routes, GraphQL Resolvers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation ensures your business logic doesn't care if you're using MongoDB or PostgreSQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Native: Coding in 2026
&lt;/h2&gt;

&lt;p&gt;I realized that if the folder structure is standard, AI tools (like &lt;strong&gt;Cursor&lt;/strong&gt;, &lt;strong&gt;ChatGPT&lt;/strong&gt;, or &lt;strong&gt;Gemini&lt;/strong&gt;) can understand the codebase 10x faster. &lt;br&gt;
Every project generated includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.cursorrules&lt;/strong&gt;: Pre-configured rules so Cursor knows exactly how to add new features following your chosen architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;prompts/&lt;/strong&gt;: A library of "Agent Skills" to help you generate Use Cases or Repositories without breaking patterns.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;You don't even need to install it globally. Just run:&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 init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI will walk you through the setup. Within seconds, you'll have a project with &lt;strong&gt;80%+ unit test coverage&lt;/strong&gt;, &lt;strong&gt;ESLint/Prettier&lt;/strong&gt; configured, and a &lt;strong&gt;Docker Compose&lt;/strong&gt; file that actually works.&lt;/p&gt;

&lt;p&gt;Checking the official docs:&lt;br&gt;
👉 &lt;a href="https://paudang.github.io/nodejs-quickstart-structure/" rel="noopener noreferrer"&gt;Official Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out the repo and give it a ⭐ if it helps your workflow:&lt;br&gt;
👉 &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;GitHub: paudang/nodejs-quickstart-structure&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love to hear your feedback! What's the one thing you always struggle with when starting a new Node project? &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>microservices</category>
      <category>node</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Stop Writing Flaky Tests: The Ultimate Node.js Testing Strategy (Unit + E2E)</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Mon, 23 Mar 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/paudang/stop-writing-flaky-tests-the-ultimate-nodejs-testing-strategy-unit-e2e-4d25</link>
      <guid>https://dev.to/paudang/stop-writing-flaky-tests-the-ultimate-nodejs-testing-strategy-unit-e2e-4d25</guid>
      <description>&lt;p&gt;Hi DEV community,&lt;/p&gt;

&lt;p&gt;If you are a Backend Developer working with Node.js, you have likely experienced the dreaded scenario: &lt;strong&gt;"It passes on my machine, but randomly fails on the CI/CD pipeline."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This phenomenon is known as &lt;strong&gt;Flaky Tests&lt;/strong&gt;. It usually stems from writing End-to-End (E2E) tests that share database states across test files, or due to network and infrastructure services (Redis, Kafka) not being fully initialized when the test begins.&lt;/p&gt;

&lt;p&gt;Today, I’m going to share the complete testing architecture and lessons I learned while building the automation framework &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;nodejs-quickstart-structure&lt;/a&gt;. We will solve the core problem: &lt;strong&gt;How to build blazing fast Unit Tests and completely deterministic E2E Tests.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Crisis in 90% of Projects
&lt;/h2&gt;

&lt;p&gt;Many teams implement testing "half-heartedly":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests:&lt;/strong&gt; Dependencies like the Database or Redis aren't mocked, causing the tests to drag on because they wait for network I/O.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E2E Tests:&lt;/strong&gt; Developers use their local dev database to run E2E suites. Test A creates a User, Test B checks the total number of Users. If they run in a different order, the test suite explodes! Some even use conditionals in tests: &lt;em&gt;&lt;code&gt;if (statusCode == 404) expect(404) else expect(201)&lt;/code&gt;&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This is a massive Anti-pattern!&lt;/strong&gt; A test must strictly return exactly one predictable result based on static inputs.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The "Big Tech" Strategy: Draw a Hard Line
&lt;/h2&gt;

&lt;p&gt;To fix this, you must strictly delineate your boundaries:&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit Tests (Fast &amp;amp; Isolated)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; Verify Business Logic (Use cases, Services, Domain).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule:&lt;/strong&gt; MOCK EVERYTHING. No real database connections, no external APIs, no touching Redis or Kafka.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed:&lt;/strong&gt; Thousands of test cases should execute in less than 2 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  E2E Tests (Black-box &amp;amp; Automated Infra)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; Verify the entire request flow (Route -&amp;gt; Controller -&amp;gt; Usecase -&amp;gt; Repo -&amp;gt; DB -&amp;gt; Response).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule:&lt;/strong&gt; Use the &lt;strong&gt;REAL Database, Redis, and Kafka&lt;/strong&gt; (spinned up via isolated Docker Containers or Testcontainers).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Characteristics:&lt;/strong&gt; Data must be TRUNCATED/Teared down before or after each test suite to guarantee a "clean room" environment. It runs slower, but absolute correctness is guaranteed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. The Recipe for Perfect E2E Tests
&lt;/h2&gt;

&lt;p&gt;To prevent E2E tests from interfering with the developer's local development environment, follow these steps and source demo &lt;a href="https://github.com/paudang/nodejs-service-redis-kafka" rel="noopener noreferrer"&gt;nodejs-service-redis-kafka&lt;/a&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Fully isolate &lt;code&gt;jest.config.js&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Do not share your Unit test configurations with E2E. Create a dedicated &lt;code&gt;jest.e2e.config.js&lt;/code&gt; with a higher &lt;code&gt;testTimeout&lt;/code&gt; (e.g., 30 seconds to allow databases to boot).&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./jest.config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;testMatch&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;&amp;lt;rootDir&amp;gt;/tests/e2e/**/*.test.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;testPathIgnorePatterns&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;/node_modules/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;testTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;clearMocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Use Node.js Scripts to Manage Docker Lifecycle
&lt;/h3&gt;

&lt;p&gt;Instead of forcing developers to manually type &lt;code&gt;docker-compose up&lt;/code&gt; before running tests, write an automated orchestration script:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assign a dedicated port (&lt;code&gt;PORT=3001&lt;/code&gt; instead of &lt;code&gt;3000&lt;/code&gt;) to avoid Dev Server collisions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;execSync('docker-compose up -d db redis kafka')&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;wait-on&lt;/code&gt; npm package to &lt;strong&gt;poll the healthcheck&lt;/strong&gt; until dependencies are fully green.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm run test:e2e:run&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Clean up gracefully: &lt;code&gt;execSync('docker-compose down')&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wait for dependencies to prevent "Flaky connections"&lt;/span&gt;
&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`npx wait-on http-get://127.0.0.1:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TEST_PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/health -t 120000`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jest --config ./jest.e2e.config.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Fix Kafka's "read ECONNRESET" Locally
&lt;/h3&gt;

&lt;p&gt;The most common issue when testing Kafka in a local E2E run is that the tests run on the host network while Kafka is stuck in the Docker bridged network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Explicitly map the &lt;code&gt;PLAINTEXT_HOST&lt;/code&gt; listener to a dedicated port (e.g., &lt;code&gt;9093&lt;/code&gt;):&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="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;kafka&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9093:9093"&lt;/span&gt; &lt;span class="c1"&gt;# Host mapping&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;KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:9093&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,PLAINTEXT_HOST://:9093,CONTROLLER://:9094&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in your &lt;code&gt;.env.test&lt;/code&gt;, simply point &lt;code&gt;KAFKA_BROKER=localhost:9093&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Write "Iron-clad" Assertions
&lt;/h3&gt;

&lt;p&gt;Eliminate loose assertions. Because your database is wiped clean (or relies on random seeds like &lt;code&gt;Date.now()&lt;/code&gt;), the output status must be absolutely rigid!&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should create a user successfully via REST&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uniqueEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`test_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;@example.com`&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SERVER_URL&lt;/span&gt;&lt;span class="p"&gt;)&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;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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;Test User&lt;/span&gt;&lt;span class="dl"&gt;'&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;uniqueEmail&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Strictly expect a 201 Created&lt;/span&gt;
    &lt;span class="nf"&gt;expect&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="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&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. Conclusion
&lt;/h2&gt;

&lt;p&gt;Shifting to an isolated, automated Docker test strategy will cost you 1-2 days of initial setup infrastructure work. But in return, it brings &lt;strong&gt;absolute peace of mind&lt;/strong&gt; to the team as the codebase scales.&lt;/p&gt;

&lt;p&gt;If you find this setup process too tedious, you can simply grab the exact folder structures, Docker automation scripts, and Jest configurations that I’ve already pre-configured out of the box in my open-source CLI generator:&lt;br&gt;
👉 &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;nodejs-quickstart-structure on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drop a star (⭐) if you find it helpful! Happy coding, and here's to never seeing &lt;code&gt;Test Failed randomly&lt;/code&gt; in GitHub Actions again!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>Master Caching Patterns: A Clean Architecture Guide with AI-Native Tooling</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Thu, 19 Mar 2026 15:02:06 +0000</pubDate>
      <link>https://dev.to/paudang/master-caching-patterns-a-clean-architecture-guide-with-ai-native-tooling-p7j</link>
      <guid>https://dev.to/paudang/master-caching-patterns-a-clean-architecture-guide-with-ai-native-tooling-p7j</guid>
      <description>&lt;p&gt;In high-performance systems, caching is the ultimate "lifesaver" for reducing database load and optimizing response times. However, choosing the right pattern (Cache-Aside, Write-Through, etc.) depends heavily on your specific use case. In this article, I’ll walk you through a hands-on demo project that showcases 5 essential caching patterns, built with &lt;strong&gt;Clean Architecture&lt;/strong&gt; and the power of an AI-Native tool: &lt;strong&gt;nodejs-quickstart-structure&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&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%2Fe02fvwulm3sdwag3w0oy.png" alt=" " width="640" height="640"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🚀 Why Caching?
&lt;/h2&gt;

&lt;p&gt;When scaling applications, the database often becomes the bottleneck. Caching strategically places frequently accessed data in memory (like Redis) to bypass slow disk I/O. But not all caching is equal. To understand the nuances, I built a showcase service from scratch.&lt;/p&gt;

&lt;p&gt;To skip the boilerplate and focus on the patterns, I used &lt;a href="https://github.com/paudang/nodejs-quickstart-structure" rel="noopener noreferrer"&gt;nodejs-quickstart-structure&lt;/a&gt;, a CLI tool designed for AI-ready, structured development.&lt;/p&gt;




&lt;h2&gt;
  
  
  📂 5 Caching Patterns You Must Know
&lt;/h2&gt;

&lt;p&gt;Here is a breakdown of the 5 patterns implemented in this demo:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cache-Aside (Lazy Loading)
&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%2Fc2v6nge0uosjc2ljij9r.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%2Fc2v6nge0uosjc2ljij9r.png" alt=" " width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: The application manages the cache. Data is only loaded into the cache when specifically requested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to use&lt;/strong&gt;: Best for systems with &lt;strong&gt;Read &amp;gt;&amp;gt; Write&lt;/strong&gt; ratios (e.g., User Profiles).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;: Memory efficiency; cache only contains data that is actually needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Read-Through
&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%2Fix6mhsj30nhr3wi96hf3.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%2Fix6mhsj30nhr3wi96hf3.png" alt=" " width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: Offloads data-fetching logic to the Cache Provider. The app simply calls "Get" from the cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to use&lt;/strong&gt;: To keep the business logic (Use Case) clean and decouple from the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;: Extremely clean business code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Write-Through
&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%2F62niw25r5y4p49hscguz.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%2F62niw25r5y4p49hscguz.png" alt=" " width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: Data is written to both the Cache and the Database simultaneously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to use&lt;/strong&gt;: When &lt;strong&gt;Strong Consistency&lt;/strong&gt; is required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;: Minimal data inconsistency risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Write-Around
&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%2Furat61ubxcq86ujstqzi.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%2Furat61ubxcq86ujstqzi.png" alt=" " width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: Data is written directly to the DB, and the corresponding cache entry is invalidated (deleted).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to use&lt;/strong&gt;: When the written data might not be read again immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;: Prevents "polluting" the cache with data that won't be reused soon.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Write-Back (Write-Behind)
&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%2F5po6m7z4ljybcv4xxnol.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%2F5po6m7z4ljybcv4xxnol.png" alt=" " width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: Data is written to the Cache first; the DB update happens asynchronously in the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When to use&lt;/strong&gt;: For &lt;strong&gt;exceptionally high write performance&lt;/strong&gt; (e.g., Logging, Real-time Metrics).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pros&lt;/strong&gt;: Near-instant write response.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠️ Technical Implementation (Step-by-Step)
&lt;/h2&gt;

&lt;p&gt;Follow this roadmap to see how I build a production-ready demo using Clean Architecture:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Project Initialization
&lt;/h3&gt;

&lt;p&gt;Bootstrap the project using the AI-Native CLI:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Selection Guide:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project name: &lt;code&gt;nodejs-service-caching-pattern&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Architecture: &lt;code&gt;Clean Architecture&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Database: &lt;code&gt;MySQL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Caching: &lt;code&gt;Redis&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Communication: &lt;code&gt;REST APIs&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Defining Domain Interfaces
&lt;/h3&gt;

&lt;p&gt;Abstract the caching and repository logic to ensure true decoupling.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/domain/services/ICacheService.ts" rel="noopener noreferrer"&gt;ICacheService.ts&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ICacheService&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="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;key&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;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttl&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="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="k"&gt;void&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;getOrSet&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;key&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;fetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="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;ttl&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="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/domain/repositories/IUserRepository.ts" rel="noopener noreferrer"&gt;IUserRepository.ts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Infrastructure Layer
&lt;/h3&gt;

&lt;p&gt;Implementation for Redis and Sequelize (MySQL).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/infrastructure/caching/redisClient.ts" rel="noopener noreferrer"&gt;redisClient.ts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/infrastructure/repositories/UserRepository.ts" rel="noopener noreferrer"&gt;UserRepository.ts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Database Seeding: Generate 1,000 dummy users for demo purposes.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/flyway/sql/V20260319__seed_1000_users.sql" rel="noopener noreferrer"&gt;V20260319__seed_1000_users.sql&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Implementing the Use Cases
&lt;/h3&gt;

&lt;p&gt;This is where the pattern logic lives.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read-Through Example&lt;/strong&gt;:
&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/usecases/getUserInfoReadThrough.ts" rel="noopener noreferrer"&gt;getUserInfoReadThrough.ts&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The cache provider handles DB fetching upon miss&lt;/span&gt;
&lt;span class="k"&gt;return&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;cacheService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOrSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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="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="k"&gt;return&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;findById&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="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write-Back Example&lt;/strong&gt;:
&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/usecases/updateUserWriteBack.ts" rel="noopener noreferrer"&gt;updateUserWriteBack.ts&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Update cache immediately, DB updates in background&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;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;updatedUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&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="nf"&gt;asyncDatabaseUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check sample code at here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/usecases/createUserWriteThrough.ts" rel="noopener noreferrer"&gt;createUserWriteThrough.ts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/usecases/createUserWriteAround.ts" rel="noopener noreferrer"&gt;createUserWriteAround.ts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/usecases/updateUserWriteBack.ts" rel="noopener noreferrer"&gt;updateUserWriteBack.ts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. API &amp;amp; Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/interfaces/controllers/cacheDemoController.ts" rel="noopener noreferrer"&gt;cacheDemoController.ts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/paudang/nodejs-service-caching-pattern/blob/main/src/config/swagger.yml" rel="noopener noreferrer"&gt;swagger.yml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔍 How to Test &amp;amp; Verify
&lt;/h2&gt;

&lt;p&gt;Once deployed, you can trigger these endpoints to verify the internal logic:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Test Cache-Aside / Read-Through (Read Path)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First Call (Cold Cache)&lt;/strong&gt;: &lt;code&gt;curl -X GET http://localhost:3000/api/demo/cache-aside/1&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Internal:&lt;/em&gt; App checks Redis (Miss) -&amp;gt; Queries MySQL -&amp;gt; Updates Redis -&amp;gt; Returns.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Subsequent Call (Warm Cache)&lt;/strong&gt;: Repeat the command.

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Internal:&lt;/em&gt; App checks Redis (Hit) -&amp;gt; Returns immediately with zero DB overhead.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Test Write-Through (Sync Write)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt;: &lt;code&gt;curl -X POST http://localhost:3000/api/demo/write-through -H "Content-Type: application/json" -d '{"name": "Performance", "email": "perf@test.com"}'&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Internal:&lt;/em&gt; Service writes to MySQL AND Redis simultaneously.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Test Write-Around (Clean Write)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt;: &lt;code&gt;curl -X POST http://localhost:3000/api/demo/write-around -H "Content-Type: application/json" -d '{"name": "Guest", "email": "guest@test.com"}'&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Internal:&lt;/em&gt; Writes to MySQL, then calls &lt;code&gt;DEL&lt;/code&gt; to invalidate the Redis key. The next read will be a Miss to load the fresh data.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Test Write-Back (Async Write)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt;: &lt;code&gt;curl -X PUT http://localhost:3000/api/demo/write-back/1 -H "Content-Type: application/json" -d '{"name": "Fast Update"}'&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Internal:&lt;/em&gt; Updates Redis immediately (super fast response). The DB update happens after a short delay asynchronously.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Final Thoughts on AI-Native Tooling
&lt;/h2&gt;

&lt;p&gt;What makes &lt;code&gt;nodejs-quickstart-structure&lt;/code&gt; special is not just the speed of initialization, but its &lt;strong&gt;AI-Native&lt;/strong&gt; nature. With &lt;code&gt;.cursorrules&lt;/code&gt; and pre-built prompts, it ensures that your project maintains Clean Architecture and consistent coding standards automatically from development to production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Source Code&lt;/strong&gt;: &lt;a href="https://github.com/paudang/nodejs-service-caching-pattern" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I hope this guide helps you choose the right caching pattern for your next project&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>redis</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Why Your Docker Container Works on Windows but Fails on Linux: The Case-Sensitive Naming Nightmare</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/paudang/why-your-docker-container-works-on-windows-but-fails-on-linux-the-case-sensitive-naming-nightmare-422l</link>
      <guid>https://dev.to/paudang/why-your-docker-container-works-on-windows-but-fails-on-linux-the-case-sensitive-naming-nightmare-422l</guid>
      <description>&lt;p&gt;Hi Everyone,&lt;/p&gt;

&lt;p&gt;Have you ever faced that frustrating moment: Your code runs perfectly on your local machine (Windows/macOS), but the moment you containerize it with Docker or deploy it to a Linux server, everything breaks with a cryptic &lt;code&gt;Module not found&lt;/code&gt; error?&lt;/p&gt;

&lt;p&gt;The culprit usually isn't your logic—it’s a "sweet lie" told by your operating system.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. The Kernel-Level Reality Check
&lt;/h3&gt;

&lt;p&gt;The trouble starts with how different Operating Systems handle their &lt;strong&gt;File Systems&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows (NTFS):&lt;/strong&gt; Microsoft prioritizes user convenience. To Windows, &lt;code&gt;User.service.ts&lt;/code&gt; and &lt;code&gt;user.service.ts&lt;/code&gt; are semantically the same. Therefore, NTFS is designed to be &lt;strong&gt;Case-Insensitive&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux (Ext4/XFS):&lt;/strong&gt; The philosophy of Linux (and Unix) is absolute precision. In ASCII, 'U' (65) and 'u' (117) are two completely different entities. Linux treats &lt;code&gt;User.ts&lt;/code&gt; and &lt;code&gt;user.ts&lt;/code&gt; as two distinct files that can coexist in the same folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The Docker "Trap" on Local Machines
&lt;/h3&gt;

&lt;p&gt;Docker on Windows or macOS doesn't actually run directly on those kernels. It runs inside a &lt;strong&gt;Lightweight Linux VM&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you &lt;code&gt;mount&lt;/code&gt; (bind mount) your code from a Windows host (Case-Insensitive) into that Linux VM (Case-Sensitive):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Mistake:&lt;/strong&gt; You name your file &lt;code&gt;UserMapper.ts&lt;/code&gt; but write &lt;code&gt;import { UserMapper } from './usermapper'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Local Pass:&lt;/strong&gt; Windows tells the engine: &lt;em&gt;"Sure, I know what you mean, here is the file."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Docker Crash:&lt;/strong&gt; The Linux environment inside Docker yells: &lt;em&gt;"I don't see any file named usermapper (lowercase) here!"&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Result:&lt;/strong&gt; Your system crashes at runtime or fails the build process immediately.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Vaccinating Your Node.js Projects
&lt;/h3&gt;

&lt;p&gt;Instead of relying on human memory, we should use tools to enforce consistency. In my Open Source project, &lt;strong&gt;nodejs-quickstart-structure&lt;/strong&gt;, I’ve implemented a 3-layer defense system:&lt;/p&gt;

&lt;h4&gt;
  
  
  Layer 1: ESLint Enforcement (Stop it at the source)
&lt;/h4&gt;

&lt;p&gt;We use the &lt;code&gt;eslint-plugin-import&lt;/code&gt; plugin to flag errors directly in VS Code if your import path doesn't match the actual filename 100%.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"import/no-unresolved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Layer 2: CI/CD Pipeline (The Ultimate Gatekeeper)
&lt;/h4&gt;

&lt;p&gt;Our GitHub Actions run on Ubuntu (Linux). If you accidentally push a naming mismatch, the pipeline will fail during the test suite, preventing broken code from ever reaching Production.&lt;/p&gt;

&lt;h4&gt;
  
  
  Layer 3: Standardized Naming Conventions
&lt;/h4&gt;

&lt;p&gt;Stick to a strict naming convention across the entire project. Whether it's kebab-case or PascalCase, consistency is your best friend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;Linux environments don't create bugs; they simply expose the inconsistencies that Windows hides from you. Synchronizing your environment from Local to Production is a hallmark of a Senior Developer.&lt;/p&gt;

&lt;p&gt;If you’re looking for a Node.js boilerplate that is pre-configured to be "immune" to these environment issues (along with Kafka, Redis, and Clean Architecture), feel free to check out my project:&lt;/p&gt;

&lt;p&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;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;NPM&lt;/strong&gt;: &lt;code&gt;npx nodejs-quickstart-structure init&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I hope this saves you a few hours of meaningless debugging! Feel free to drop a ⭐ if you find the project helpful!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>docker</category>
      <category>linux</category>
      <category>node</category>
    </item>
    <item>
      <title>15 Minutes to "Ship It": (Clean Architecture + REST API + Kafka + Docker &amp; CI/CD) From Zero to Production with Node.js</title>
      <dc:creator>Pau Dang</dc:creator>
      <pubDate>Mon, 16 Mar 2026 02:02:12 +0000</pubDate>
      <link>https://dev.to/paudang/15-minutes-to-ship-it-from-zero-to-production-with-nodejs-clean-architecture-rest-api--3l7b</link>
      <guid>https://dev.to/paudang/15-minutes-to-ship-it-from-zero-to-production-with-nodejs-clean-architecture-rest-api--3l7b</guid>
      <description>&lt;p&gt;Starting a new Node.js project often involves tedious repetitive tasks: scaffolding directory structures, setting up Express, configuring database connections, managing migrations, and integrating messaging systems like Kafka. This "boilerplate phase" can eat up hours of your initial development time.&lt;/p&gt;

&lt;p&gt;Today, I’ll show you how to go &lt;strong&gt;from zero to a production-ready environment&lt;/strong&gt; in minutes. We will build a high-performance Node.js service using &lt;strong&gt;Clean Architecture&lt;/strong&gt;, &lt;strong&gt;TypeScript&lt;/strong&gt;, &lt;strong&gt;MySQL&lt;/strong&gt;, &lt;strong&gt;Flyway&lt;/strong&gt; for database migrations, &lt;strong&gt;Kafka&lt;/strong&gt; for real-time event-driven messaging, &lt;strong&gt;Docker Compose&lt;/strong&gt; for orchestration, and &lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/CD.&lt;/p&gt;

&lt;p&gt;Let’s dive in!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎯 &lt;strong&gt;"Ready-to-Run" Source Code for You:&lt;/strong&gt;&lt;br&gt;
Instead of manually copying snippets, I’ve packaged the entire source code for this article into a production-grade template on GitHub. This project has already reached &lt;strong&gt;3,000+ downloads&lt;/strong&gt; and is being used by developers for real-world services.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;&lt;a href="https://github.com/paudang/nodejs-clean-rest-kafka" rel="noopener noreferrer"&gt;Repo: paudang/nodejs-clean-rest-kafka&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Just &lt;code&gt;git clone&lt;/code&gt;, run &lt;code&gt;docker-compose up -d&lt;/code&gt;, and you're live!)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 1: Initialize Project &amp;amp; Install Dependencies
&lt;/h2&gt;

&lt;p&gt;First, let's create the project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;nodejs-clean-rest-kafka
&lt;span class="nb"&gt;cd &lt;/span&gt;nodejs-clean-rest-kafka
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the essential production libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;express cors helmet hpp express-rate-limit dotenv morgan kafkajs sequelize mysql2 winston
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install development dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript @types/node @types/express @types/cors @types/morgan ts-node tsconfig-paths tsc-alias jest ts-jest @types/jest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize &lt;code&gt;tsconfig.json&lt;/code&gt; with Path Aliases (&lt;code&gt;@/*&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es2020"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commonjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rootDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Architecting for Scale (Clean Architecture) 🏗️
&lt;/h2&gt;

&lt;p&gt;Using &lt;strong&gt;Clean Architecture&lt;/strong&gt; ensures your codebase remains decoupled and highly testable. We divide the source code into distinct layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; src/domain src/usecases src/interfaces src/infrastructure src/utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;src/domain&lt;/strong&gt;: Core business entities and logic (framework-independent).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/usecases&lt;/strong&gt;: Application-specific business rules (The "Interactors").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/interfaces&lt;/strong&gt;: Adapters such as Controllers and HTTP Routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/infrastructure&lt;/strong&gt;: Technical details like Database connections, Kafka clients, and Loggers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/utils&lt;/strong&gt;: Shared utility functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation allows you to swap your database or framework without touching the core business logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Event-Driven Messaging with Kafka 🚀
&lt;/h2&gt;

&lt;p&gt;In a microservices architecture, Kafka acts as the "heartbeat" for asynchronous communication. We’ll build a &lt;strong&gt;KafkaService&lt;/strong&gt; using the &lt;strong&gt;Connection Promise&lt;/strong&gt; pattern to ensure the Producer is fully connected before sending messages, preventing data loss during startup.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;src/infrastructure/messaging/kafkaClient.ts&lt;/code&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Kafka&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Producer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kafkajs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&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;KafkaService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Producer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;isConnected&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;private&lt;/span&gt; &lt;span class="nx"&gt;connectionPromise&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="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kafka&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;Kafka&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;clientId&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-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;brokers&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;localhost:9092&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;producer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// "Connection Promise" ensures we only connect once efficiently&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connectionPromise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;connectionPromise&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;connectionPromise&lt;/span&gt; &lt;span class="o"&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="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;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&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;isConnected&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[Kafka] Producer connected successfully&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="k"&gt;return&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;connectionPromise&lt;/span&gt;&lt;span class="p"&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;sendEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&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;action&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;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Always wait for readiness&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;producer&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="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ts&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;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Kafka] Triggered &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topic&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kafkaService&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;KafkaService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3.5: Scaling Consumers with Clean Interfaces 🛠️
&lt;/h2&gt;

&lt;p&gt;Managing dozens of event types can quickly become messy. We use &lt;strong&gt;Abstract Base Classes&lt;/strong&gt; and &lt;strong&gt;Schema Validation&lt;/strong&gt; to keep consumers organized:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. BaseConsumer (The Blueprint)
&lt;/h3&gt;

&lt;p&gt;At &lt;code&gt;src/interfaces/messaging/baseConsumer.ts&lt;/code&gt;, we define a template for all consumers. It handles JSON parsing and error logging, so subclasses can focus solely on business logic:&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;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseConsumer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="nx"&gt;topic&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="kd"&gt;abstract&lt;/span&gt; &lt;span class="nf"&gt;handle&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;unknown&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="k"&gt;void&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;async&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;EachMessagePayload&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;rawValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&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;rawValue&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawValue&lt;/span&gt;&lt;span class="p"&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="nf"&gt;handle&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="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;
  
  
  2. Schema Validation (The Contract)
&lt;/h3&gt;

&lt;p&gt;Using &lt;strong&gt;Zod&lt;/strong&gt; at &lt;code&gt;src/interfaces/messaging/schemas/userEventSchema.ts&lt;/code&gt;, we define the contract between Producer and Consumer. This ensures type safety across the wire.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. WelcomeEmailConsumer (The Implementation)
&lt;/h3&gt;

&lt;p&gt;Logic that triggers an email when a &lt;code&gt;USER_CREATED&lt;/code&gt; event is received:&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;class&lt;/span&gt; &lt;span class="nc"&gt;WelcomeEmailConsumer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseConsumer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-topic&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;handle&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;unknown&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserEventSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;result&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;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USER_CREATED&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Kafka] 📧 Sending welcome email to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&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;payload&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="s2"&gt;...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Database Version Control with Flyway
&lt;/h2&gt;

&lt;p&gt;Manual database changes are a nightmare in production. &lt;strong&gt;Flyway&lt;/strong&gt; manages your schema versioning through simple SQL files.&lt;/p&gt;

&lt;p&gt;Example &lt;code&gt;V1__Create_Users_Table.sql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Clean UseCases &amp;amp; Controllers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. UseCase (The Interactor)
&lt;/h3&gt;

&lt;p&gt;Decoupled logic at &lt;code&gt;src/usecases/createUser.ts&lt;/code&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUserUseCase&lt;/span&gt; &lt;span class="o"&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;name&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;email&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="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;user&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;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;name&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="c1"&gt;// Repository abstraction&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kafkaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendEvent&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-events&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;USER_CREATED&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;id&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;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;user&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;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Controller (Interface Layer)
&lt;/h3&gt;

&lt;p&gt;The Controller's sole task is to receive requests, call use cases, and return responses. Very clean!&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;import&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;Response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createUserUseCase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/usecases/createUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUser&lt;/span&gt; &lt;span class="o"&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;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="o"&gt;=&amp;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;name&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="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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUserUseCase&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;email&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;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&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;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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&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;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&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;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;Internal Server Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Docker for Production Excellence
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;Dockerfile&lt;/code&gt; (Multi-stage Build)
&lt;/h3&gt;

&lt;p&gt;We separate the build environment from the runtime to minimize image size and attack surface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NPM_CONFIG_UPDATE_NOTIFIER=false&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;docker-compose.yml&lt;/code&gt; (The Full Stack)
&lt;/h3&gt;

&lt;p&gt;One command to rule them all:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;kafka&lt;/span&gt;&lt;span class="pi"&gt;]&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;KAFKA_BROKER=kafka:29092&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_HOST=db&lt;/span&gt;
  &lt;span class="na"&gt;db&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;mysql:8.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3306:3306"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./flyway/sql:/docker-entrypoint-initdb.d"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;kafka&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;confluentinc/cp-kafka:7.4.0&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;zookeeper&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9092:9092"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  One Last Surprise... 🤫
&lt;/h2&gt;

&lt;p&gt;Think this took hours to set up?&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;The Truth Is:&lt;/strong&gt; The entire project structure—Clean Architecture, Kafka Producers/Consumers, Flyway migrations, Docker configs, and CI/CD pipelines—was generated in &lt;strong&gt;under 60 seconds&lt;/strong&gt; using an automation tool I built.&lt;/p&gt;

&lt;p&gt;Time-to-market is everything. Stop reinventing the wheel and start shipping business value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to generate a "perfect" Node.js repo like this yourself? Try my CLI tool:&lt;/strong&gt;&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 init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the full document here: &lt;a href="https://dev.to/paudang/stop-wasting-time-on-boilerplate-generate-production-ready-nodejs-apps-in-1-minute-n1p"&gt;Nodejs Quickstart Structure&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building production-ready software shouldn't be a chore. I hope this helps you ship your next big idea faster than ever. If you found this useful, don't forget to give a Star ⭐ on GitHub! 🔥&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>microservices</category>
      <category>kafka</category>
    </item>
  </channel>
</rss>
