<?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: Ronak Navadia</title>
    <description>The latest articles on DEV Community by Ronak Navadia (@ronak_navadia_0611).</description>
    <link>https://dev.to/ronak_navadia_0611</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%2F1689057%2F86486230-c9a4-4be5-92ff-bd130f06f7a8.png</url>
      <title>DEV Community: Ronak Navadia</title>
      <link>https://dev.to/ronak_navadia_0611</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ronak_navadia_0611"/>
    <language>en</language>
    <item>
      <title>Stop Confusing Workers with Concurrency</title>
      <dc:creator>Ronak Navadia</dc:creator>
      <pubDate>Tue, 14 Apr 2026 17:35:32 +0000</pubDate>
      <link>https://dev.to/ronak_navadia_0611/stop-confusing-workers-with-concurrency-o2o</link>
      <guid>https://dev.to/ronak_navadia_0611/stop-confusing-workers-with-concurrency-o2o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A conversation with every developer who's ever asked "but aren't they the same thing?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Let me tell you about a conversation I've had at least a dozen times.&lt;/p&gt;

&lt;p&gt;A developer comes to me, frustrated. They've set up Bull for background jobs. They've even set the &lt;code&gt;concurrency&lt;/code&gt; option to 5, feeling confident. But their API is still grinding to a halt whenever heavy jobs kick in. And when they try to "scale their workers", they end up with five copies of their entire API running — which wasn't the plan at all.&lt;/p&gt;

&lt;p&gt;Sound familiar? Let's fix this, once and for all — not with theory, but with the clear mental model you actually need.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, what actually happens when you run your app?
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;node dist/main.js&lt;/code&gt;, something very specific happens on your machine:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;The truth:&lt;/strong&gt; You start &lt;strong&gt;one Node.js process&lt;/strong&gt;. One event loop. One chunk of memory. One running instance of your entire application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. Everything you've built — your controllers, your services, your Bull job processors — all of it lives inside that single process.&lt;/p&gt;

&lt;p&gt;This isn't a problem at first. But as your system grows, that single process starts to become a bottleneck. And this is exactly where the confusion about "workers" begins.&lt;/p&gt;




&lt;h2&gt;
  
  
  So what is a "worker", really?
&lt;/h2&gt;

&lt;p&gt;Here's the thing nobody says clearly enough: &lt;strong&gt;a worker is just another Node.js process.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a thread. Not a magic Bull setting. Not a special NestJS construct. It's a process — the same kind of process you start when you run &lt;code&gt;node dist/main.js&lt;/code&gt;. The only difference is in &lt;em&gt;what it does&lt;/em&gt; once it starts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;API Process&lt;/th&gt;
&lt;th&gt;Worker Process&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Starts with&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.listen(3000)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;createApplicationContext()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Does&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Waits for HTTP requests&lt;/td&gt;
&lt;td&gt;Polls a queue for jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Handles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Validation, auth, routing&lt;/td&gt;
&lt;td&gt;Sending emails, processing files, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Has a port the world can reach&lt;/td&gt;
&lt;td&gt;No port, invisible from outside&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A process becomes an API server only because it calls &lt;code&gt;app.listen()&lt;/code&gt;. A process becomes a worker only because it processes background jobs instead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"API and Worker are not different code — they are different ways of running your app."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Same code. Different entry point. Different role.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Then what does Bull's &lt;code&gt;concurrency&lt;/code&gt; option actually do?
&lt;/h2&gt;

&lt;p&gt;This is the question that trips everyone up. And it's a completely fair question.&lt;/p&gt;

&lt;p&gt;When you write something like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;email.processor.ts&lt;/code&gt;&lt;/strong&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&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;EmailProcessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="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;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// send the email&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;You are &lt;em&gt;not&lt;/em&gt; creating 5 workers. You are telling the &lt;strong&gt;one worker process that's already running&lt;/strong&gt; to handle up to 5 jobs at the same time — all within its single event loop.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How many jobs &lt;em&gt;one process&lt;/em&gt; handles simultaneously&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multiple Workers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How many &lt;em&gt;separate processes&lt;/em&gt; are doing the handling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Think of it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🍳 &lt;strong&gt;Concurrency&lt;/strong&gt; = one chef trying to watch 5 pots at once&lt;/li&gt;
&lt;li&gt;👨‍🍳👨‍🍳👨‍🍳 &lt;strong&gt;Multiple workers&lt;/strong&gt; = hiring 5 separate chefs, each at their own stove&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;I/O-heavy jobs&lt;/strong&gt; (like sending emails or making API calls), concurrency works beautifully.&lt;br&gt;
For &lt;strong&gt;CPU-heavy jobs&lt;/strong&gt; (like image processing or video encoding), you want separate processes — because CPU work actually blocks the event loop.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why mixing API and Worker in one process causes pain
&lt;/h2&gt;

&lt;p&gt;Here's how most NestJS apps start out. Everything in one process:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;app.module.ts&lt;/code&gt; (the problematic way)&lt;/strong&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;BullModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&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;BullModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerQueue&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;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nx"&gt;EmailModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// contains the @Processor&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;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you start this app with &lt;code&gt;node dist/main.js&lt;/code&gt;, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ HTTP server listening on port 3000&lt;/li&gt;
&lt;li&gt;✅ Bull processor actively picking up jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fine for small scale. But three ugly problems emerge as you grow:&lt;/p&gt;




&lt;h3&gt;
  
  
  ⚠️ Problem 1 · Slow API
&lt;/h3&gt;

&lt;p&gt;A heavy background job — resizing images, crunching data — hogs the event loop. Your API users start seeing timeouts. They didn't change anything. Your background jobs strangled the API.&lt;/p&gt;




&lt;h3&gt;
  
  
  ⚠️ Problem 2 · Wasteful Scaling
&lt;/h3&gt;

&lt;p&gt;You want 3 more job processors, so you spin up 3 more instances of your app. Surprise — you now have 3 extra API servers too, all competing on the same port or behind a load balancer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You wanted kitchen staff. You hired three more waiters who also cook.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  ⚠️ Problem 3 · No Isolation
&lt;/h3&gt;

&lt;p&gt;A runaway job crashes the process. Down goes your API too. Two unrelated concerns, one shared fate.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix: two entry points, two roles
&lt;/h2&gt;

&lt;p&gt;The solution is elegant. You write two entry point files — one for each role. They both import &lt;code&gt;AppModule&lt;/code&gt;, but they start it differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;main.ts&lt;/code&gt; — the API process&lt;/strong&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bootstrap&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;app&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;NestFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppModule&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;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="c1"&gt;// gives it a port — makes it an API&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;worker.ts&lt;/code&gt; — the worker process&lt;/strong&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bootstrap&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;NestFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createApplicationContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// no port — just loads modules and starts processing&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can start them independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node dist/main.js    &lt;span class="c"&gt;# starts the API&lt;/span&gt;
node dist/worker.js  &lt;span class="c"&gt;# starts a worker&lt;/span&gt;
node dist/worker.js  &lt;span class="c"&gt;# starts another worker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three separate processes. One handles HTTP. Two process jobs. They never step on each other.&lt;/p&gt;




&lt;h2&gt;
  
  
  The hidden gotcha you will hit
&lt;/h2&gt;

&lt;p&gt;Even after splitting your entry points, there's a trap. Both files still import &lt;code&gt;AppModule&lt;/code&gt; — and &lt;code&gt;AppModule&lt;/code&gt; still registers your Bull processor. So your "API process" will also start picking up jobs. You've separated the files but not the behaviour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix it with an environment variable:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;app.module.ts&lt;/code&gt; — now with role awareness&lt;/strong&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Only load the job processor in worker processes&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;RUN_WORKER&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&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;EmailProcessorModule&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start them like this:&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="c"&gt;# API — no job processing&lt;/span&gt;
&lt;span class="nv"&gt;RUN_WORKER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;node dist/main.js

&lt;span class="c"&gt;# Worker — only job processing&lt;/span&gt;
&lt;span class="nv"&gt;RUN_WORKER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;node dist/worker.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your API process has zero awareness of job processors. It queues jobs and walks away. The worker processes pick those jobs up.&lt;/p&gt;




&lt;h2&gt;
  
  
  How they talk to each other (they don't, directly)
&lt;/h2&gt;

&lt;p&gt;Your API and your workers never call each other. There are no inter-process function calls. Instead, they communicate through Redis via Bull's queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────┐       ┌─────────────────────────┐       ┌─────────────┐       ┌──────────────────────────┐
│   Client   │  ───► │      API Process        │  ───► │ Redis Queue │  ───► │      Worker Process      │
│            │       │   adds job to queue     │       │             │       │  picks up &amp;amp; executes     │
└────────────┘       └─────────────────────────┘       └─────────────┘       └──────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In your API controller:&lt;/strong&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;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;emailQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendWelcome&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;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// API's job is done. It forgets about this immediately.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In your worker processor:&lt;/strong&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendWelcome&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;handleWelcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Job&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="nx"&gt;mailer&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;job&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;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Worker handles it whenever it's free.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The queue is the contract between them. Durable, decoupled, and fault-tolerant.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Managing it all with PM2
&lt;/h2&gt;

&lt;p&gt;Manually running terminal commands gets old fast. PM2 is the standard solution — it manages your processes, restarts them on crash, and lets you scale workers with a single flag.&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="c"&gt;# Start the API (1 instance)&lt;/span&gt;
pm2 start dist/main.js &lt;span class="nt"&gt;--name&lt;/span&gt; api &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;RUN_WORKER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;# Start 3 worker processes&lt;/span&gt;
pm2 start dist/worker.js &lt;span class="nt"&gt;--name&lt;/span&gt; worker &lt;span class="nt"&gt;-i&lt;/span&gt; 3 &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;RUN_WORKER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# See what's running&lt;/span&gt;
pm2 list

&lt;span class="c"&gt;# Scale workers to 5 without restarting anything&lt;/span&gt;
pm2 scale worker 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you independent control. Need to handle a spike in emails? Scale the workers. Getting more API traffic? Scale the API. They move independently because they &lt;em&gt;are&lt;/em&gt; independent.&lt;/p&gt;




&lt;h2&gt;
  
  
  The restaurant analogy (because it genuinely helps)
&lt;/h2&gt;

&lt;p&gt;Your system is a restaurant. Here's the full cast:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Restaurant Role&lt;/th&gt;
&lt;th&gt;Your System Equivalent&lt;/th&gt;
&lt;th&gt;Technical Term&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Customer walks in&lt;/td&gt;
&lt;td&gt;HTTP request arrives&lt;/td&gt;
&lt;td&gt;Client request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Waiter takes the order&lt;/td&gt;
&lt;td&gt;API receives request, validates, queues a job&lt;/td&gt;
&lt;td&gt;API Process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order ticket goes to kitchen&lt;/td&gt;
&lt;td&gt;Job lands in Redis&lt;/td&gt;
&lt;td&gt;Bull Queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chef cooks the meal&lt;/td&gt;
&lt;td&gt;Worker picks up the job and executes it&lt;/td&gt;
&lt;td&gt;Worker Process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chef can multitask on 3 orders&lt;/td&gt;
&lt;td&gt;One worker with &lt;code&gt;concurrency: 3&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Bull concurrency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hiring a second chef&lt;/td&gt;
&lt;td&gt;Starting a second worker process&lt;/td&gt;
&lt;td&gt;Process scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;A great restaurant doesn't make the waiter cook and manage inventory at the same time. Separation of concerns isn't just an architectural principle — it's common sense.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The complete picture, in one place
&lt;/h2&gt;

&lt;p&gt;Let's bring it all together with one final summary. The key ideas, no fluff:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A process&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A running Node.js app with its own memory and event loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;An API process&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A process that called &lt;code&gt;app.listen()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A worker process&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A process that didn't — it just processes queue jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How many jobs one worker handles at once, not separate processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Separation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Different entry points + env flags to control which modules load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PM2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The thing that manages, monitors, and scales all of it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Your API handles requests. Your workers handle work.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;They share a codebase — but never a process."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Once this clicks, a whole category of &lt;em&gt;"why is my API slow?"&lt;/em&gt; and &lt;em&gt;"why are my jobs not running where I expect?"&lt;/em&gt; questions just… disappear. You start thinking in processes instead of in files, and that changes how you architect everything.&lt;/p&gt;

&lt;p&gt;If you have questions, or if something in here is still fuzzy — that's what the comments are for. I've had this conversation many times, and each version of it has made this explanation a little sharper. Your confusion is probably someone else's confusion too.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Queue Whisperer: Taming Bull Jobs in NestJS Like a Pro</title>
      <dc:creator>Ronak Navadia</dc:creator>
      <pubDate>Tue, 14 Apr 2026 17:32:49 +0000</pubDate>
      <link>https://dev.to/ronak_navadia_0611/the-queue-whisperer-taming-bull-jobs-in-nestjs-like-a-pro-1gha</link>
      <guid>https://dev.to/ronak_navadia_0611/the-queue-whisperer-taming-bull-jobs-in-nestjs-like-a-pro-1gha</guid>
      <description>&lt;p&gt;Picture this: your app is blowing up. Hundreds of appointment confirmations need to go out, 48-hour reminders are piling up, and your server is sweating. You &lt;em&gt;could&lt;/em&gt; process everything in one big, synchronous pile — but that's like trying to run a restaurant where one chef cooks, serves, cleans, and takes reservations all at once.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;Bull&lt;/strong&gt; — NestJS's battle-tested job queue library. And today, we're going to talk about the unsung heroes of Bull: &lt;strong&gt;consumers&lt;/strong&gt; and &lt;strong&gt;concurrency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Grab a coffee. Let's fix that restaurant. ☕&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Jobs Are Piling Up
&lt;/h2&gt;

&lt;p&gt;Every non-trivial backend eventually faces this moment. You need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send an order confirmation the second someone books an appointment&lt;/li&gt;
&lt;li&gt;Fire off a reminder 48 hours before that appointment&lt;/li&gt;
&lt;li&gt;Not block your main thread doing any of it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bull solves this beautifully — but &lt;em&gt;how&lt;/em&gt; you set up your consumers determines whether your queue hums like a well-oiled machine or groans under pressure.&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;three patterns&lt;/strong&gt; worth knowing. Each has its moment to shine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pattern 1: The One-Chef Kitchen (Single Consumer)
&lt;/h2&gt;

&lt;p&gt;Sometimes, simplicity wins. One processor class. Multiple job handlers. Clean, contained, easy to reason about.&lt;/p&gt;

&lt;p&gt;Think of it as a single skilled chef who knows how to make every dish — they just work on a few orders at a time.&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appointmentQueue&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;AppointmentProcessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// Chef's special #1: Order Confirmations&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendOrderConfirmationMessageJob&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;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&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;handleOrderConfirmationMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Job&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing order confirmation job:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&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="c1"&gt;// Your magic here&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Chef's special #2: 48-Hour Reminders&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send48HourReminderForAppointmentJob&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;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handle48HourReminder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Job&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing 48-hour reminder job:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&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="c1"&gt;// Your magic here&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;
  
  
  What's happening here?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One class, two jobs&lt;/strong&gt; — &lt;code&gt;AppointmentProcessor&lt;/code&gt; owns everything related to appointments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency per job&lt;/strong&gt; — Notice how &lt;code&gt;sendOrderConfirmationMessageJob&lt;/code&gt; gets &lt;code&gt;3&lt;/code&gt; workers while the reminder job gets &lt;code&gt;2&lt;/code&gt;. You're not guessing; you're &lt;em&gt;deliberately&lt;/em&gt; allocating processing power based on expected load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;{ concurrency: X }&lt;/code&gt; option&lt;/strong&gt; inside &lt;code&gt;@Process()&lt;/code&gt; is your dial — turn it up for high-traffic jobs, keep it lower for jobs that are resource-heavy or rate-limited by external APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When to use this:&lt;/strong&gt; Small-to-medium queues, tightly related job types, or when you want everything in one place. Great for getting started fast.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Pattern 2: The Brigade Kitchen (Multiple Consumers)
&lt;/h2&gt;

&lt;p&gt;Now imagine your restaurant got a Michelin star. One chef can't cut it anymore. You hire specialists — a pasta chef, a pastry chef, a grill master.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;multi-consumer pattern&lt;/strong&gt;: each job type gets its &lt;em&gt;own&lt;/em&gt; dedicated processor class.&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;// Specialist #1: The Confirmation Pro&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appointmentQueue&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;OrderConfirmationProcessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendOrderConfirmationMessageJob&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;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&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;handleOrderConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Job&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing order confirmation job:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&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="c1"&gt;// Confirmation logic&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Specialist #2: The Reminder Guru&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appointmentQueue&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;ReminderProcessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send48HourReminderForAppointmentJob&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;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handle48HourReminder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Job&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing 48-hour reminder job:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&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="c1"&gt;// Reminder logic&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;
  
  
  Why split them up?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns&lt;/strong&gt; — Each class does &lt;em&gt;one&lt;/em&gt; thing and does it well. When the reminder logic needs to change, you open one small file, not a sprawling processor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent scaling&lt;/strong&gt; — Need more confirmation workers? Crank up the concurrency on &lt;code&gt;OrderConfirmationProcessor&lt;/code&gt; without touching anything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier testing&lt;/strong&gt; — Unit testing a focused class is a joy compared to wrestling with a monolith.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When to use this:&lt;/strong&gt; Growing codebases, jobs with very different logic or dependencies, or teams where different developers own different job types.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Pattern 3: The Ghost Kitchen (Dynamic Processing with &lt;code&gt;onModuleInit&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Here's where things get interesting. What if you don't want to use decorators at all? What if you want to wire everything up &lt;em&gt;programmatically&lt;/em&gt;, maybe based on config, environment variables, or runtime conditions?&lt;/p&gt;

&lt;p&gt;Meet the ghost kitchen — fully functional, no storefront, completely dynamic.&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;InjectQueue&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;@nestjs/bull&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;Queue&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;bull&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;Injectable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnModuleInit&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;@nestjs/common&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="nd"&gt;Injectable&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;JobProcessingService&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnModuleInit&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="nd"&gt;InjectQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appointmentQueue&lt;/span&gt;&lt;span class="dl"&gt;'&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;appointmentQueue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Queue&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;onModuleInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Wire up confirmation jobs with 3 concurrent workers&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;appointmentQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendOrderConfirmationMessageJob&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing order confirmation job:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&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="c1"&gt;// Confirmation logic&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Wire up reminder jobs with 2 concurrent workers&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;appointmentQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send48HourReminderForAppointmentJob&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing 48-hour reminder job:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&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="c1"&gt;// Reminder logic&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 trick here?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;onModuleInit()&lt;/code&gt;&lt;/strong&gt; fires when the NestJS module boots up — the perfect moment to register your workers before any jobs start flowing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency as the second argument&lt;/strong&gt; — In &lt;code&gt;queue.process(jobName, concurrency, handler)&lt;/code&gt;, that middle number is your power lever. No decorators, just plain code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum flexibility&lt;/strong&gt; — You can read concurrency values from a config service, toggle jobs on/off per environment, or even spin up workers conditionally. Decorators can't do that.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When to use this:&lt;/strong&gt; When you need runtime flexibility, config-driven behaviour, or you're working in a service-first architecture where decorators feel out of place.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Secret Ingredient: Concurrency
&lt;/h2&gt;

&lt;p&gt;All three patterns orbit the same concept — &lt;strong&gt;concurrency&lt;/strong&gt;. Let's demystify it.&lt;/p&gt;

&lt;p&gt;Concurrency in Bull is simply: &lt;em&gt;"how many workers can chew through this job type at the same time?"&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One job at a time. Safe, slow, sequential.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Three jobs running simultaneously. Faster, but uses more resources.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;High throughput. Great for I/O-bound tasks; risky for CPU-heavy ones.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few rules of thumb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I/O-bound jobs&lt;/strong&gt; (sending emails, hitting APIs) → higher concurrency is fine; your workers spend most time &lt;em&gt;waiting&lt;/em&gt;, not &lt;em&gt;computing&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU-bound jobs&lt;/strong&gt; (image processing, PDF generation) → keep concurrency low; too many parallel workers will starve your CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate-limited external APIs&lt;/strong&gt; → concurrency should respect the API's limits, not just your server's capacity.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;Here's a quick cheat sheet for picking your pattern:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Best Pattern&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small app, few job types&lt;/td&gt;
&lt;td&gt;Single Consumer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate teams or complex logic&lt;/td&gt;
&lt;td&gt;Multiple Consumers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config-driven or dynamic setup&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onModuleInit()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The beauty of Bull is that none of these patterns are mutually exclusive. You can use a single consumer for simple jobs and a dedicated processor for a particularly complex one — mix and match as your needs evolve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Job queues aren't glamorous. They live in the background, quietly doing the work that keeps your users happy — sending that confirmation email before they start to wonder, firing that reminder just in time.&lt;/p&gt;

&lt;p&gt;But how you &lt;em&gt;architect&lt;/em&gt; your consumers? That's where the craft lives.&lt;/p&gt;

&lt;p&gt;Whether you go with a one-chef kitchen, a full brigade, or a ghost kitchen running on vibes and &lt;code&gt;onModuleInit&lt;/code&gt; — now you know the trade-offs, and you can choose with confidence.&lt;/p&gt;

&lt;p&gt;Go build something that scales. 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>How does Javascript work under the hood?</title>
      <dc:creator>Ronak Navadia</dc:creator>
      <pubDate>Sat, 06 Jul 2024 12:54:45 +0000</pubDate>
      <link>https://dev.to/ronak_navadia_0611/how-does-javascript-work-under-the-hood-3ofh</link>
      <guid>https://dev.to/ronak_navadia_0611/how-does-javascript-work-under-the-hood-3ofh</guid>
      <description>&lt;p&gt;Okay, So today we shall learn about How Javascript works.&lt;/p&gt;

&lt;p&gt;Firstly, JavaScript is a lightweight, cross-platform, single-threaded, and interpreted compiled programming language.&lt;/p&gt;

&lt;p&gt;So, Everything inside Javascript happens inside a place known as execution context.&lt;/p&gt;

&lt;p&gt;Now the question arises in your mind what is execution context?&lt;/p&gt;

&lt;p&gt;Execution Context:-&lt;/p&gt;

&lt;p&gt;The environment that handles the code transformation and code execution by scanning the script file (JS file).&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%2Fp1fg6tcydte6twiv2t7m.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%2Fp1fg6tcydte6twiv2t7m.png" alt="Execution context" width="660" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown above, the Execution context is divided into 2 parts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Memory component (variable environment)&lt;br&gt;
This is a place where all the variables and functions are stored in key-value pairs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code component (thread of execution)&lt;br&gt;
This is a place where code is executed one line at a time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now lets see how execution context works?&lt;/p&gt;

&lt;p&gt;consider the below example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var n = 3;

function cube(num) {
  var result = num * num * num;
  return result;
}

var cube3 = cube(n);
var cube5 = cube(5);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now execution context is created with memory and code components as shown in the above image&lt;/p&gt;

&lt;p&gt;The first phase is the memory creation phase and the second phase is the code execution phase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory creation phase
&lt;/h3&gt;

&lt;p&gt;In this phase, JS will allocate the memory to all the variables and functions&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allocate memory to n variable and store value as undefined.&lt;/li&gt;
&lt;li&gt;allocate memory to the cube function and store the whole 
function code.&lt;/li&gt;
&lt;li&gt;allocate memory to cube3 variable and store value as undefined.&lt;/li&gt;
&lt;li&gt;allocate memory to cube5 variable and store value as undefined.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now after the memory creation let's see how the code is executed in the code execution phase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code execution phase
&lt;/h2&gt;

&lt;p&gt;Javascript goes through the whole JS program line by line and executes the code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;assign value 2 to n &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;now it goes to the line where the square function is invoked. so whenever any function is invoked a new execution context is created same as mentioned in the image. &lt;/p&gt;

&lt;p&gt;so for the square(n) a new execution context is created with memory and code components&lt;/p&gt;

&lt;p&gt;here again, memory is allocated to num and as undefined and then it runs the code execution phase for the function in which it assigns num a value i.e. passed from square(n) where n = 3. so num will assign 3. After that ans will be assigned num * num * num i.e. 27. &lt;/p&gt;

&lt;p&gt;Now whenever anything is returned from the function its value is assigned to the variable that invoked the function. here in our case, cube3 will have 27 as ans is assigned 27.&lt;/p&gt;

&lt;p&gt;Note here that whenever an execution context is done with its task it is deleted from the call stack.&lt;/p&gt;

&lt;p&gt;The same thing happens for the 2nd function cube 5.&lt;/p&gt;

&lt;p&gt;Thank you so much for being with me.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>coding</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
