<?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: Ankit Aabad</title>
    <description>The latest articles on Forem by Ankit Aabad (@ankit_aabad_be198a64a0520).</description>
    <link>https://forem.com/ankit_aabad_be198a64a0520</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%2F3022404%2F689fd76b-0b0e-4adc-86e7-75a447b03ae0.jpg</url>
      <title>Forem: Ankit Aabad</title>
      <link>https://forem.com/ankit_aabad_be198a64a0520</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ankit_aabad_be198a64a0520"/>
    <language>en</language>
    <item>
      <title>⚡ How to Respect Rate Limits With Bottleneck</title>
      <dc:creator>Ankit Aabad</dc:creator>
      <pubDate>Sat, 28 Jun 2025 03:19:39 +0000</pubDate>
      <link>https://forem.com/ankit_aabad_be198a64a0520/how-to-respect-rate-limits-without-losing-your-sanity-56ch</link>
      <guid>https://forem.com/ankit_aabad_be198a64a0520/how-to-respect-rate-limits-without-losing-your-sanity-56ch</guid>
      <description>&lt;p&gt;Modern systems often rely on APIs or services that enforce strict limits on how frequently and how many requests can be made — whether due to scalability, fairness, resource protection or your subscription plan.&lt;/p&gt;

&lt;p&gt;You might be working with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; An internal rate-limited service&lt;/li&gt;
&lt;li&gt; An async job runner that must avoid resource contention&lt;/li&gt;
&lt;li&gt; A background task that syncs data without overwhelming a target 
system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, trying to "pace" requests with custom code typically leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;    Fragile setTimeout() logic&lt;/li&gt;
&lt;li&gt;    Inconsistent behavior under load&lt;/li&gt;
&lt;li&gt;    Queues that are difficult to debug or scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s where Bottleneck comes in — a lightweight, production-ready scheduling library for Node.js that brings powerful, composable rate-limiting primitives to your async workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 The 3 Core Flags and What They Solve
&lt;/h2&gt;




&lt;h3&gt;
  
  
  1. ⏱️ &lt;code&gt;minTime&lt;/code&gt;: Space Out Requests (RPS)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“My API only allows 10 requests per second.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limiter&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;Bottleneck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;minTime&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="c1"&gt;// 100ms = 10 RPS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&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;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://example.com/api?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&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="c1"&gt;// Queue up 200 requests&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;results&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="s2"&gt;All requests completed!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensures &lt;strong&gt;100ms spacing&lt;/strong&gt; between job starts — giving you consistent pacing.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. 🔄 &lt;code&gt;maxConcurrent&lt;/code&gt;: Limit Parallel Jobs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I don’t want more than 3 jobs running at once.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limiter&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;Bottleneck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;maxConcurrent&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&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;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;No matter how many jobs are queued, only &lt;strong&gt;3 run at the same time&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. 🪣 &lt;code&gt;reservoir&lt;/code&gt;: Enforce Request Quotas
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The API allows only 1000 requests per hour.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limiter&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;Bottleneck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;reservoir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reservoirRefreshInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
  &lt;span class="na"&gt;reservoirRefreshAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&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;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;smtpClient&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;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;Prevents more than 1000 jobs in a given hour. When the bucket is empty, jobs wait for refill.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 How They Work Together
&lt;/h2&gt;

&lt;p&gt;Bottleneck applies &lt;strong&gt;all constraints&lt;/strong&gt; together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;minTime&lt;/code&gt; → controls &lt;strong&gt;how fast&lt;/strong&gt; jobs start&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maxConcurrent&lt;/code&gt; → limits &lt;strong&gt;how many run at once&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reservoir&lt;/code&gt; → caps &lt;strong&gt;total jobs over time&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ✅ Example
&lt;/h2&gt;

&lt;p&gt;Here is a Sample code to test with Bottleneck, Just change the delay timer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I want to send 10 requests per second, at most 3 in parallel, and not exceed 1000 requests per hour.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Testing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Bottleneck&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bottleneck&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;limiter&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;Bottleneck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;minTime&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="c1"&gt;// 10 requests per second&lt;/span&gt;
    &lt;span class="na"&gt;maxConcurrent&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="na"&gt;reservoir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reservoirRefreshInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
    &lt;span class="na"&gt;reservoirRefreshAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callAPI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&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;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="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="s2"&gt;finished request for payload:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Queue 100 requests&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;results&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="s2"&gt;All requests completed!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧪 Quick Flag Guide
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Limit requests per second&lt;/td&gt;
&lt;td&gt;&lt;code&gt;minTime&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avoid parallel overload&lt;/td&gt;
&lt;td&gt;&lt;code&gt;maxConcurrent&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Respect quota (e.g. 1000/hr)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;reservoir&lt;/code&gt; + refresh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combine pacing &amp;amp; concurrency&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;minTime&lt;/code&gt; + &lt;code&gt;maxConcurrent&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full traffic shaping&lt;/td&gt;
&lt;td&gt;All 3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🧘 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The next time you hit a &lt;code&gt;429 Too Many Requests&lt;/code&gt; or crash a service with async overload — don’t use hacks. Use Bottleneck.&lt;/p&gt;

&lt;p&gt;✅ Declarative.&lt;br&gt;&lt;br&gt;
✅ Predictable.&lt;br&gt;&lt;br&gt;
✅ Powerful.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Why Single Function Lambdas is a terrible choice for Serverless development.</title>
      <dc:creator>Ankit Aabad</dc:creator>
      <pubDate>Sun, 06 Apr 2025 08:28:49 +0000</pubDate>
      <link>https://forem.com/ankit_aabad_be198a64a0520/why-single-function-lambdas-is-a-terrible-choice-for-serverless-development-4dh</link>
      <guid>https://forem.com/ankit_aabad_be198a64a0520/why-single-function-lambdas-is-a-terrible-choice-for-serverless-development-4dh</guid>
      <description>&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%2Fh9w4l29193m5p9tffltn.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%2Fh9w4l29193m5p9tffltn.png" alt="LambdaLiths are Better" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Single function lambdas for every endpoint is really bad on every possible front, be it developer experience, debugging experience, deployment efficiency, cost or performance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One common best practice that’s often pushed by serverless advocates is to create a separate Lambda function for every single endpoint. I think that’s a terrible idea. In this post we will examine the arguments from the other side and see why the arguments lack merit, consistency, or real-world practicality.&lt;/p&gt;

&lt;p&gt;This push for extreme separation reminds me of Golang. I never really liked the language itself, but I’ve always appreciated one of its core design philosophies: &lt;a href="https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html" rel="noopener noreferrer"&gt;Less is exponentially more.&lt;/a&gt; Interestingly, while I don’t think that idea works well in the context of programming language design, it makes a lot of sense when applied to Serverless infrastructure. When building serverless systems, &lt;strong&gt;a few cohesive, well-structured Lambdas&lt;/strong&gt; often result in simpler, more maintainable architectures than a maze of single-function lambdas.&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%2Fs0mtrhcfgi3wnbvdz91j.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%2Fs0mtrhcfgi3wnbvdz91j.png" alt="Less is Exponentially More" width="486" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Architect Around Work, Not Routes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;HTTP paths are not architectural boundaries&lt;/strong&gt;   &lt;strong&gt;— workloads are&lt;/strong&gt;. Lambdas should be shaped by what they actually do and the dependencies they need to run efficiently.&lt;/p&gt;

&lt;p&gt;For example, you might have a Lambda that powers your entire web server. Another for data crunching using an npm package that ships with a rust binary (&lt;a href="https://www.npmjs.com/package/nodejs-polars" rel="noopener noreferrer"&gt;node polars&lt;/a&gt;) leveraging multiple vCpus. A third might handle image processing with &lt;a href="https://sharp.pixelplumbing.com/" rel="noopener noreferrer"&gt;sharp&lt;/a&gt;, triggered by S3 uploads. And maybe another is dedicated to web scraping where you bundle a headless browser like &lt;a href="https://developer.chrome.com/docs/puppeteer/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; as Layer to Lambda.&lt;/p&gt;

&lt;p&gt;Each of these functions serves a &lt;strong&gt;real, functional purpose&lt;/strong&gt;. They have unique performance characteristics, dependency trees, and compute or configuration needs. This is what meaningful separation looks like — it’s &lt;strong&gt;intentional&lt;/strong&gt; , based on real boundaries, and grounded in how the system actually works.&lt;/p&gt;

&lt;p&gt;But carving out a separate Lambda just because one endpoint is /user/login and the other is /user/logout? That’s not applying the Single Responsibility Principle — that’s &lt;strong&gt;butchering it in broad daylight&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And if you’re worried about routing flexibility, API Gateway invokes the most specific match. That means you can serve &lt;em&gt;all&lt;/em&gt; your generic routes through a single Lambda while still routing specific paths to their own dedicated function. You get targeted isolation where it actually matters — without blowing up your architecture into a hundred micro-lambdas.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Myth of Fine-Grained Access Control and Blast Radius
&lt;/h3&gt;

&lt;p&gt;One of the most frequently cited justifications for having a separate Lambda for every endpoint is “fine-grained access control” or reducing the “blast radius.” It sounds reasonable at first — give each function the minimum permissions it needs, so if it’s compromised or misbehaves, the damage is limited. But if you take this logic to its natural conclusion, it quickly becomes absurd.&lt;/p&gt;

&lt;p&gt;If this principle were applied consistently, you’d also be creating a &lt;strong&gt;separate Docker container for every HTTP endpoint&lt;/strong&gt; in a traditional service. But nobody does that — because it’s obviously overkill and stupid.&lt;/p&gt;

&lt;p&gt;The same goes for databases. Serverless advocates often promote &lt;strong&gt;single-table design&lt;/strong&gt; ( with DynamoDB). But hang on — doesn’t that violate “fine-grained access control”? If a createUser Lambda has PutItem access on the whole table, what’s stopping it from writing into an unrelated orders record? Should we now split the table into one-per-entity to reduce the blast radius even further? because Logic is the same. Where do we stop?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This isn’t architectural discipline — it’s &lt;strong&gt;paranoia masquerading as best practice&lt;/strong&gt;. Security and isolation are important — but &lt;strong&gt;so is pragmatism&lt;/strong&gt;. The goal is to &lt;strong&gt;build systems that are secure, maintainable, and efficient&lt;/strong&gt;  — not to pass a theoretical purity test.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don’t believe me. Arc codes is a serverless framework that creates a single IAM role and shares it across all functions. check &lt;a href="https://arc.codes/docs/en/get-started/why-architect#:~:text=All%20resources%20defined%20in%20the%20manifest%20share%20one%20IAM%20Role%20with%20least%20privilege%20access%20to%20only%20the%20resources%20defined%20in%20the%20same%20stack." rel="noopener noreferrer"&gt;here&lt;/a&gt; and in the &lt;a href="https://arc.codes/playground" rel="noopener noreferrer"&gt;playground&lt;/a&gt;. Serverless framework is another one that uses single IAM role. &lt;a href="https://www.serverless.com/framework/docs/providers/aws/guide/iam#iam-permissions-for-functions:~:text=By%20default%2C%20one%20IAM%20Role%20is%20shared%20by%20all%20the%20Lambda%20functions%20in%20your%20service.%20Also%20by%20default%2C" rel="noopener noreferrer"&gt;check here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bundle Size &amp;amp; Cold Starts
&lt;/h3&gt;

&lt;p&gt;One common argument against combining endpoints into a single Lambda (a &lt;em&gt;lambdalith&lt;/em&gt;) is that it results in a larger bundle size, which supposedly increases cold start latency. But this concern is often overstated and disconnected from real-world usage patterns.&lt;/p&gt;

&lt;p&gt;The truth is: &lt;strong&gt;most of your endpoints share the same core dependencies.&lt;/strong&gt; Your db libraries like node-postgres, drizzle or dynamodb toolbox, zod or arktype for validation all remain the same. Moreover the relation between bundle size and cold start is not linear. The typescript ecosystem is amazing and you have great tree shakeable and optimized libraries. You have honojs for web server, you have zero abstraction ORMs like zapatos.&lt;/p&gt;

&lt;p&gt;With a &lt;strong&gt;Lambdalith&lt;/strong&gt; , you unlock powerful reuse: secrets from Parameter Store, database connections, and shared libraries are all initialized once and reused across requests. Since one Lambda handles multiple routes, it receives steady traffic and stays warm, reducing cold starts and latency. But there’s more — even if a Lambda stays warm for 5–6 minutes, &lt;strong&gt;DynamoDB’s keep-alive is only ~90 seconds&lt;/strong&gt;. (personal experience not official number) So if a function isn’t hit frequently, you’ll pay a hidden penalty: a warm Lambda but a cold DB connection. That’s a &lt;strong&gt;cold start inside a warm start&lt;/strong&gt;. Split your app into dozens of tiny Lambdas and you multiply this pain.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Developer Experience, Debugging Experiece and Deployment Efficiency: Lambdaliths Keep You in Flow
&lt;/h3&gt;

&lt;p&gt;When you’re following the “one Lambda per endpoint” pattern, &lt;strong&gt;every new endpoint means deploying a new function&lt;/strong&gt;  — along with its own permissions, logging config, and event source mappings. It’s ceremony without substance. With Lambdalith you can just sam sync instead of sam deploy which is way faster.&lt;/p&gt;

&lt;p&gt;And then there’s IAM: ever added PutItem permissions for DynamoDB and realized you now need BatchWriteItem for DynamoDB? Guess what — time to redeploy.&lt;/p&gt;

&lt;p&gt;With single function lambdas you get burried under a maze of log groups. With multiple developers and all running their personal stacks this gets amplified. keep searching your logs in the awful AWS UI.&lt;/p&gt;

&lt;p&gt;You can btw have a single log group for all your lambdas but no one practices that and it comes with some dope benefits but that is for some other day.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Developer Experience makes you agile not scrum ceremonies or over engineering.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Event Warming and Provisioned Concurrency
&lt;/h3&gt;

&lt;p&gt;When it comes to &lt;strong&gt;event warming&lt;/strong&gt; or &lt;strong&gt;provisioned concurrency&lt;/strong&gt; , Lambdaliths shine. Why? Because you’re keeping &lt;strong&gt;one Lambda warm&lt;/strong&gt; that serves many routes — that’s &lt;strong&gt;maximum ROI&lt;/strong&gt; on your warm instance. A provisioned concurrency of 50 costs $108 per month. If you want to have that for 20 different micro lambdas that is $2160 And many of those might barely get any traffic. I don't recommend Provisioned concurrency in either of the case but because some people use it so adding it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Over-Engineering Is Good Business (For Them)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Not every “best practice” is about what’s best for your app — sometimes, it’s about what’s best for someone’s business model.&lt;/p&gt;

&lt;p&gt;Some third-party vendors charge &lt;strong&gt;per Lambda function&lt;/strong&gt; or &lt;strong&gt;CloudFormation stack&lt;/strong&gt; , so the more you split things up, the more they profit. Even AWS with &lt;strong&gt;AWS Inspector&lt;/strong&gt; charge &lt;strong&gt;$0.30 per function per month&lt;/strong&gt; , and with dozens of Lambdas, that adds up fast. &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%2Fyjuukrflaexec05c6eht.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%2Fyjuukrflaexec05c6eht.png" alt="Image description" width="765" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lambda Insights also charges you per function.&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%2Fbfhf43qjtic5a45zlglk.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%2Fbfhf43qjtic5a45zlglk.png" alt="Image description" width="800" height="659"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Datadog charges you per active lambda function. &lt;br&gt;
&lt;code&gt;A single billable function is defined by a unique function ARN. In the case of Lambda@Edge functions, each function in a different region is counted as a separate billable function. : Datadog&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;More functions also mean more surface area for observability, monitoring, and security tools — all of which feed into paid services.&lt;/p&gt;

&lt;p&gt;Even worse, some vendors charge &lt;strong&gt;per user&lt;/strong&gt; , and managing a highly fragmented setup often demands &lt;strong&gt;bigger teams&lt;/strong&gt;. That’s great for them — more seats, more subscriptions — but not for you.&lt;/p&gt;

&lt;p&gt;And let’s not forget the gatekeepers who define “proper serverless” in ways that conveniently align with their consulting gigs.&lt;/p&gt;

&lt;p&gt;What looks like architectural purity is often just someone else’s &lt;strong&gt;revenue stream&lt;/strong&gt; in disguise.&lt;/p&gt;

&lt;p&gt;The “one Lambda per endpoint” mantra may sound clean in theory, but in practice, it creates unnecessary complexity, inflates costs, bloats your infrastructure, and hurts developer experience. Lambdas should be shaped by functional boundaries, not URL patterns. Lambdaliths give you better cold start performance, resource reuse, simpler deployments, saves you on developer cost all without sacrificing maintainability.&lt;/p&gt;

</description>
      <category>awslambda</category>
      <category>serverlessarchitecture</category>
      <category>serverless</category>
      <category>lambda</category>
    </item>
  </channel>
</rss>
