<?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>Forem: Erick Engelhardt</title>
    <description>The latest articles on Forem by Erick Engelhardt (@erickengelhardt).</description>
    <link>https://forem.com/erickengelhardt</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%2F1396610%2Fc03f5868-d247-4a7f-a593-d3cb358de079.png</url>
      <title>Forem: Erick Engelhardt</title>
      <link>https://forem.com/erickengelhardt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/erickengelhardt"/>
    <language>en</language>
    <item>
      <title>Scaling Your Node.js App: An Introduction to Clustering</title>
      <dc:creator>Erick Engelhardt</dc:creator>
      <pubDate>Fri, 13 Jun 2025 19:04:00 +0000</pubDate>
      <link>https://forem.com/erickengelhardt/scaling-your-nodejs-app-an-introduction-to-clustering-ff7</link>
      <guid>https://forem.com/erickengelhardt/scaling-your-nodejs-app-an-introduction-to-clustering-ff7</guid>
      <description>&lt;p&gt;I’ve created a full example repo you can clone and experiment with:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/erickne/node-cluster" rel="noopener noreferrer"&gt;https://github.com/erickne/node-cluster&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;When you first spin up a Node.js server, everything runs in a single thread — meaning your app can only take advantage of &lt;em&gt;one CPU core&lt;/em&gt; at a time. That’s great for simplicity, but... not so great when your users (and their requests) start piling up. 🧑‍💻➡️💥&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enter Node.js Clustering&lt;/strong&gt; — a simple but powerful way to scale your app horizontally on a single machine by forking multiple processes that share the same server port.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Does Node Work Under the Hood?
&lt;/h3&gt;

&lt;p&gt;Before we dive deeper, it helps to understand a key part of Node’s architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js runs on &lt;strong&gt;Google’s V8 JavaScript engine&lt;/strong&gt; — single-threaded for JS execution.&lt;/li&gt;
&lt;li&gt;It uses a &lt;strong&gt;libuv-based event loop&lt;/strong&gt; to handle I/O operations asynchronously.&lt;/li&gt;
&lt;li&gt;However, &lt;em&gt;CPU-bound&lt;/em&gt; operations and &lt;em&gt;high concurrency&lt;/em&gt; can become bottlenecks — since your one event loop can only do so much.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When your app is bound by the CPU (e.g. doing data processing) or by the sheer number of concurrent users, this single-threaded model can start showing its limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Cluster?
&lt;/h3&gt;

&lt;p&gt;🟢 &lt;strong&gt;Motivation:&lt;/strong&gt; most modern CPUs are multi-core. Without clustering, you’re wasting a lot of potential.&lt;/p&gt;

&lt;p&gt;By using Node’s built-in &lt;a href="https://nodejs.org/api/cluster.html" rel="noopener noreferrer"&gt;&lt;code&gt;cluster&lt;/code&gt;&lt;/a&gt; module, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fork multiple Node.js processes (workers), one per CPU core.&lt;/li&gt;
&lt;li&gt;Have these workers share the same server port (via the parent/master process).&lt;/li&gt;
&lt;li&gt;Handle significantly more load by distributing incoming requests across these workers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;Utilize full CPU capacity&lt;/strong&gt;&lt;br&gt;
✅ &lt;strong&gt;Higher concurrency&lt;/strong&gt;&lt;br&gt;
✅ &lt;strong&gt;Increased resiliency&lt;/strong&gt; (if a worker crashes, others can continue serving requests)&lt;br&gt;
✅ &lt;strong&gt;Zero major code rewrites&lt;/strong&gt; required to enable clustering&lt;/p&gt;

&lt;h3&gt;
  
  
  Risks &amp;amp; Tradeoffs
&lt;/h3&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Increased complexity&lt;/strong&gt; (managing worker lifecycle, IPC communication)&lt;br&gt;
⚠️ &lt;strong&gt;More memory usage&lt;/strong&gt; (each worker is a full Node process)&lt;br&gt;
⚠️ &lt;strong&gt;Not a silver bullet&lt;/strong&gt; — clustering won’t fix poorly optimized code or non-scalable architecture.&lt;/p&gt;

&lt;h1&gt;
  
  
  When Should You Use Clustering?
&lt;/h1&gt;

&lt;p&gt;Now that we know &lt;em&gt;what&lt;/em&gt; clustering is and &lt;em&gt;why&lt;/em&gt; it exists, let’s talk about the big question:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Should you actually use it in your app?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spoiler: &lt;strong&gt;it depends&lt;/strong&gt;. 😅&lt;/p&gt;

&lt;h3&gt;
  
  
  Good Use Cases for Clustering
&lt;/h3&gt;

&lt;p&gt;✅ &lt;strong&gt;CPU-Intensive Workloads&lt;/strong&gt;&lt;br&gt;
If your app does heavy data processing (crypto, image resizing, report generation), clustering helps distribute this across multiple cores.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;High Traffic APIs&lt;/strong&gt;&lt;br&gt;
For APIs with lots of concurrent requests, clustering helps prevent your single event loop from becoming a bottleneck.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Web Servers&lt;/strong&gt;&lt;br&gt;
Apps built with frameworks like Express or Fastify often benefit a lot — since HTTP request handling can be easily parallelized.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Better Fault Tolerance&lt;/strong&gt;&lt;br&gt;
If one worker crashes, the others stay alive — improving your app’s availability.&lt;/p&gt;




&lt;h3&gt;
  
  
  When NOT to Use Clustering
&lt;/h3&gt;

&lt;p&gt;❌ &lt;strong&gt;Lightweight or Low Traffic Apps&lt;/strong&gt;&lt;br&gt;
If your app runs fine with a single thread and serves a small number of users, clustering might just add unnecessary complexity.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Apps Already Behind a Load Balancer&lt;/strong&gt;&lt;br&gt;
If you’re running your app behind an external load balancer (Nginx, AWS ALB, Kubernetes, etc), clustering inside Node may not be needed — your infra is already distributing the load across multiple instances.&lt;/p&gt;

&lt;p&gt;❌ &lt;strong&gt;Apps With Shared In-Memory State&lt;/strong&gt;&lt;br&gt;
If your app relies heavily on shared state (like an in-memory cache or user session object), clustering can cause consistency issues — each worker is a separate process with its own memory space.&lt;/p&gt;

&lt;p&gt;📝 &lt;em&gt;Tip:&lt;/em&gt; If you still want to cluster and need shared state, you’ll need to externalize it (Redis, database, etc).&lt;/p&gt;




&lt;h3&gt;
  
  
  Rule of Thumb
&lt;/h3&gt;

&lt;p&gt;If your app is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I/O-bound → maybe you don’t need clustering.&lt;/li&gt;
&lt;li&gt;CPU-bound → clustering will likely help.&lt;/li&gt;
&lt;li&gt;Memory-sensitive → clustering will increase memory usage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Basic Example of Clustering in Node.js
&lt;/h1&gt;

&lt;p&gt;The beauty of Node.js is that clustering is built right into the standard library. No extra dependencies required — you can use the &lt;code&gt;cluster&lt;/code&gt; module and the &lt;code&gt;os&lt;/code&gt; module to spin up workers across your CPU cores.&lt;/p&gt;

&lt;p&gt;Let’s see it in action. 👇&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple Cluster Example
&lt;/h3&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="nx"&gt;cluster&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;cluster&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="nx"&gt;os&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;os&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="nx"&gt;http&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;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numCPUs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPrimary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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;`Primary process &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;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is running`&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;`Forking &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;numCPUs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; workers...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Fork workers.&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;numCPUs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional: listen for worker exits&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;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;`Worker &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; exited. Spawning a new one.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="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;// Workers can share any TCP connection&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Handled by worker &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;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&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;`Worker &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;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; started`&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;
  
  
  How It Works:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;primary process&lt;/strong&gt; checks &lt;code&gt;cluster.isPrimary === true&lt;/code&gt; and forks &lt;code&gt;numCPUs&lt;/code&gt; workers.&lt;/li&gt;
&lt;li&gt;Each &lt;strong&gt;worker&lt;/strong&gt; starts an HTTP server on port &lt;code&gt;3000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The OS automatically load-balances incoming connections across the workers.&lt;/li&gt;
&lt;li&gt;If a worker dies, the primary process forks a new one (self-healing).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Running It
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Frlmfmx23yf96tvocgld1.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%2Frlmfmx23yf96tvocgld1.png" alt="Multiple workers based on CPUs" width="791" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now when you hit &lt;code&gt;http://localhost:3000&lt;/code&gt;, different requests will be handled by different worker processes.&lt;/p&gt;

&lt;p&gt;👉 You can test this by refreshing the page multiple times — you should see different &lt;code&gt;process.pid&lt;/code&gt; values in the response.&lt;/p&gt;

&lt;h1&gt;
  
  
  Clustering an Express App
&lt;/h1&gt;

&lt;p&gt;Many Node.js apps use &lt;strong&gt;Express&lt;/strong&gt; (or Fastify, or Koa) as the web framework.&lt;br&gt;
Good news: the clustering approach is the same — just wrap your existing app inside the worker block. ✌️&lt;/p&gt;




&lt;h3&gt;
  
  
  Example: Clustered Express App
&lt;/h3&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="nx"&gt;cluster&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;cluster&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="nx"&gt;os&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;os&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="nx"&gt;express&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numCPUs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPrimary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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;`Primary process &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;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is running`&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;`Forking &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;numCPUs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; workers...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;numCPUs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;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;`Worker &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; exited. Restarting...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;app&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;/&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Hello from worker &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;pid&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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;`Worker &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;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; listening on port 3000`&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;
  
  
  Running It
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start-express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Changed?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Instead of using &lt;code&gt;http.createServer&lt;/code&gt;, each worker runs its own Express app.&lt;/li&gt;
&lt;li&gt;The primary process still manages worker lifecycle.&lt;/li&gt;
&lt;li&gt;Load balancing happens at the OS level — you don’t have to change your app logic.&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%2Fibmgydwtwptpzwb4fbxc.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%2Fibmgydwtwptpzwb4fbxc.png" alt="Multiple workers in an Express application" width="768" height="546"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Why It Works
&lt;/h3&gt;

&lt;p&gt;👉 Each worker is a separate process with its own event loop and memory.&lt;br&gt;
👉 The OS handles distributing incoming connections to available workers (via the &lt;code&gt;cluster&lt;/code&gt; module’s internal use of shared socket).&lt;/p&gt;




&lt;h3&gt;
  
  
  Gotchas
&lt;/h3&gt;

&lt;p&gt;⚠️ &lt;strong&gt;No shared in-memory state&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you store sessions or in-memory cache in the app, this won’t be shared between workers.&lt;/li&gt;
&lt;li&gt;Use Redis / external store if you need shared state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;More memory usage&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each worker is a full Node process → watch your server memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Graceful shutdown is important&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you deploy new versions, make sure to gracefully shut down workers to avoid dropping connections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a nice &lt;strong&gt;section 5️⃣ Conclusion + repo link block&lt;/strong&gt; you can drop at the end of the post:&lt;/p&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Clustering is one of those &lt;strong&gt;"easy to start, powerful to master"&lt;/strong&gt; tools in the Node.js ecosystem.&lt;/p&gt;

&lt;p&gt;👉 If your app needs to scale beyond a single CPU core, clustering can help you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve throughput&lt;/li&gt;
&lt;li&gt;Increase resiliency&lt;/li&gt;
&lt;li&gt;Maximize your server’s hardware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 If your app is already distributed (via containers, load balancers, Kubernetes), clustering might be unnecessary — or even redundant.&lt;/p&gt;

&lt;p&gt;👉 Remember: clustering doesn’t solve performance issues in your app itself — it helps your &lt;em&gt;good app&lt;/em&gt; scale further.&lt;/p&gt;




&lt;h3&gt;
  
  
  📦 Full Example Repository
&lt;/h3&gt;

&lt;p&gt;I’ve created a full example repo you can clone and experiment with:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/erickne/node-cluster" rel="noopener noreferrer"&gt;https://github.com/erickne/node-cluster&lt;/a&gt; 🚀&lt;/p&gt;

&lt;p&gt;📂 Includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple HTTP server cluster&lt;/li&gt;
&lt;li&gt;Express app cluster&lt;/li&gt;
&lt;li&gt;Graceful shutdown pattern (bonus)&lt;/li&gt;
&lt;li&gt;Minimal &amp;amp; production-friendly setup&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That’s it — happy scaling!&lt;br&gt;
If you enjoyed the post, give the repo a ⭐ and share it with your fellow devs.&lt;/p&gt;

&lt;p&gt;And as always: &lt;strong&gt;don’t cluster just for the sake of it — cluster smart.&lt;/strong&gt; 😉&lt;/p&gt;

</description>
      <category>cluster</category>
      <category>node</category>
      <category>backend</category>
    </item>
    <item>
      <title>Demystifying the Adapter Pattern</title>
      <dc:creator>Erick Engelhardt</dc:creator>
      <pubDate>Fri, 30 May 2025 20:00:32 +0000</pubDate>
      <link>https://forem.com/erickengelhardt/demystifying-the-adapter-pattern-eoa</link>
      <guid>https://forem.com/erickengelhardt/demystifying-the-adapter-pattern-eoa</guid>
      <description>&lt;p&gt;Ever tried plugging a USB-C into a micro-USB port? That’s exactly what the Adapter Pattern is here for. Not literally, but conceptually.&lt;/p&gt;

&lt;p&gt;The Adapter Pattern is all about &lt;strong&gt;making incompatible interfaces work together&lt;/strong&gt;. It acts like a bridge between two systems that weren’t designed to cooperate. You wrap one object with another that translates method calls and properties into a compatible format.&lt;/p&gt;




&lt;h3&gt;
  
  
  When Should You Use It?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You have legacy code that doesn't match your current interface.&lt;/li&gt;
&lt;li&gt;You want to integrate third-party libraries with your own abstraction.&lt;/li&gt;
&lt;li&gt;You're refactoring a codebase gradually and need backward compatibility.&lt;/li&gt;
&lt;li&gt;You're dealing with multiple interfaces that perform similar but not identical functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially useful in enterprise-scale projects where changing one system has ripple effects across many modules.&lt;/p&gt;




&lt;h3&gt;
  
  
  Real-World Example: Payments
&lt;/h3&gt;

&lt;p&gt;Imagine you're switching payment providers. Your app was built using an older &lt;code&gt;LegacyPayment&lt;/code&gt; class, but you want to move toward a new unified interface.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1 - Legacy system and desired abstraction
&lt;/h4&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;LegacyPaymentSdk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;sendPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&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="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;`Paid $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; via LegacyPayment`&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;class&lt;/span&gt; &lt;span class="nc"&gt;StripeSdk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;charge&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="kr"&gt;any&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;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;`Charged $&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="s2"&gt; via Stripe with metadata:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PaymentPayload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;amount&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="nl"&gt;payerName&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="nl"&gt;payerEmail&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PaymentProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;pay&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;PaymentPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PaymentResult&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PaymentResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transactionId&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="nl"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;h4&gt;
  
  
  Step 2 - Adapters for each implementation
&lt;/h4&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;LegacyPaymentAdapter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;PaymentProvider&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;legacy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LegacyPaymentSdk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;pay&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;PaymentPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PaymentResult&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;Adapter translating pay() to sendPayment()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Only amount is used; legacy gateway does not support metadata&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;legacy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendPayment&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;amount&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;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;legacy-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Simulated 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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripeAdapter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;PaymentProvider&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="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StripeSdk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;pay&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;PaymentPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PaymentResult&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;Adapter translating pay() to charge()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payerName&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payerEmail&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;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charge&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;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&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;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe-456&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// Simulated response&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;h4&gt;
  
  
  Step 3 - Service layer using the interface
&lt;/h4&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;PaymentService&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="k"&gt;private&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;PaymentProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;handleCheckout&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;PaymentPayload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PaymentResult&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;Service initiating payment...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pay&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;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;`Service completed payment. Transaction ID: &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;transactionId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Success: &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="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;return&lt;/span&gt; &lt;span class="nx"&gt;result&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;h4&gt;
  
  
  Step 4 - Usage with different adapters
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;legacyAdapter&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;LegacyPaymentAdapter&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;LegacyPaymentSdk&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;stripeAdapter&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;StripeAdapter&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;StripeSdk&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;legacyService&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;PaymentService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;legacyAdapter&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;stripeService&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;PaymentService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stripeAdapter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;legacyService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleCheckout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;stripeService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleCheckout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bob&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bob@example.com&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;h4&gt;
  
  
  Run the Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Full Output
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1) Service initiating payment...
2) Adapter translating pay() to sendPayment()
3) Paid $100 via LegacyPayment
4) Service completed payment.

5) Service initiating payment...
6) Adapter translating pay() to charge()
7) Charged $200 via Stripe with metadata: { name: 'Bob', email: 'bob@example.com' }
8) Service completed payment.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Output Breakdown
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;LegacyPayment flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1)&lt;/code&gt; The &lt;code&gt;PaymentService&lt;/code&gt; starts the legacy checkout process.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2)&lt;/code&gt; The adapter translates the call to match the legacy interface.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3)&lt;/code&gt; Payment is processed using the legacy gateway.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;4)&lt;/code&gt; The service completes the first transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stripe flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;5)&lt;/code&gt; Begins the second checkout using the Stripe adapter.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;6)&lt;/code&gt; Adapter maps the unified method to Stripe’s method.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;7)&lt;/code&gt; Payment succeeds, including user metadata.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;8)&lt;/code&gt; Finalizes the second transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing the Adapter Integration
&lt;/h3&gt;

&lt;p&gt;The repository includes unit tests to ensure your service logic remains decoupled from any specific SDK or gateway.&lt;/p&gt;

&lt;p&gt;The file &lt;code&gt;payment.service.spec.ts&lt;/code&gt; uses a mocked version of the &lt;code&gt;PaymentProvider&lt;/code&gt; interface to test the &lt;code&gt;PaymentService&lt;/code&gt; independently.&lt;/p&gt;

&lt;p&gt;This allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fully isolate service behavior from real implementations&lt;/li&gt;
&lt;li&gt;Guarantee contract compliance&lt;/li&gt;
&lt;li&gt;Swap adapters without affecting test coverage&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Run the Tests
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Test File: &lt;code&gt;payment.service.spec.ts&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This testing approach ensures your &lt;code&gt;PaymentService&lt;/code&gt; can be reused anywhere, as long as a compatible adapter is injected.&lt;/p&gt;




&lt;p&gt;This structure lets you plug and play with multiple providers without changing your business logic. Each adapter can include gateway-specific logic, such as formatting, validation, metadata conversion, retries, and so on.&lt;/p&gt;

&lt;p&gt;If tomorrow you adopt another provider (like PayPal or Adyen), all you need is another adapter.&lt;/p&gt;




&lt;h3&gt;
  
  
  Benefits of the Adapter Pattern
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decoupling&lt;/strong&gt;: Your core app logic is isolated from external or legacy systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradual migration&lt;/strong&gt;: Swap internals behind the scenes without breaking public interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: Enforce a common API across different providers or modules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability&lt;/strong&gt;: Mock adapters in tests instead of actual implementations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extendability&lt;/strong&gt;: Add logic to adapt not just method names but also data formatting, validation, or retries.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Common Pitfalls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overuse&lt;/strong&gt;: Don't use adapters as an excuse to skip proper refactoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance hit&lt;/strong&gt;: Wrapping layers can introduce slight overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too much abstraction&lt;/strong&gt;: Over-adapting might confuse new developers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use it where there's a real gap in compatibility, not as a blanket solution for all mismatches.&lt;/p&gt;




&lt;h3&gt;
  
  
  Pro Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pair with Dependency Injection for even better separation of concerns.&lt;/li&gt;
&lt;li&gt;Combine with Factory Pattern to automate the adapter selection logic.&lt;/li&gt;
&lt;li&gt;Favor interfaces over concrete classes to increase flexibility.&lt;/li&gt;
&lt;li&gt;Avoid adapting things that should just be rewritten. Sometimes adapters hide tech debt instead of solving it.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Adapters aren't glamorous, but they solve real-world messes. And let's be honest, clean architecture often comes down to choosing the right boring solution at the right time.&lt;/p&gt;

&lt;p&gt;Check out a full working repo: &lt;a href="https://github.com/erickne/ts-adapter-pattern" rel="noopener noreferrer"&gt;https://github.com/erickne/ts-adapter-pattern&lt;/a&gt;&lt;/p&gt;

</description>
      <category>adapter</category>
      <category>typescript</category>
      <category>architecture</category>
      <category>designpatterns</category>
    </item>
  </channel>
</rss>
