<?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: teaganga</title>
    <description>The latest articles on Forem by teaganga (@teaganga).</description>
    <link>https://forem.com/teaganga</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%2F1117555%2F86105290-fbff-4e14-bd75-c5bf611d38c7.png</url>
      <title>Forem: teaganga</title>
      <link>https://forem.com/teaganga</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/teaganga"/>
    <language>en</language>
    <item>
      <title>Using Docker Compose Profiles to unit tests part of the application</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Fri, 20 Feb 2026 09:26:10 +0000</pubDate>
      <link>https://forem.com/teaganga/using-docker-compose-profiles-to-unit-tests-part-of-the-application-56cc</link>
      <guid>https://forem.com/teaganga/using-docker-compose-profiles-to-unit-tests-part-of-the-application-56cc</guid>
      <description>&lt;p&gt;&lt;strong&gt;Compose profiles&lt;/strong&gt; let you group services so you can start only a subset of your stack for specific scenarios (e.g., lightweight testing vs. full production).&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;docker-compose.yml&lt;/code&gt;, add a &lt;code&gt;profiles&lt;/code&gt; list to each service you want to group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;core"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;

  &lt;span class="na"&gt;jobrunner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;core"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;nextjs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;full"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;authserver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;full"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker compose up&lt;/code&gt; (no &lt;code&gt;--profile&lt;/code&gt;)&lt;br&gt;
Starts only services &lt;strong&gt;without&lt;/strong&gt; a &lt;code&gt;profiles&lt;/code&gt; key.&lt;br&gt;
In the example above, nothing would start unless you define a default (non-profiled) service.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker compose --profile core up&lt;/code&gt;&lt;br&gt;
Starts only services tagged with &lt;code&gt;core&lt;/code&gt; (&lt;code&gt;postgres&lt;/code&gt;, &lt;code&gt;jobrunner&lt;/code&gt;).&lt;br&gt;
Ideal for a lightweight testing stack.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;docker compose --profile full up&lt;/code&gt;&lt;br&gt;
Starts only services tagged with &lt;code&gt;full&lt;/code&gt; (&lt;code&gt;nextjs&lt;/code&gt;, &lt;code&gt;authserver&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiple profiles are supported:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; core &lt;span class="nt"&gt;--profile&lt;/span&gt; full up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows you to maintain &lt;strong&gt;one Compose file&lt;/strong&gt; while running minimal stacks for testing or the full environment as needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a service should always run, omit the &lt;code&gt;profiles&lt;/code&gt; key. It will start regardless of profile flags.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Multiple Databases and Profiles
&lt;/h1&gt;

&lt;p&gt;Docker Compose does &lt;strong&gt;not&lt;/strong&gt; allow multiple services with the same name—even across different profiles.&lt;/p&gt;

&lt;p&gt;If you need both a production database and a &lt;strong&gt;unit test database&lt;/strong&gt;, give them unique service names and assign each to its own profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;unit-test-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Database Configuration Strategy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your application containers use &lt;strong&gt;one &lt;code&gt;DATABASE_URL&lt;/code&gt; at a time&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each profile startup determines which database the app connects to.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In a &lt;code&gt;test&lt;/code&gt; profile, set &lt;code&gt;DATABASE_URL&lt;/code&gt; to &lt;code&gt;unit-test-db&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In a &lt;code&gt;prod&lt;/code&gt; profile, set &lt;code&gt;DATABASE_URL&lt;/code&gt; to &lt;code&gt;app-db&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;There is &lt;strong&gt;no runtime toggling&lt;/strong&gt; — each profile spin-up decides which database your app connects to based on the environment variables provided at startup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Alternative Approach
&lt;/h2&gt;

&lt;p&gt;Instead of profiles, you can maintain separate Compose files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker-compose.test.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker-compose.prod.yml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each file can hardcode the correct database host.&lt;/p&gt;

&lt;p&gt;This provides clearer separation but requires maintaining multiple files.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concrete Example with &lt;code&gt;unit-test-db&lt;/code&gt; and &lt;code&gt;app-db&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Below is a clean, complete working example.&lt;/p&gt;




&lt;h2&gt;
  
  
  1️⃣ &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# -----------------&lt;/span&gt;
  &lt;span class="c1"&gt;# Databases&lt;/span&gt;
  &lt;span class="c1"&gt;# -----------------&lt;/span&gt;

  &lt;span class="na"&gt;unit-test-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unit-test-db&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;testdb&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5433:5432"&lt;/span&gt;

  &lt;span class="na"&gt;app-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-db&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proddb&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;

  &lt;span class="c1"&gt;# -----------------&lt;/span&gt;
  &lt;span class="c1"&gt;# Application&lt;/span&gt;
  &lt;span class="c1"&gt;# -----------------&lt;/span&gt;

  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;unit-test-db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-db&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DATABASE_URL}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2️⃣ What &lt;code&gt;DATABASE_URL&lt;/code&gt; Should Look Like
&lt;/h2&gt;

&lt;p&gt;Inside Docker, the hostname is the &lt;strong&gt;service name&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test profile → connect to &lt;code&gt;unit-test-db&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgresql://test:test@unit-test-db:5432/testdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prod profile → connect to &lt;code&gt;app-db&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgresql://prod:prod@app-db:5432/proddb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  3️⃣ Running with Different Environment Variable Sets
&lt;/h1&gt;

&lt;h2&gt;
  
  
  ✅ Option A — Use Separate &lt;code&gt;.env&lt;/code&gt; Files (Recommended)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;.env.test&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL=postgresql://test:test@unit-test-db:5432/testdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;.env.prod&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL=postgresql://prod:prod@app-db:5432/proddb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start test stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env.test up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Start prod stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; prod &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env.prod up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the cleanest and most maintainable approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Option B — Inline Environment Variables
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql://test:test@unit-test-db:5432/testdb &lt;span class="se"&gt;\&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prod
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql://prod:prod@app-db:5432/proddb &lt;span class="se"&gt;\&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; prod up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  4️⃣ What Actually Starts
&lt;/h1&gt;

&lt;p&gt;When you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unit-test-db&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;will start.&lt;/p&gt;

&lt;p&gt;When you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; prod up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;app-db&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;will start.&lt;/p&gt;




&lt;h1&gt;
  
  
  Clean Mental Model
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profile&lt;/th&gt;
&lt;th&gt;DB Container&lt;/th&gt;
&lt;th&gt;DATABASE_URL host&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;test&lt;/td&gt;
&lt;td&gt;unit-test-db&lt;/td&gt;
&lt;td&gt;unit-test-db&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prod&lt;/td&gt;
&lt;td&gt;app-db&lt;/td&gt;
&lt;td&gt;app-db&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;service name becomes the DNS hostname inside Docker’s network&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each profile launch defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which database container runs&lt;/li&gt;
&lt;li&gt;Which &lt;code&gt;DATABASE_URL&lt;/code&gt; your app uses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no live switching — environment selection happens at startup.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>docker</category>
      <category>containers</category>
      <category>database</category>
    </item>
    <item>
      <title>How to Add Vitest to a Cloudflare Worker Project (TypeScript)</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Sun, 25 Jan 2026 08:02:06 +0000</pubDate>
      <link>https://forem.com/teaganga/how-to-add-vitest-to-a-cloudflare-worker-project-typescript-1g14</link>
      <guid>https://forem.com/teaganga/how-to-add-vitest-to-a-cloudflare-worker-project-typescript-1g14</guid>
      <description>&lt;p&gt;Cloudflare Workers are fast, lightweight, and TypeScript-friendly — but testing them often feels confusing at first.&lt;br&gt;
This guide walks through &lt;strong&gt;adding Vitest to an existing Cloudflare Worker project&lt;/strong&gt;, explains &lt;em&gt;why&lt;/em&gt; each step exists, and clears up common confusions (like &lt;code&gt;jsdom&lt;/code&gt;, coverage packages, and where tests should live).&lt;/p&gt;

&lt;p&gt;By the end, you’ll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vitest running cleanly&lt;/li&gt;
&lt;li&gt;A sensible folder structure&lt;/li&gt;
&lt;li&gt;Zero unnecessary dependencies&lt;/li&gt;
&lt;li&gt;A setup that fits Cloudflare Workers (not browsers)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Starting Point: Existing Worker Project
&lt;/h2&gt;

&lt;p&gt;Assume your project looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── package.json
├── package-lock.json
├── public/
│   └── index.html
├── src/
│   └── index.ts
├── tsconfig.json
├── worker-configuration.d.ts
└── wrangler.jsonc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;strong&gt;TypeScript Cloudflare Worker&lt;/strong&gt;, likely using Wrangler.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Vitest?
&lt;/h2&gt;

&lt;p&gt;Vitest is a modern test runner built on Vite. It’s a great fit because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript works out of the box&lt;/li&gt;
&lt;li&gt;Very fast startup and watch mode&lt;/li&gt;
&lt;li&gt;Jest-style API (&lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt;, &lt;code&gt;expect&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Minimal configuration&lt;/li&gt;
&lt;li&gt;Works cleanly with Worker / Node-style environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For new TypeScript projects, Vitest is usually the best choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Install Vitest (and Only What You Need)
&lt;/h2&gt;

&lt;p&gt;Run this once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; vitest @vitest/coverage-v8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What this actually installs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;vitest&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
The test runner itself. This is required.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;@vitest/coverage-v8&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Optional but useful. It enables code-coverage reporting using V8’s native engine.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are installed as &lt;strong&gt;dev dependencies&lt;/strong&gt; (&lt;code&gt;-D&lt;/code&gt;) because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tests run locally or in CI&lt;/li&gt;
&lt;li&gt;they are never deployed to Cloudflare&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ What you do &lt;em&gt;not&lt;/em&gt; need
&lt;/h3&gt;

&lt;p&gt;You might see advice to install &lt;code&gt;jsdom&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Do not install it for Cloudflare Workers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Workers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do not have &lt;code&gt;document&lt;/code&gt; or &lt;code&gt;window&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;are not browser environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So skip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; jsdom   ❌
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Add a Vitest Config
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;vitest.config.ts&lt;/code&gt; at the project root:&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;defineConfig&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;vitest/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node&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;h3&gt;
  
  
  Why &lt;code&gt;environment: 'node'&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Cloudflare Workers behave much closer to &lt;strong&gt;Node-style runtimes&lt;/strong&gt; than browsers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No DOM&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;window&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;document&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using &lt;code&gt;node&lt;/code&gt; avoids fake browser APIs and keeps tests honest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Add Test Scripts
&lt;/h2&gt;

&lt;p&gt;Update &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:run"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:coverage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest run --coverage"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When to use which
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm test&lt;/code&gt; → watch mode (great during development)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run test:run&lt;/code&gt; → one-off run (CI-friendly)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run test:coverage&lt;/code&gt; → generates coverage stats&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 4: Where Should Tests Live?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Put tests inside &lt;code&gt;src/&lt;/code&gt;, next to the code they test.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── index.ts
├── index.test.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the project grows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── handlers/
│   ├── auth.ts
│   └── auth.test.ts
├── utils/
│   ├── math.ts
│   └── math.test.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this works best
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tests stay close to the code&lt;/li&gt;
&lt;li&gt;Imports are simple (&lt;code&gt;./index&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Easy to refactor&lt;/li&gt;
&lt;li&gt;Vitest finds these files automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vitest already looks for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**/*.test.ts
**/*.spec.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No extra configuration needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Example Test
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/index.ts&lt;/code&gt;
&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&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;b&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;src/index.test.ts&lt;/code&gt;
&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&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;vitest&lt;/span&gt;&lt;span class="dl"&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;add&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;./index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;adds two numbers&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&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="mi"&gt;2&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="nf"&gt;toBe&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="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;Run it:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Or once:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Make Sure Tests Aren’t Deployed
&lt;/h2&gt;

&lt;p&gt;Ensure your &lt;code&gt;tsconfig.json&lt;/code&gt; excludes test files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.ts"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.test.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.spec.ts"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test files are never bundled&lt;/li&gt;
&lt;li&gt;Workers stay lean&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Use &lt;strong&gt;Vitest&lt;/strong&gt; for Cloudflare Workers&lt;/li&gt;
&lt;li&gt;✅ Install &lt;code&gt;vitest&lt;/code&gt; (coverage optional)&lt;/li&gt;
&lt;li&gt;❌ Do not install &lt;code&gt;jsdom&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;📁 Put tests inside &lt;code&gt;src/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;⚙️ Use &lt;code&gt;environment: 'node'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;🚀 You’re production-ready and CI-friendly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup is minimal, modern, and scales cleanly as your Worker grows.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Debugging Cloudflare Workers with VS Code: Queue Handlers Edition (Part 4)</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Wed, 10 Dec 2025 13:47:55 +0000</pubDate>
      <link>https://forem.com/teaganga/debugging-cloudflare-workers-with-vs-code-queue-handlers-edition-part-4-7i5</link>
      <guid>https://forem.com/teaganga/debugging-cloudflare-workers-with-vs-code-queue-handlers-edition-part-4-7i5</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/teaganga/testing-cloudflare-workers-locally-queues-cron-and-http-part-3-4ilp"&gt;Part 3&lt;/a&gt;, I showed you how to test queues, cron, and HTTP handlers locally. But here's the question I kept asking: &lt;strong&gt;can I actually debug this stuff with breakpoints?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Short answer: &lt;strong&gt;Yes, absolutely.&lt;/strong&gt; And it works better than I expected.&lt;/p&gt;

&lt;p&gt;Let me show you how to set it up.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup: VS Code + Wrangler Dev + Debugger
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;wrangler dev&lt;/code&gt;, it starts a Node.js process that runs your Worker. VS Code's debugger can attach to that process, which means you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Set breakpoints in your &lt;code&gt;queue()&lt;/code&gt; handler&lt;/li&gt;
&lt;li&gt;✅ Set breakpoints in your &lt;code&gt;scheduled()&lt;/code&gt; handler&lt;/li&gt;
&lt;li&gt;✅ Set breakpoints in your &lt;code&gt;fetch()&lt;/code&gt; handler&lt;/li&gt;
&lt;li&gt;✅ Inspect variables, step through code, everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how to set it up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Run Wrangler in Debug Mode
&lt;/h2&gt;

&lt;p&gt;Start Wrangler with the inspect flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler dev &lt;span class="nt"&gt;--remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--inspect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important flags:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--remote=false&lt;/code&gt; - runs locally (not in Cloudflare's dev environment)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--inspect&lt;/code&gt; - enables the Node.js debugger on port 9229&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should see output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Debugger listening on ws://127.0.0.1:9229/...
For help, see: https://nodejs.org/en/docs/inspector
⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;Debugger listening&lt;/code&gt; line means you're good to go.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Configure VS Code
&lt;/h2&gt;

&lt;p&gt;Create or update &lt;code&gt;.vscode/launch.json&lt;/code&gt; in your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Attach to Wrangler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"skipFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;node_internals&amp;gt;/**"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sourceMaps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resolveSourceMapLocations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"!**/node_modules/**"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attaches VS Code's debugger to the Wrangler process&lt;/li&gt;
&lt;li&gt;Skips Node.js internal files (you don't want to debug Node itself)&lt;/li&gt;
&lt;li&gt;Enables source maps so breakpoints work with TypeScript&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Start Debugging
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run Wrangler&lt;/strong&gt; in a terminal:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   wrangler dev &lt;span class="nt"&gt;--remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--inspect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open VS Code's Debug panel&lt;/strong&gt; (&lt;code&gt;Cmd+Shift+D&lt;/code&gt; on Mac, &lt;code&gt;Ctrl+Shift+D&lt;/code&gt; on Windows)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Select "Attach to Wrangler"&lt;/strong&gt; from the dropdown&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Press the green play button&lt;/strong&gt; (or press &lt;code&gt;F5&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should see "Debugger attached" in your terminal, and VS Code's debug toolbar appears.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Set Breakpoints and Trigger Queue Messages
&lt;/h2&gt;

&lt;p&gt;Now the magic happens. Let's debug a queue handler:&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;// src/index.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&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;`[queue] Received &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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="s2"&gt; messages`&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 👈 Set a breakpoint on this line&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;`[queue] Processing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// 👈 And another one here&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processJob&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;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&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;p&gt;&lt;strong&gt;Set breakpoints:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click in the gutter (left of line numbers) in VS Code&lt;/li&gt;
&lt;li&gt;A red dot appears - that's your breakpoint&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Trigger a queue message:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler queues send my-jobs &lt;span class="s1"&gt;'{"type": "test", "data": "hello"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🎉 Your breakpoint hits!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VS Code pauses execution, and you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspect &lt;code&gt;message.body&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check the value of &lt;code&gt;env&lt;/code&gt; bindings&lt;/li&gt;
&lt;li&gt;Step through the code line by line&lt;/li&gt;
&lt;li&gt;See the call stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is &lt;strong&gt;incredibly useful&lt;/strong&gt; for debugging complex queue logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging All Three Handler Types
&lt;/h2&gt;

&lt;h3&gt;
  
  
  HTTP Handlers (&lt;code&gt;fetch&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Set a breakpoint in your fetch handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;url&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 👈 Breakpoint here&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;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/trigger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&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;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manual&lt;/span&gt;&lt;span class="dl"&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then hit the endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8787/api/trigger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The debugger pauses, and you can inspect the &lt;code&gt;request&lt;/code&gt; object, headers, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled Handlers (&lt;code&gt;scheduled&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Set a breakpoint in your scheduled handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 👈 Breakpoint here&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;time&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheduledTime&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;[scheduled] Running at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;time&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trigger it by pressing &lt;strong&gt;&lt;code&gt;s&lt;/code&gt;&lt;/strong&gt; in the Wrangler terminal (or use the &lt;code&gt;/__scheduled&lt;/code&gt; endpoint).&lt;/p&gt;

&lt;p&gt;The debugger pauses, and you can see the &lt;code&gt;event.scheduledTime&lt;/code&gt; value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queue Handlers (&lt;code&gt;queue&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;We already covered this above, but here's a more complex example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 👈 Breakpoint here to inspect message structure&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseJobMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 👈 Another breakpoint to step through processing&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processJob&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;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 👈 Breakpoint to verify ack is called&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 👈 Breakpoint to debug failures&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Job failed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retry&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler queues send my-jobs &lt;span class="s1"&gt;'{"id": 1}'&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"id": 2}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can step through each message in the batch and see exactly what's happening.&lt;/p&gt;




&lt;h2&gt;
  
  
  Advanced Debugging Techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Conditional Breakpoints
&lt;/h3&gt;

&lt;p&gt;Right-click a breakpoint in VS Code → "Edit Breakpoint" → "Expression"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the breakpoint only hits for messages with &lt;code&gt;id: 42&lt;/code&gt;. Super useful when debugging batches.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Logpoints
&lt;/h3&gt;

&lt;p&gt;Don't want to stop execution? Use a logpoint instead:&lt;/p&gt;

&lt;p&gt;Right-click → "Add Logpoint"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This logs without pausing, like &lt;code&gt;console.log&lt;/code&gt; but without modifying your code.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Watch Expressions
&lt;/h3&gt;

&lt;p&gt;In VS Code's Debug panel, add Watch expressions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&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;DATABASE_URL&lt;/span&gt;
&lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These update automatically as you step through code.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Debug Console
&lt;/h3&gt;

&lt;p&gt;While paused, you can run code in the Debug Console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Check if a binding exists&lt;/span&gt;
&lt;span class="o"&gt;&amp;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;MY_QUEUE&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Inspect message body&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&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="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Test a function&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parseJobMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;p&gt;This is &lt;strong&gt;amazing&lt;/strong&gt; for exploring objects and testing fixes without restarting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Issues and Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue 1: Breakpoints Show as Gray/Unverified
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Source maps aren't working correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Make sure your &lt;code&gt;tsconfig.json&lt;/code&gt; has source maps enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sourceMap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"inlineSourceMap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And restart both Wrangler and the VS Code debugger.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue 2: "Cannot connect to runtime process"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Wrangler isn't running with &lt;code&gt;--inspect&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Make sure you started Wrangler with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler dev &lt;span class="nt"&gt;--remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--inspect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 3: Debugger Disconnects After First Request
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; This can happen with certain Wrangler versions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Use &lt;code&gt;--persist-to&lt;/code&gt; to keep the Worker alive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler dev &lt;span class="nt"&gt;--remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--inspect&lt;/span&gt; &lt;span class="nt"&gt;--persist-to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.wrangler/state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 4: Can't Debug External Dependencies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Breakpoints in &lt;code&gt;node_modules&lt;/code&gt; don't work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Remove this line from &lt;code&gt;launch.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"skipFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;node_internals&amp;gt;/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But be warned - you'll hit a LOT of Node.js internal breakpoints.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Debugging Workflow
&lt;/h2&gt;

&lt;p&gt;Here's how I debug a typical queue handler issue:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Reproduce the Issue Locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1: Start Wrangler&lt;/span&gt;
wrangler dev &lt;span class="nt"&gt;--remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--inspect&lt;/span&gt;

&lt;span class="c"&gt;# Terminal 2: Send problematic message&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"id": 123, "type": "problematic"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Attach Debugger
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Open VS Code&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;F5&lt;/code&gt; to attach&lt;/li&gt;
&lt;li&gt;Set breakpoints in the queue handler&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Re-trigger
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Send the same message again&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"id": 123, "type": "problematic"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debugger hits, and I can step through to find the issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Test the Fix
&lt;/h3&gt;

&lt;p&gt;Fix the code, save (Wrangler hot-reloads), trigger again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler queues send my-jobs &lt;span class="s1"&gt;'{"id": 123, "type": "problematic"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it works, remove breakpoints and move on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pro Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Multiple Launch Configurations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Attach to Wrangler (Local)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9229&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Attach to Wrangler (Remote)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"localRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"remoteRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/tmp/worker"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch between them based on whether you're using &lt;code&gt;--remote=false&lt;/code&gt; or not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Debug Script
&lt;/h3&gt;

&lt;p&gt;Add to &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wrangler dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev:debug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wrangler dev --remote=false --inspect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wrangler queues send my-jobs '{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}'"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can just run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Debugger Statements
&lt;/h3&gt;

&lt;p&gt;Sometimes I just add &lt;code&gt;debugger;&lt;/code&gt; directly in the code:&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="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;debugger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Automatic breakpoint when debugger is attached&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;processJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is faster than clicking around in VS Code when you know exactly where to break.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging Production Issues Locally
&lt;/h2&gt;

&lt;p&gt;Sometimes you get a failed message in production and want to replay it locally:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Get the Message from Your DLQ
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List messages in your dead letter queue&lt;/span&gt;
wrangler queues messages list my-jobs-dlq

&lt;span class="c"&gt;# Copy the message body&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Replay it Locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Paste the exact production message&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"id": 123, "data": "..."}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Debug With Real Data
&lt;/h3&gt;

&lt;p&gt;Now you're debugging with the &lt;strong&gt;actual message that failed&lt;/strong&gt;, which makes finding bugs way easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Debugging
&lt;/h2&gt;

&lt;p&gt;You can also profile your queue handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&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;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;for &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;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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;jobStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;jobStart&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;`Job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobTime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&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;totalTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&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;`Batch of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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="s2"&gt; took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;totalTime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a breakpoint after the loop and inspect &lt;code&gt;totalTime&lt;/code&gt; to see if you're hitting performance issues.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Yes, you can absolutely debug queue handlers with VS Code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In fact, you can debug all three handler types (HTTP, cron, queue) with full breakpoint support, variable inspection, and step-through debugging.&lt;/p&gt;

&lt;p&gt;The setup is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;wrangler dev --remote=false --inspect&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Configure VS Code's &lt;code&gt;launch.json&lt;/code&gt; to attach on port 9229&lt;/li&gt;
&lt;li&gt;Set breakpoints and trigger your handlers&lt;/li&gt;
&lt;li&gt;Debug like any other Node.js app&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This has &lt;strong&gt;completely changed&lt;/strong&gt; how I develop Workers. I rarely use &lt;code&gt;console.log&lt;/code&gt; debugging anymore because breakpoints are so much faster.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Drop them in the comments! What's your debugging workflow like?&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start Wrangler in debug mode&lt;/span&gt;
wrangler dev &lt;span class="nt"&gt;--remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--inspect&lt;/span&gt;

&lt;span class="c"&gt;# Send test queue message&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"test": true}'&lt;/span&gt;

&lt;span class="c"&gt;# Trigger scheduled event&lt;/span&gt;
&lt;span class="c"&gt;# Press 's' in Wrangler terminal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.vscode/launch.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Attach to Wrangler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"skipFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;node_internals&amp;gt;/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy debugging!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>javascript</category>
      <category>vscode</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Testing Cloudflare Workers Locally: Queues, Cron, and HTTP (Part 3)</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Wed, 10 Dec 2025 13:28:27 +0000</pubDate>
      <link>https://forem.com/teaganga/testing-cloudflare-workers-locally-queues-cron-and-http-part-3-4ilp</link>
      <guid>https://forem.com/teaganga/testing-cloudflare-workers-locally-queues-cron-and-http-part-3-4ilp</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/teaganga/triggering-long-jobs-in-cloudflare-workers-8mh"&gt;Part 1&lt;/a&gt;, I explained why Queues solve the long-running job problem. In &lt;a href="https://dev.to/teaganga/adding-queue-and-cron-handlers-to-your-cloudflare-worker-part-2-5ge7"&gt;Part 2&lt;/a&gt;, I showed you how to set up all three handler types.&lt;/p&gt;

&lt;p&gt;Now let's talk about the part that tripped me up the most: &lt;strong&gt;how do you actually test this stuff locally?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I spent way too long trying to figure out if I needed to deploy to production just to test a queue message. Turns out, Wrangler has everything built-in—once you know how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge: Three Different Handler Types
&lt;/h2&gt;

&lt;p&gt;Your Worker now has three ways to get invoked:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HTTP requests&lt;/strong&gt; → &lt;code&gt;fetch()&lt;/code&gt; handler&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron schedules&lt;/strong&gt; → &lt;code&gt;scheduled()&lt;/code&gt; handler
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue messages&lt;/strong&gt; → &lt;code&gt;queue()&lt;/code&gt; handler&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each one needs to be tested differently because Cloudflare doesn't "magically fire" cron or queue events in dev mode. You have to manually trigger them.&lt;/p&gt;

&lt;p&gt;Here's how I do it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Start Your Dev Server
&lt;/h2&gt;

&lt;p&gt;First, get your Worker running locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Your HTTP server (usually on &lt;code&gt;localhost:8787&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ Your queue consumer (waiting for messages)&lt;/li&gt;
&lt;li&gt;✅ Your scheduled handler (waiting for triggers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Wrangler creates a &lt;strong&gt;real local queue&lt;/strong&gt; in memory. You don't need any external services or database. It's all built-in.&lt;/p&gt;

&lt;p&gt;You should see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Testing HTTP Handlers (Easy)
&lt;/h2&gt;

&lt;p&gt;This is the straightforward one. Just hit your endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8787/api/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or open it in your browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8787/admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;fetch()&lt;/code&gt; handler runs exactly like it would in production. Nothing special needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Testing Queue Handlers (The Tricky One)
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. Your queue consumer is running, but &lt;strong&gt;it won't receive messages automatically&lt;/strong&gt;. You have to send them manually.&lt;/p&gt;

&lt;p&gt;Wrangler provides a CLI command for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler queues send domain-jobs &lt;span class="s1"&gt;'{"type": "test-job", "data": "hello"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;domain-jobs&lt;/code&gt; with whatever your queue name is (from your &lt;code&gt;wrangler.jsonc&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The message goes into the local in-memory queue&lt;/li&gt;
&lt;li&gt;Wrangler immediately delivers it to your &lt;code&gt;queue()&lt;/code&gt; handler&lt;/li&gt;
&lt;li&gt;You see the logs in your terminal&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should see output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[queue] Processing 1 messages
[queue] Starting job abc-123
[queue] Job complete!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Batch Delivery
&lt;/h3&gt;

&lt;p&gt;Want to test how your Worker handles multiple messages? Send several in quick succession:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler queues send domain-jobs &lt;span class="s1"&gt;'{"id": 1}'&lt;/span&gt;
wrangler queues send domain-jobs &lt;span class="s1"&gt;'{"id": 2}'&lt;/span&gt;
wrangler queues send domain-jobs &lt;span class="s1"&gt;'{"id": 3}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on your &lt;code&gt;max_batch_size&lt;/code&gt; and timing, Wrangler might deliver these as one batch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[queue] Processing 3 messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you test your batching logic locally before deploying.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Testing Scheduled Handlers (Cron)
&lt;/h2&gt;

&lt;p&gt;Cron jobs don't fire automatically in dev mode (thank goodness—imagine getting pinged every minute while coding).&lt;/p&gt;

&lt;p&gt;You have two ways to trigger them:&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Press a Key
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;wrangler dev&lt;/code&gt; is running, press the &lt;strong&gt;&lt;code&gt;s&lt;/code&gt;&lt;/strong&gt; key in your terminal.&lt;/p&gt;

&lt;p&gt;This sends a scheduled event to your Worker. You'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trigger schedule event (s to trigger)
[scheduled] Running scheduled job...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option B: HTTP Endpoint
&lt;/h3&gt;

&lt;p&gt;Wrangler exposes a special endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"http://localhost:8787/__scheduled?cron=*+*+*+*+*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This manually fires your &lt;code&gt;scheduled()&lt;/code&gt; handler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; I add a test-only endpoint in development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;url&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Only in development&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;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/__test-cron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scheduledTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;cron&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="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cron simulated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... rest of your handlers&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;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&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;[scheduled] Running at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheduledTime&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// Your job logic 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;p&gt;Then I can just hit &lt;code&gt;http://localhost:8787/__test-cron&lt;/code&gt; in my browser during development.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Local Testing Workflow
&lt;/h2&gt;

&lt;p&gt;Here's my typical dev loop:&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminal 1: Run the Worker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave this running. Watch the logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminal 2: Send Test Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test HTTP&lt;/span&gt;
curl http://localhost:8787/api/health

&lt;span class="c"&gt;# Test queue&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"test": true}'&lt;/span&gt;

&lt;span class="c"&gt;# Test multiple messages&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..5&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;wrangler queues send my-jobs &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Terminal 1: Press Keys to Trigger Events
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;s&lt;/code&gt;&lt;/strong&gt; to fire a scheduled event&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;c&lt;/code&gt;&lt;/strong&gt; to clear the console&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;&lt;code&gt;x&lt;/code&gt;&lt;/strong&gt; to exit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives me full control over when things execute, which is perfect for debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the Local Queue
&lt;/h2&gt;

&lt;p&gt;When I first started, I wondered: "Is this a &lt;em&gt;real&lt;/em&gt; queue, or just a mock?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's real.&lt;/strong&gt; Here's what you get locally:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Local Queue&lt;/th&gt;
&lt;th&gt;Production Queue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Batch delivery&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;message.ack()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;message.retry()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic retries&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dead letter queue&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;❌ No (in-memory)&lt;/td&gt;
&lt;td&gt;✅ Yes (durable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delays/scheduling&lt;/td&gt;
&lt;td&gt;⚠️ Simplified&lt;/td&gt;
&lt;td&gt;✅ Full featured&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The key difference:&lt;/strong&gt; Local queues are &lt;strong&gt;in-memory only&lt;/strong&gt;. When you stop Wrangler, the queue disappears.&lt;/p&gt;

&lt;p&gt;But for development, this is perfect. You get full queue semantics without needing any infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Queue Configuration Deep Dive
&lt;/h2&gt;

&lt;p&gt;Let me show you the correct &lt;code&gt;wrangler.jsonc&lt;/code&gt; format, because I got this wrong the first time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-worker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/index.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"queues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// Producer: lets you SEND messages&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"producers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"background-jobs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"binding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JOB_QUEUE"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Used in env.JOB_QUEUE.send()&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c1"&gt;// Consumer: lets you RECEIVE messages&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"consumers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"background-jobs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Must match producer queue name&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max_batch_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Max messages per batch&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max_batch_timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;// Max seconds to wait for full batch&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max_retries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c1"&gt;// Retry failed messages up to 10x&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"dead_letter_queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"background-jobs-dlq"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Where failed messages go&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;producers&lt;/code&gt; need a &lt;strong&gt;binding&lt;/strong&gt; (that's how you reference it in code)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;consumers&lt;/code&gt; do &lt;strong&gt;NOT&lt;/strong&gt; have a binding (you just implement the handler)&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;queue name&lt;/strong&gt; connects producers to consumers&lt;/li&gt;
&lt;li&gt;You can have multiple producers sending to the same queue&lt;/li&gt;
&lt;li&gt;You can have multiple consumers processing the same queue (in different Workers)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dead Letter Queue Setup
&lt;/h3&gt;

&lt;p&gt;If you configure a &lt;code&gt;dead_letter_queue&lt;/code&gt;, you need to also consume it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"queues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"producers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main-jobs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"binding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MAIN_QUEUE"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"consumers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main-jobs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max_retries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"dead_letter_queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"failed-jobs"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"queue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"failed-jobs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Consume the DLQ&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max_batch_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// Process failures carefully&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then handle DLQ messages differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Check if this is from the DLQ&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed-jobs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;logFailureForManualReview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Don't retry again&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;// Normal processing&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Debugging Tips I Wish I Knew Earlier
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Use Structured Logging
&lt;/h3&gt;

&lt;p&gt;Don't just &lt;code&gt;console.log('queue handler')&lt;/code&gt;. Include context:&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="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&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;`[queue] Received &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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="s2"&gt; messages`&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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;`[queue] Processing message &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&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;`[queue] ✓ Message &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; complete`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[queue] ✗ Message &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retry&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;p&gt;This makes it &lt;strong&gt;so much easier&lt;/strong&gt; to see what's happening when you have multiple messages in flight.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Test Retry Logic Explicitly
&lt;/h3&gt;

&lt;p&gt;Force a failure to see if retries work:&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;# Send a message that will fail&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"shouldFail": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shouldFail&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;`[queue] Simulating failure (attempt &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;continue&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="nf"&gt;processJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch your logs to see the retry attempts increment.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Simulate Production Conditions
&lt;/h3&gt;

&lt;p&gt;Test with realistic batch sizes:&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;# Send 10 messages quickly&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..10&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;wrangler queues send my-jobs &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &amp;amp;
&lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;wait&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps you catch race conditions or batching issues before deploying.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing Scheduled Events Realistically
&lt;/h2&gt;

&lt;p&gt;Here's a pattern I use to test different cron schedules:&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="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&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;hour&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheduledTime&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getUTCHours&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Different behavior based on time&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;hour&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;6&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="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;[scheduled] Running full refresh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;full-refresh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;[scheduled] Running incremental update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;incremental&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test different times:&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;# Simulate 6am UTC&lt;/span&gt;
curl &lt;span class="s2"&gt;"http://localhost:8787/__scheduled?scheduledTime=2024-01-01T06:00:00Z"&lt;/span&gt;

&lt;span class="c"&gt;# Simulate 3pm UTC  &lt;/span&gt;
curl &lt;span class="s2"&gt;"http://localhost:8787/__scheduled?scheduledTime=2024-01-01T15:00:00Z"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets me verify the logic works correctly at different hours without waiting for cron to actually fire.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Gotchas I Ran Into
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Gotcha 1: Queue Messages Aren't Persisted Locally
&lt;/h3&gt;

&lt;p&gt;If you send messages and then restart &lt;code&gt;wrangler dev&lt;/code&gt;, they're gone. The local queue is in-memory only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Use a script to set up test data:&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;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# setup-test-queue.sh&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Sending test messages..."&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"id": 1, "type": "test"}'&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"id": 2, "type": "test"}'&lt;/span&gt;
wrangler queues send my-jobs &lt;span class="s1"&gt;'{"id": 3, "type": "test"}'&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Done!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this after starting dev mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gotcha 2: Bindings Don't Auto-Update
&lt;/h3&gt;

&lt;p&gt;If you change your &lt;code&gt;wrangler.jsonc&lt;/code&gt; bindings, you need to &lt;strong&gt;restart&lt;/strong&gt; &lt;code&gt;wrangler dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Just saving the file isn't enough. Stop and restart the dev server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gotcha 3: Environment Variables
&lt;/h3&gt;

&lt;p&gt;Make sure you have your env vars set locally:&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;# .dev.vars file&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:5432
&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;test-key-123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrangler loads these automatically in dev mode.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Complete Testing Checklist
&lt;/h2&gt;

&lt;p&gt;Before deploying, I test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] HTTP endpoints respond correctly&lt;/li&gt;
&lt;li&gt;[ ] Queue messages get processed&lt;/li&gt;
&lt;li&gt;[ ] Batch processing handles multiple messages&lt;/li&gt;
&lt;li&gt;[ ] Failed messages retry correctly&lt;/li&gt;
&lt;li&gt;[ ] Scheduled events trigger the right jobs&lt;/li&gt;
&lt;li&gt;[ ] All three handlers can access environment bindings&lt;/li&gt;
&lt;li&gt;[ ] Error handling works as expected&lt;/li&gt;
&lt;li&gt;[ ] Logs are clear and informative&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This usually takes 10-15 minutes and catches 95% of issues before they hit production.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About Integration Tests?
&lt;/h2&gt;

&lt;p&gt;For unit tests, you can mock the queue:&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;// tests/queue.test.ts&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;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;test&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;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processes messages correctly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockEnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&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;await&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mockEnv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&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;ack&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalled&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;But honestly? &lt;strong&gt;I prefer testing with real Wrangler&lt;/strong&gt; because it catches configuration issues that mocks don't.&lt;/p&gt;




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

&lt;p&gt;Testing Workers locally is easier than I expected once I understood the tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wrangler dev&lt;/code&gt; gives you a real local queue&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wrangler queues send&lt;/code&gt; lets you trigger queue messages&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;s&lt;/code&gt; to trigger scheduled events&lt;/li&gt;
&lt;li&gt;Everything runs in-memory, nothing to configure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The local dev experience is actually &lt;em&gt;really good&lt;/em&gt;. I rarely need to deploy to staging anymore just to test something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next time:&lt;/strong&gt; I'll show you how to set up monitoring and alerts so you know when things break in production. Because they will. 😅&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Questions? Hit me in the comments!&lt;/strong&gt; What's your local testing workflow like?&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>serverless</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Adding Queue and Cron Handlers to Your Cloudflare Worker (Part 2)</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Wed, 10 Dec 2025 13:21:20 +0000</pubDate>
      <link>https://forem.com/teaganga/adding-queue-and-cron-handlers-to-your-cloudflare-worker-part-2-5ge7</link>
      <guid>https://forem.com/teaganga/adding-queue-and-cron-handlers-to-your-cloudflare-worker-part-2-5ge7</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/teaganga/triggering-long-jobs-in-cloudflare-workers-8mh"&gt;Part 1&lt;/a&gt;, I explained why Queues are the right solution for long-running background jobs. Now let me show you &lt;strong&gt;exactly how to set it up&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the copy-paste-ready guide I wish I had when I started.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;By the end of this post, your Worker will support &lt;strong&gt;all three invocation types&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HTTP&lt;/strong&gt; (&lt;code&gt;fetch&lt;/code&gt;) - your API and UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron&lt;/strong&gt; (&lt;code&gt;scheduled&lt;/code&gt;) - automatic periodic jobs
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue&lt;/strong&gt; (&lt;code&gt;queue&lt;/code&gt;) - on-demand background processing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All in one deployment, using Cloudflare's official Worker runtime.&lt;/p&gt;

&lt;p&gt;Let's go step by step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Add Queue Configuration
&lt;/h2&gt;

&lt;p&gt;First, open your &lt;code&gt;wrangler.toml&lt;/code&gt; (or &lt;code&gt;wrangler.jsonc&lt;/code&gt;) and add a queue binding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# wrangler.toml&lt;/span&gt;

&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-worker"&lt;/span&gt;
&lt;span class="py"&gt;main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/index.ts"&lt;/span&gt;

&lt;span class="nn"&gt;[[queues.producers]]&lt;/span&gt;
&lt;span class="py"&gt;queue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"background-jobs"&lt;/span&gt;
&lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"JOB_QUEUE"&lt;/span&gt;

&lt;span class="nn"&gt;[[queues.consumers]]&lt;/span&gt;
&lt;span class="py"&gt;queue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"background-jobs"&lt;/span&gt;
&lt;span class="py"&gt;max_batch_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="py"&gt;max_batch_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;producers&lt;/code&gt; - lets your Worker &lt;strong&gt;send&lt;/strong&gt; messages to the queue&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;consumers&lt;/code&gt; - lets your Worker &lt;strong&gt;receive&lt;/strong&gt; messages from the queue&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;binding&lt;/code&gt; - the name you'll use in your code (like &lt;code&gt;env.JOB_QUEUE&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The queue name (&lt;code&gt;background-jobs&lt;/code&gt;) connects producers and consumers. Use the same name for both.&lt;/p&gt;

&lt;p&gt;Now deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloudflare will automatically create the queue if it doesn't exist yet. No manual setup needed!&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Add Cron Schedule
&lt;/h2&gt;

&lt;p&gt;While you're in &lt;code&gt;wrangler.toml&lt;/code&gt;, add a cron trigger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# wrangler.toml (continued)&lt;/span&gt;

&lt;span class="nn"&gt;[triggers]&lt;/span&gt;
&lt;span class="py"&gt;crons&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"0 */6 * * *"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c"&gt;# Every 6 hours&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses standard cron syntax:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;*/10 * * * *&lt;/code&gt; - every 10 minutes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0 2 * * *&lt;/code&gt; - daily at 2am UTC&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0 */6 * * *&lt;/code&gt; - every 6 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can add multiple schedules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[triggers]&lt;/span&gt;
&lt;span class="py"&gt;crons&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"0 2 * * *"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c"&gt;# Daily refresh&lt;/span&gt;
  &lt;span class="s"&gt;"*/30 * * * *"&lt;/span&gt;    &lt;span class="c"&gt;# Health check every 30 min&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Add the Handler Functions
&lt;/h2&gt;

&lt;p&gt;Now the fun part. Your Worker needs to export an object with all three handlers:&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;// src/index.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1️⃣ HTTP Handler&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;url&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/trigger-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Enqueue a background job&lt;/span&gt;
      &lt;span class="k"&gt;await&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;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heavy-processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;requestedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Job queued successfully!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;202&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// 2️⃣ Cron Handler&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&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;[cron] Running scheduled job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Option A: Do the work directly&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doPeriodicMaintenance&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="c1"&gt;// Option B: Enqueue work for the queue handler&lt;/span&gt;
    &lt;span class="k"&gt;await&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;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scheduled-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;scheduledTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scheduledTime&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;[cron] Job complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// 3️⃣ Queue Handler&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&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;`[queue] Processing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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="s2"&gt; messages`&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;`[queue] Starting job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processHeavyJob&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Mark as successfully processed&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[queue] Job failed:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Return to queue for retry&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retry&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each handler gets &lt;code&gt;env&lt;/code&gt; (bindings) and &lt;code&gt;ctx&lt;/code&gt; (execution context)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message.ack()&lt;/code&gt; tells Cloudflare "this message is done"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message.retry()&lt;/code&gt; puts it back in the queue for another attempt&lt;/li&gt;
&lt;li&gt;You can enqueue from &lt;strong&gt;any&lt;/strong&gt; handler (HTTP, cron, or even queue)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 4: Implement Your Job Logic
&lt;/h2&gt;

&lt;p&gt;Here's a clean pattern I use to keep job logic separate:&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;// src/jobs/processor.ts&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processHeavyJob&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;payload&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;Starting heavy 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;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// This can run for minutes with unlimited CPU time!&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchExternalAPIs&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;processLargeDataset&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;writeResultsToStorage&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;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;Job complete!&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;Then import it in your main Worker:&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;// src/index.ts&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;processHeavyJob&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;./jobs/processor&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="k"&gt;default&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;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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="nf"&gt;processHeavyJob&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&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;p&gt;This keeps your handlers clean and your job logic testable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Trigger It From Your Admin UI
&lt;/h2&gt;

&lt;p&gt;Now when an admin clicks "Run Job" in your UI:&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;// Inside your fetch handler&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;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/run-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Validate admin auth first!&lt;/span&gt;

  &lt;span class="k"&gt;await&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;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin-triggered&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin-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;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="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="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Job queued&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request returns &lt;strong&gt;instantly&lt;/strong&gt; (under 1ms typically), and the job runs in the background with no time limits.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding What You Just Built
&lt;/h2&gt;

&lt;p&gt;You now have three ways to trigger work:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Handler&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;CPU Limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP Request&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fetch()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User actions, API calls&lt;/td&gt;
&lt;td&gt;10-50ms*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cron Schedule&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;scheduled()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Periodic maintenance&lt;/td&gt;
&lt;td&gt;30s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Queue Message&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;queue()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Heavy background jobs&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Unlimited&lt;/strong&gt; ⚡&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*50ms on paid plans, 30s on Business+&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The magic:&lt;/strong&gt; Queue handlers have unlimited CPU time. You can process for minutes without hitting limits.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Patterns I Use
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 1: Immediate + Scheduled
&lt;/h3&gt;

&lt;p&gt;Use cron for regular updates, but let admins trigger on-demand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/refresh-now&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&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;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manual&lt;/span&gt;&lt;span class="dl"&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Same job, runs automatically every 6 hours&lt;/span&gt;
    &lt;span class="k"&gt;await&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;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Processes both manual and automatic triggers&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;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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="nf"&gt;doTheWork&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;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&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;
  
  
  Pattern 2: Progressive Work
&lt;/h3&gt;

&lt;p&gt;Break huge jobs into chunks:&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="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Process this batch&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// If more work remains, enqueue next chunk&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;cursor&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;items&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;fetchNextBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nextCursor&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you process unlimited amounts of data without hitting timeouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Dead Letter Queue
&lt;/h3&gt;

&lt;p&gt;Track failed jobs:&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="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Save to storage for manual review&lt;/span&gt;
        &lt;span class="k"&gt;await&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;FAILED_JOBS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Don't retry again&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="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Try again&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Should You Keep Everything in One Worker?
&lt;/h2&gt;

&lt;p&gt;You &lt;strong&gt;can&lt;/strong&gt; put all three handlers in one Worker. They don't compete for resources at runtime—each invocation is isolated.&lt;/p&gt;

&lt;p&gt;But I usually split them once my job logic gets heavy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worker 1: API &amp;amp; UI&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handles &lt;code&gt;fetch()&lt;/code&gt; only&lt;/li&gt;
&lt;li&gt;Stays small and fast&lt;/li&gt;
&lt;li&gt;Just enqueues messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worker 2: Job Runner&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handles &lt;code&gt;queue()&lt;/code&gt; and &lt;code&gt;scheduled()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Can import heavy dependencies&lt;/li&gt;
&lt;li&gt;Focuses on background work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You configure this by creating two separate &lt;code&gt;wrangler.toml&lt;/code&gt; files (or two directories), and pointing the queue consumer to the job worker.&lt;/p&gt;

&lt;p&gt;I'll cover this architecture in Part 3 if there's interest!&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing Your Handlers Locally
&lt;/h2&gt;

&lt;p&gt;Cloudflare's dev server supports all three handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;To test queues locally:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1: Run your worker&lt;/span&gt;
wrangler dev

&lt;span class="c"&gt;# Terminal 2: Send a test message&lt;/span&gt;
wrangler queues producer send background-jobs &lt;span class="s1"&gt;'{"test": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;To test cron locally:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cron doesn't run in dev mode, but you can simulate it:&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;// Add a test endpoint&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;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/__test-cron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;scheduledTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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;ctx&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cron simulated&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;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;You now have a Worker that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Serve HTTP traffic quickly&lt;/li&gt;
&lt;li&gt;✅ Run scheduled maintenance jobs&lt;/li&gt;
&lt;li&gt;✅ Process long-running background work on-demand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete code is less than 100 lines, and it gives you the full power of Cloudflare's infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add monitoring (I use Sentry)&lt;/li&gt;
&lt;li&gt;Set up alerts for failed jobs&lt;/li&gt;
&lt;li&gt;Consider splitting into multiple Workers as you scale&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What would you like to see in Part 3?&lt;/strong&gt; Two-Worker architecture? Error handling patterns? Let me know in the comments!&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Full minimal example&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;work&lt;/span&gt;&lt;span class="dl"&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Queued!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JOB_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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="nf"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# wrangler.toml&lt;/span&gt;
&lt;span class="nn"&gt;[[queues.producers]]&lt;/span&gt;
&lt;span class="py"&gt;queue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-jobs"&lt;/span&gt;
&lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"JOB_QUEUE"&lt;/span&gt;

&lt;span class="nn"&gt;[[queues.consumers]]&lt;/span&gt;
&lt;span class="py"&gt;queue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-jobs"&lt;/span&gt;

&lt;span class="nn"&gt;[triggers]&lt;/span&gt;
&lt;span class="py"&gt;crons&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"0 */6 * * *"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>serverless</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Triggering Long Jobs in Cloudflare Workers</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Wed, 10 Dec 2025 13:14:22 +0000</pubDate>
      <link>https://forem.com/teaganga/triggering-long-jobs-in-cloudflare-workers-8mh</link>
      <guid>https://forem.com/teaganga/triggering-long-jobs-in-cloudflare-workers-8mh</guid>
      <description>&lt;p&gt;I recently hit a wall with Cloudflare Workers that I'm sure many of you have encountered: &lt;strong&gt;how do you trigger a long-running background job on demand?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It seems simple enough—just call a function from your API, right? But Workers aren't quite that straightforward, and I spent way too long trying to work around limitations that, it turns out, Cloudflare already solved for me.&lt;/p&gt;

&lt;p&gt;Let me walk you through what I learned.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: My Job Was Too Long for HTTP
&lt;/h2&gt;

&lt;p&gt;I had a Worker that handled my admin UI. One of the features was a button that kicked off a heavy background process—think scraping, data processing, batch operations, that kind of thing.&lt;/p&gt;

&lt;p&gt;My first implementation was naive:&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;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/run-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runHeavyJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Job complete!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked fine in development. But in production? &lt;strong&gt;Timeouts. Everywhere.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's why that didn't work:&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP Requests Have Strict Limits
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;CPU Time&lt;/th&gt;
&lt;th&gt;Wall Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;10ms&lt;/td&gt;
&lt;td&gt;30s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workers Paid&lt;/td&gt;
&lt;td&gt;50ms&lt;/td&gt;
&lt;td&gt;30s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business+&lt;/td&gt;
&lt;td&gt;30s&lt;/td&gt;
&lt;td&gt;30s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My job needed more than 30 seconds of wall time, and I was burning through CPU time like crazy. Even on the paid plan, I kept hitting limits.&lt;/p&gt;

&lt;p&gt;"No problem," I thought, "I'll just use &lt;code&gt;ctx.waitUntil()&lt;/code&gt; to let it finish after the response!"&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;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/run-job&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;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;runHeavyJob&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// Still doesn't work! 😭&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Job started!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turns out, &lt;strong&gt;&lt;code&gt;waitUntil()&lt;/code&gt; doesn't extend the timeout&lt;/strong&gt;—it just lets you do cleanup work after sending the response. The isolate still shuts down at the same time limit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Couldn't Just Use &lt;code&gt;scheduled()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;My next idea was: "Wait, I have a cron job that runs this successfully every night. Why not just... trigger that?"&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;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/run-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Can I just... call scheduled() somehow? 🤔&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Nope!&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Done!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&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="nf"&gt;runHeavyJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// This works great!&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;This doesn't work.&lt;/strong&gt; You can't call &lt;code&gt;scheduled()&lt;/code&gt; from your code. Cloudflare's cron system is the only thing that can invoke it.&lt;/p&gt;

&lt;p&gt;I tried a bunch of hacky workarounds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calling the Cloudflare API to trigger a cron (requires external auth, not instant)&lt;/li&gt;
&lt;li&gt;Setting up webhooks to external services (defeats the purpose of Workers)&lt;/li&gt;
&lt;li&gt;Storing a flag in KV and polling it every minute (works, but... gross)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these felt wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Lightbulb Moment: Queues Are Made For This
&lt;/h2&gt;

&lt;p&gt;Then I discovered &lt;strong&gt;Cloudflare Queues&lt;/strong&gt;, and everything clicked.&lt;/p&gt;

&lt;p&gt;Queues give you a third type of invocation handler:&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;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;async&lt;/span&gt; &lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&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;async&lt;/span&gt; &lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&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="c1"&gt;// 👈 This one!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the key difference:&lt;/p&gt;

&lt;h3&gt;
  
  
  Execution Limits by Handler Type
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Handler&lt;/th&gt;
&lt;th&gt;CPU Time&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fetch()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10-50ms (most plans)&lt;/td&gt;
&lt;td&gt;Quick APIs, UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scheduled()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30s&lt;/td&gt;
&lt;td&gt;Periodic jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;queue()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Unlimited&lt;/strong&gt; ⚡&lt;/td&gt;
&lt;td&gt;Heavy processing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Queue handlers have no CPU time limit.&lt;/strong&gt; Only wall time limits, which are measured in minutes, not seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Actually Solved It
&lt;/h2&gt;

&lt;p&gt;Here's my final architecture:&lt;/p&gt;

&lt;h3&gt;
  
  
  Worker 1: Admin UI (Producer)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/run-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Enqueue a message&lt;/span&gt;
      &lt;span class="k"&gt;await&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;MY_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heavy-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;triggeredBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Job queued!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Worker 2: Job Runner (Consumer)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;triggeredBy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heavy-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runHeavyJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Runs with unlimited CPU time! 🎉&lt;/span&gt;
        &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ack&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This works beautifully because:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The UI Worker stays fast (just enqueues and returns)&lt;/li&gt;
&lt;li&gt;The job Worker runs with unlimited CPU time&lt;/li&gt;
&lt;li&gt;Queues handle retries automatically if something fails&lt;/li&gt;
&lt;li&gt;I can scale the workers independently&lt;/li&gt;
&lt;li&gt;Execution is nearly instant (no polling delay)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Important: Handlers Don't Compete for Resources
&lt;/h2&gt;

&lt;p&gt;I initially thought combining &lt;code&gt;fetch()&lt;/code&gt; and &lt;code&gt;queue()&lt;/code&gt; in the same Worker would cause problems. Like, maybe a running queue job would slow down HTTP requests?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nope.&lt;/strong&gt; Each handler invocation runs in its own isolated execution context. They don't share CPU or memory at runtime.&lt;/p&gt;

&lt;p&gt;What they DO share is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The code bundle&lt;/strong&gt; (larger bundles = slower cold starts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The deployment&lt;/strong&gt; (a bug in one handler affects the whole Worker)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you CAN put all three handlers in one Worker:&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;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MY_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;job&lt;/span&gt;&lt;span class="dl"&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Queued!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MY_QUEUE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batch&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;ctx&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="nf"&gt;runHeavyJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// This won't slow down fetch()&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;But I prefer separating them because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Smaller UI bundle = faster cold starts for users&lt;/li&gt;
&lt;li&gt;I can deploy job changes without touching the UI&lt;/li&gt;
&lt;li&gt;Cleaner separation of concerns&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Other Options I Considered
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cron Polling
&lt;/h3&gt;

&lt;p&gt;Set a flag in KV, then check it every minute with &lt;code&gt;scheduled()&lt;/code&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Job will run soon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;ctx&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;pending&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV&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;pending-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pending&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="nf"&gt;runHeavyJob&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending-job&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but it's not instant. You're at the mercy of your cron interval (minimum 1 minute).&lt;/p&gt;

&lt;h3&gt;
  
  
  Durable Object Alarms
&lt;/h3&gt;

&lt;p&gt;Durable Objects can set alarms that fire almost immediately:&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JobRunner&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAlarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&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&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;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alarm set&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;alarm&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="nf"&gt;runHeavyJob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Runs in the DO context&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;This is actually pretty elegant, but requires setting up Durable Objects, which feels heavyweight for this use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Recommendation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For on-demand long-running jobs: use Queues.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They're purpose-built for exactly this scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unlimited CPU time&lt;/li&gt;
&lt;li&gt;Built-in retry logic&lt;/li&gt;
&lt;li&gt;Simple API&lt;/li&gt;
&lt;li&gt;Scales automatically&lt;/li&gt;
&lt;li&gt;Near-instant execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setup is minimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# wrangler.toml&lt;/span&gt;
&lt;span class="nn"&gt;[[queues.producers]]&lt;/span&gt;
&lt;span class="py"&gt;queue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-jobs"&lt;/span&gt;
&lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MY_QUEUE"&lt;/span&gt;

&lt;span class="nn"&gt;[[queues.consumers]]&lt;/span&gt;
&lt;span class="py"&gt;queue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-jobs"&lt;/span&gt;
&lt;span class="py"&gt;max_batch_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="py"&gt;max_batch_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you're off to the races.&lt;/p&gt;




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

&lt;p&gt;What I learned from this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't fight the platform.&lt;/strong&gt; I wasted time trying to make &lt;code&gt;fetch()&lt;/code&gt; do something it wasn't designed for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the limits.&lt;/strong&gt; Understanding CPU vs. wall time saved me hours of debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queues are underrated.&lt;/strong&gt; They're not just for distributed systems—they're perfect for background jobs in monoliths too.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're running into timeout issues with Workers, check if Queues solve your problem. They probably do.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Getting Started With NextJs and Shadcn on Cloudflare, with Hono and D1</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Sat, 06 Dec 2025 10:17:34 +0000</pubDate>
      <link>https://forem.com/teaganga/getting-started-with-nextjs-and-shadcn-on-cloudflare-with-hono-and-d1-35dk</link>
      <guid>https://forem.com/teaganga/getting-started-with-nextjs-and-shadcn-on-cloudflare-with-hono-and-d1-35dk</guid>
      <description>&lt;p&gt;This is a starting kit created with &lt;code&gt;wrangler init&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╭ Create an application with Cloudflare Step 1 of 3
│
├ In which directory do you want to create your application?
│ dir ./nameless-ocean-1ab9
│
├ What would you like to start with?
│ category Framework Starter
│
├ Which development framework do you want to use?
│ framework Next.js
│
├ Continue with Next.js via `npx create-next-app@15.4.6 nameless-ocean-1ab9`
│

✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like your code inside a `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to use Turbopack for `next dev`? … No / Yes
✔ Would you like to customize the import alias (`@/*` by default)? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /home/adiian/projects/aapps/starterkit/nameless-ocean-1ab9.

Using npm.

Initializing project with template: app-tw 


Installing dependencies:
- react
- react-dom
- next

Installing devDependencies:
- typescript
- @types/node
- @types/react
- @types/react-dom
- @tailwindcss/postcss
- tailwindcss
- eslint
- eslint-config-next
- @eslint/eslintrc


added 332 packages, and audited 333 packages in 59s

136 packages are looking for funding
  run `npm fund` for details

1 moderate severity vulnerability

To address all issues, run:
  npm audit fix --force

Run `npm audit` for details.
Success! Created nameless-ocean-1ab9 at /home/adiian/projects/aapps/starterkit/nameless-ocean-1ab9

A new version of `create-next-app` is available!
You can update by running: npm i -g create-next-app

├ Copying template files
│ files copied to project directory
│
╰ Application created 

╭ Configuring your application for Cloudflare Step 2 of 3
│
├ Installing wrangler A command line tool for building Cloudflare Workers
│ installed via `npm install wrangler --save-dev`
│
├ Adding the Cloudflare adapter
│ installed @opennextjs/cloudflare)}
│
├ Updating `next.config.ts`
│ updated `next.config.ts`
│
├ Adding Wrangler files to the .gitignore file
│ updated .gitignore file
│
├ Updating `package.json` scripts
│ updated `package.json`
│
├ Generating types for your application
│ generated to `./cloudflare-env.d.ts` via `npm run cf-typegen`
│
├ Installing @types/node
│ installed via npm
│
├ You're in an existing git repository. Do you want to use git for version control?
│ yes git
│
╰ Application configured 

╭ Deploy with Cloudflare Step 3 of 3
│
├ Do you want to deploy your application?
│ no deploy via `npm run deploy`
│
╰ Done 

────────────────────────────────────────────────────────────
🎉  SUCCESS  Application created successfully!

💻 Continue Developing
Change directories: cd nameless-ocean-1ab9
Start dev server: npm run dev
Deploy: npm run deploy

📖 Explore Documentation
https://developers.cloudflare.com/workers

🐛 Report an Issue
https://github.com/cloudflare/workers-sdk/issues/new/choose

💬 Join our Community
https://discord.cloudflare.com

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

&lt;/div&gt;



&lt;p&gt;Then install shadcn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx shadcn@latest init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run npx shadcn@latest init, here's what happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;npx downloads the package temporarily - It fetches shadcn from npm to a cache&lt;/li&gt;
&lt;li&gt;Runs it immediately - Executes the CLI command&lt;/li&gt;
&lt;li&gt;Doesn't permanently install it - The package isn't added to your node_modules or package.json, but shadcn will install a bunch of other packages:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    "dependencies": {
        "@hookform/resolvers": "^5.2.2",
        ...
        "@radix-ui/react-label": "^2.1.7",
        "@radix-ui/react-slot": "^1.2.3",
        "class-variance-authority": "^0.7.1",
        "clsx": "^2.1.1",
        "lucide-react": "^0.546.0",
        ...
        "react-hook-form": "^7.65.0",
        "tailwind-merge": "^3.3.1",
        "zod": "^4.1.12"
    },
    "devDependencies": {
        ...
        "tw-animate-css": "^1.4.0",
        ...
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx shadcn@latest add button card input form label
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you ran the init and add commands shadcn will install of those packages:&lt;/p&gt;

&lt;h2&gt;
  
  
  Installed by &lt;code&gt;shadcn init&lt;/code&gt;:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;class-variance-authority&lt;/code&gt;&lt;/strong&gt; - Manages component variants (size, color, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;clsx&lt;/code&gt;&lt;/strong&gt; - Combines CSS class names conditionally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tailwind-merge&lt;/code&gt;&lt;/strong&gt; - Intelligently merges Tailwind classes (prevents conflicts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tw-animate-css&lt;/code&gt;&lt;/strong&gt; - Animation utilities for Tailwind v4 (replaces old &lt;code&gt;tailwindcss-animate&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installed by &lt;code&gt;shadcn add &amp;lt;component&amp;gt;&lt;/code&gt;:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  When you added &lt;code&gt;button&lt;/code&gt;, &lt;code&gt;label&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@radix-ui/react-slot&lt;/code&gt;&lt;/strong&gt; - Used by Button component for composition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@radix-ui/react-label&lt;/code&gt;&lt;/strong&gt; - Accessible label primitive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;lucide-react&lt;/code&gt;&lt;/strong&gt; - Icon library (many components use icons)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When you added &lt;code&gt;form&lt;/code&gt;:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;react-hook-form&lt;/code&gt;&lt;/strong&gt; - Powerful form state management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@hookform/resolvers&lt;/code&gt;&lt;/strong&gt; - Connects validation libraries to react-hook-form&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;zod&lt;/code&gt;&lt;/strong&gt; - TypeScript-first schema validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What shadcn Does Smart
&lt;/h2&gt;

&lt;p&gt;shadcn only installs what you &lt;strong&gt;actually need&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a &lt;code&gt;button&lt;/code&gt;? → Gets Radix Slot&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;form&lt;/code&gt;? → Gets react-hook-form + zod&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;select&lt;/code&gt;? → Gets more Radix primitives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps your bundle size lean - you're not installing form libraries if you only need buttons. You don't need to manually install anything else unless you want additional components.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>nextjs</category>
      <category>hono</category>
    </item>
    <item>
      <title>Microsoft Certifications Relevant to Fabric Data Engineering &amp; Architecture</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Fri, 05 Dec 2025 11:51:52 +0000</pubDate>
      <link>https://forem.com/teaganga/microsoft-certifications-relevant-to-fabric-data-engineering-architecture-4b7l</link>
      <guid>https://forem.com/teaganga/microsoft-certifications-relevant-to-fabric-data-engineering-architecture-4b7l</guid>
      <description>&lt;p&gt;I want to start studding for a certification for MS Azure in the Ai Field, so I create a list of certifications to see which is the best fit for me. I recently noticed a job requiring MS Fabric knowledge.&lt;/p&gt;

&lt;p&gt;So I'm checking if I should study something more specific or something broader.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⭐ &lt;strong&gt;1. DP-700 — Microsoft Fabric Data Engineer Associate&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Most directly relevant to Fabric engineering.&lt;/strong&gt;&lt;br&gt;
Focus: &lt;em&gt;Implementing data-engineering solutions inside Fabric.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lakehouses, Warehouses, KQL DBs&lt;/li&gt;
&lt;li&gt;Data ingestion &amp;amp; transformation (Pipelines, Dataflows, Notebooks)&lt;/li&gt;
&lt;li&gt;Storage design, Delta Lake&lt;/li&gt;
&lt;li&gt;Real-time ingestion (Eventstream/Eventhouse)&lt;/li&gt;
&lt;li&gt;Monitoring &amp;amp; governance in Fabric&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best for: &lt;strong&gt;Fabric Data Engineers / Fabric Architects&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ⭐ &lt;strong&gt;2. DP-600 — Fabric Analytics Engineer Associate&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Focuses on the &lt;strong&gt;semantic layer + analytics&lt;/strong&gt; side of Fabric.&lt;/p&gt;

&lt;p&gt;Covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semantic models&lt;/li&gt;
&lt;li&gt;Direct Lake / DirectQuery&lt;/li&gt;
&lt;li&gt;Power BI in Fabric&lt;/li&gt;
&lt;li&gt;Data modeling, DAX, metrics&lt;/li&gt;
&lt;li&gt;Performance tuning for analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best for: &lt;strong&gt;BI/Fabric Analytics Engineers&lt;/strong&gt;, those touching modeling/reporting.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⭐ &lt;strong&gt;3. DP-203 — Azure Data Engineer Associate&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The classic Azure data engineering cert.&lt;/p&gt;

&lt;p&gt;Covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Data Lake, Synapse, Databricks&lt;/li&gt;
&lt;li&gt;Data ingestion/ETL/ELT&lt;/li&gt;
&lt;li&gt;SQL/NoSQL data stores&lt;/li&gt;
&lt;li&gt;Batch + streaming&lt;/li&gt;
&lt;li&gt;Lakehouse fundamentals (pre-Fabric world)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful because Fabric still relies on many &lt;strong&gt;Azure concepts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Best for: &lt;strong&gt;Azure Data Engineers / Hybrid Fabric–Azure architects&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ⭐ &lt;strong&gt;4. AZ-305 — Azure Solutions Architect Expert&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;High-level architecture and design certification.&lt;/p&gt;

&lt;p&gt;Covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designing end-to-end Azure solutions&lt;/li&gt;
&lt;li&gt;Storage, networking, security, identity&lt;/li&gt;
&lt;li&gt;Cost optimization &amp;amp; governance&lt;/li&gt;
&lt;li&gt;Aligning designs with Microsoft Cloud Adoption Framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfect match for the role’s architecture responsibilities.&lt;/p&gt;

&lt;p&gt;Best for: &lt;strong&gt;Solution Architects / Lead Engineers / Consultants&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ➕ Helpful, but Secondary
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. PL-300 — Power BI Data Analyst&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data modeling&lt;/li&gt;
&lt;li&gt;DAX&lt;/li&gt;
&lt;li&gt;Report design&lt;/li&gt;
&lt;li&gt;Dataset relationships &amp;amp; performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful for Fabric because Power BI is the &lt;strong&gt;semantic layer&lt;/strong&gt; within Fabric.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;6. AI-900 — Azure AI Fundamentals&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Basics of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cognitive Services&lt;/li&gt;
&lt;li&gt;ML concepts&lt;/li&gt;
&lt;li&gt;GenAI and Azure OpenAI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good only if the project includes AI/ML components.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;7. AI-102 — Azure AI Engineer Associate&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Advanced AI implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NLP, Vision, Speech&lt;/li&gt;
&lt;li&gt;Azure AI services integration&lt;/li&gt;
&lt;li&gt;Responsible AI, deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only relevant if Fabric solutions integrate AI pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Certification Comparison Table&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Certification&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Focus Area&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Key Topics Covered&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Relevance to Azure AI / Fabric&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;⭐ &lt;strong&gt;DP-700 — Microsoft Fabric Data Engineer Associate&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Fabric data engineering&lt;/td&gt;
&lt;td&gt;• Lakehouses, Warehouses, KQL DBs&lt;br&gt;• Pipelines, Dataflows, Notebooks&lt;br&gt;• Delta Lake, storage design&lt;br&gt;• Eventstream/Eventhouse&lt;br&gt;• Monitoring &amp;amp; governance&lt;/td&gt;
&lt;td&gt;Fabric Data Engineers, Fabric Architects&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Highest relevance for Fabric engineering&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⭐ &lt;strong&gt;DP-600 — Fabric Analytics Engineer Associate&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Semantic modeling + analytics in Fabric&lt;/td&gt;
&lt;td&gt;• Semantic models&lt;br&gt;• Direct Lake / DirectQuery&lt;br&gt;• Power BI in Fabric&lt;br&gt;• DAX, metrics, performance tuning&lt;/td&gt;
&lt;td&gt;BI Engineers, Analytics Engineers&lt;/td&gt;
&lt;td&gt;Strong for modeling/reporting inside Fabric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⭐ &lt;strong&gt;DP-203 — Azure Data Engineer Associate&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Core Azure data engineering&lt;/td&gt;
&lt;td&gt;• ADLS, Synapse, Databricks&lt;br&gt;• ETL/ELT workflows&lt;br&gt;• SQL/NoSQL stores&lt;br&gt;• Batch + streaming&lt;br&gt;• Pre-Fabric lakehouse fundamentals&lt;/td&gt;
&lt;td&gt;Azure Data Engineers, Hybrid Azure–Fabric roles&lt;/td&gt;
&lt;td&gt;Very relevant; Azure foundations still apply to Fabric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⭐ &lt;strong&gt;AZ-305 — Azure Solutions Architect Expert&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Architecture &amp;amp; high-level Azure design&lt;/td&gt;
&lt;td&gt;• End-to-end Azure solution design&lt;br&gt;• Networking, security, identity&lt;br&gt;• Governance &amp;amp; cost optimization&lt;br&gt;• Microsoft CAF alignment&lt;/td&gt;
&lt;td&gt;Solutions Architects, Lead Engineers&lt;/td&gt;
&lt;td&gt;Very relevant if you’ll design Fabric + Azure solutions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;➕ &lt;strong&gt;PL-300 — Power BI Data Analyst&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;BI modeling and reporting&lt;/td&gt;
&lt;td&gt;• Data modeling&lt;br&gt;• DAX&lt;br&gt;• Report design&lt;br&gt;• Performance optimization&lt;/td&gt;
&lt;td&gt;Data Analysts, BI Engineers&lt;/td&gt;
&lt;td&gt;Helpful because Power BI is Fabric’s semantic layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;➕ &lt;strong&gt;AI-900 — Azure AI Fundamentals&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Intro to Azure AI&lt;/td&gt;
&lt;td&gt;• Cognitive Services basics&lt;br&gt;• ML &amp;amp; GenAI fundamentals&lt;/td&gt;
&lt;td&gt;AI beginners, generalists&lt;/td&gt;
&lt;td&gt;Useful only if touching AI components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;➕ &lt;strong&gt;AI-102 — Azure AI Engineer Associate&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Applied AI engineering&lt;/td&gt;
&lt;td&gt;• NLP, Vision, Speech&lt;br&gt;• Azure AI service integration&lt;br&gt;• Responsible AI&lt;br&gt;• Deployment patterns&lt;/td&gt;
&lt;td&gt;AI Engineers&lt;/td&gt;
&lt;td&gt;Relevant only if AI will be integrated with Fabric pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;For a &lt;strong&gt;Fabric Data Engineer / Solution Architect consultant&lt;/strong&gt;, the ideal bundle is:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Core / Highly Recommended&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;DP-700 — Fabric Data Engineer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DP-203 — Azure Data Engineer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AZ-305 — Solutions Architect Expert&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Nice-to-Have / Add-on&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;DP-600 — Fabric Analytics Engineer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PL-300 — Power BI Data Analyst&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-900 / AI-102&lt;/strong&gt; (only if AI-related work is expected)&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>aiops</category>
      <category>ai</category>
    </item>
    <item>
      <title>Selenium vs. Playwright vs. Cypress (2025): The Ultimate Comparison Guide</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Thu, 04 Dec 2025 15:31:11 +0000</pubDate>
      <link>https://forem.com/teaganga/selenium-vs-playwright-vs-cypress-2025-the-ultimate-comparison-guide-3m2f</link>
      <guid>https://forem.com/teaganga/selenium-vs-playwright-vs-cypress-2025-the-ultimate-comparison-guide-3m2f</guid>
      <description>&lt;p&gt;Automated testing is no longer optional. As web applications grow more complex, development teams rely on powerful testing frameworks to ensure reliability, performance, and seamless user experiences. I had to pick a testing framework to test our web application recently, so I checked what other options are beyond Selenium and Playright, to discover Cypress which seems good for heavy front end react-like apps. So I had to pick one of the three tools dominate the modern landscape:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Selenium&lt;/strong&gt; — the industry veteran&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright&lt;/strong&gt; — the modern powerhouse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cypress&lt;/strong&gt; — the developer-friendly favorite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They all automate browsers—but their approaches, strengths, and ideal use cases couldn’t be more different.&lt;/p&gt;

&lt;p&gt;This guide offers a clean, deep comparison plus practical advice on when to use each tool over the others.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 &lt;strong&gt;Quick Comparison Table&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Tool&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Selenium&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Playwright&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Cypress&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java, Python, C#, JS, Ruby etc.&lt;/td&gt;
&lt;td&gt;JS/TS, Python, Java, .NET&lt;/td&gt;
&lt;td&gt;JavaScript only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All browsers + mobile (Appium)&lt;/td&gt;
&lt;td&gt;Chromium, Firefox, WebKit&lt;/td&gt;
&lt;td&gt;Chromium, Edge, Firefox (beta)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Execution Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Medium–Slow&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;WebDriver protocol&lt;/td&gt;
&lt;td&gt;Modern API, no driver&lt;/td&gt;
&lt;td&gt;Runs inside the browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise, multi-language stacks&lt;/td&gt;
&lt;td&gt;Modern E2E testing, low flakiness&lt;/td&gt;
&lt;td&gt;Front-end apps, JS teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flakiness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Medium–High&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parallel Execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual setup&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Paid dashboard for easy parallelization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile Automation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full (Appium)&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recorder Tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Third-party&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Open Source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (dashboard has paid tier)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  🧩 &lt;strong&gt;Selenium: The Veteran of Web Testing&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Selenium has been the backbone of browser automation for over a decade. It’s mature, stable, and extremely flexible—supporting nearly every programming language and browser.&lt;/p&gt;

&lt;p&gt;It integrates deeply with enterprise ecosystems, which is why large organizations continue to rely on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  👍 &lt;strong&gt;Why teams choose Selenium&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Works with &lt;strong&gt;Java, Python, C#, JavaScript&lt;/strong&gt;, and more&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;legacy browsers&lt;/strong&gt; and enterprise environments&lt;/li&gt;
&lt;li&gt;Compatible with &lt;strong&gt;Appium for mobile automation&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Huge community, plugins, and long-term ecosystem stability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👎 &lt;strong&gt;Where Selenium struggles&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Slower execution due to WebDriver architecture&lt;/li&gt;
&lt;li&gt;Requires more manual waits and synchronization&lt;/li&gt;
&lt;li&gt;Higher flakiness if not tuned well&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 &lt;strong&gt;Selenium vs. Playwright — When to Choose Which&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Selenium over Playwright&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;multiple languages&lt;/strong&gt;, not just JS/Python/Java/.NET&lt;/li&gt;
&lt;li&gt;Your environment requires &lt;strong&gt;older browser support&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You rely on enterprise ecosystems (TestNG, Cucumber, corporate CI setups)&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;mobile automation&lt;/strong&gt; with Appium&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Playwright over Selenium&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want &lt;strong&gt;fast and reliable tests&lt;/strong&gt; with minimal flakiness&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;auto-waiting, parallelism, and built-in debugging&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Cross-browser support (including WebKit) matters&lt;/li&gt;
&lt;li&gt;You are starting a &lt;strong&gt;new automation project&lt;/strong&gt; and want the modern option&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 &lt;strong&gt;Selenium vs. Cypress — When to Choose Which&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Selenium over Cypress&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;full cross-browser&lt;/strong&gt; or mobile coverage&lt;/li&gt;
&lt;li&gt;You want to use &lt;strong&gt;languages other than JavaScript&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Your application requires &lt;strong&gt;multi-tab or complex window scenarios&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You work in large enterprises needing legacy support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Cypress over Selenium&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your team is &lt;strong&gt;JavaScript-focused&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;instant feedback&lt;/strong&gt; and a visual test runner&lt;/li&gt;
&lt;li&gt;You’re testing &lt;strong&gt;front-end SPA frameworks&lt;/strong&gt; like React/Vue/Angular&lt;/li&gt;
&lt;li&gt;You value &lt;strong&gt;developer experience&lt;/strong&gt; over flexibility&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  ⚡ &lt;strong&gt;Playwright: The Modern Powerhouse&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Playwright is quickly becoming the default choice for new testing projects.&lt;br&gt;
Created by the original Puppeteer team at Microsoft, it delivers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speed&lt;/li&gt;
&lt;li&gt;Reliability&lt;/li&gt;
&lt;li&gt;A modern API&lt;/li&gt;
&lt;li&gt;Powerful debugging tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Playwright removes the WebDriver middle layer entirely, enabling stable tests with automatic waiting and excellent cross-browser support (including WebKit, which mimics Safari).&lt;/p&gt;

&lt;h3&gt;
  
  
  👍 &lt;strong&gt;Why teams love Playwright&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extremely fast&lt;/strong&gt;, low-flake tests&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;Chromium, Firefox, WebKit&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel execution&lt;/strong&gt; out of the box&lt;/li&gt;
&lt;li&gt;Great debugging tools (trace viewer, codegen, inspector)&lt;/li&gt;
&lt;li&gt;Tests run consistently in CI and locally&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👎 &lt;strong&gt;Where Playwright has limits&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No full mobile automation (only WebView)&lt;/li&gt;
&lt;li&gt;Fewer enterprise ecosystem integrations than Selenium&lt;/li&gt;
&lt;li&gt;Requires TypeScript/Java/Python/.NET skillset&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 &lt;strong&gt;Playwright vs. Selenium — When to Choose Which&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Playwright over Selenium&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want &lt;strong&gt;modern, reliable, low-maintenance&lt;/strong&gt; tests&lt;/li&gt;
&lt;li&gt;Cross-browser support matters—but not necessarily legacy browsers&lt;/li&gt;
&lt;li&gt;You want built-in &lt;strong&gt;parallel execution&lt;/strong&gt;, tracing, and time-travel debugging&lt;/li&gt;
&lt;li&gt;You’re starting a new automation project&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Selenium over Playwright&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your org requires &lt;strong&gt;Java/C#/Python-first&lt;/strong&gt; solutions&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;legacy browser&lt;/strong&gt; support&lt;/li&gt;
&lt;li&gt;You run extensive &lt;strong&gt;mobile automation&lt;/strong&gt; suites&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 &lt;strong&gt;Playwright vs. Cypress — When to Choose Which&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Playwright over Cypress&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;multi-tab, multi-domain&lt;/strong&gt;, or iframe capabilities&lt;/li&gt;
&lt;li&gt;You want full support for &lt;strong&gt;Chromium, Firefox, and WebKit&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;scalable CI&lt;/strong&gt; performance&lt;/li&gt;
&lt;li&gt;You prefer flexibility over opinionated workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Cypress over Playwright&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your team loves &lt;strong&gt;visual debugging&lt;/strong&gt; and time-travel UI&lt;/li&gt;
&lt;li&gt;You’re exclusively testing &lt;strong&gt;JavaScript front-end apps&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You want the simplest onboarding for junior developers&lt;/li&gt;
&lt;li&gt;You value an &lt;strong&gt;all-in-one developer experience&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🌿 &lt;strong&gt;Cypress: The Developer-Friendly Favorite&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Cypress is adored by front-end teams.&lt;br&gt;
Its biggest selling point? A &lt;strong&gt;fantastic developer experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cypress runs inside the browser itself, offering time-travel debugging, clear error messages, and instant visual feedback. This makes it perfect for front-end-heavy applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  👍 &lt;strong&gt;Why teams choose Cypress&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Unmatched &lt;strong&gt;debugging and test visualization&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Seamless &lt;strong&gt;JavaScript/TypeScript&lt;/strong&gt; workflow&lt;/li&gt;
&lt;li&gt;Deep DOM access, great for SPAs&lt;/li&gt;
&lt;li&gt;Zero-flake auto-waiting for most interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👎 &lt;strong&gt;Cypress limitations&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Limited &lt;strong&gt;cross-browser&lt;/strong&gt; support&lt;/li&gt;
&lt;li&gt;No multi-tab support&lt;/li&gt;
&lt;li&gt;No native mobile automation&lt;/li&gt;
&lt;li&gt;Opinionated architecture restricts flexibility&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 &lt;strong&gt;Cypress vs. Selenium — When to Choose Which&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Cypress over Selenium&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You’re testing a &lt;strong&gt;React/Vue/Angular SPA&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Your team is 100% JavaScript&lt;/li&gt;
&lt;li&gt;You need excellent debugging and fast feedback&lt;/li&gt;
&lt;li&gt;You want a tool that’s easy for new developers to learn&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Selenium over Cypress&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;enterprise-level browser coverage&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You work in &lt;strong&gt;multi-language&lt;/strong&gt; environments&lt;/li&gt;
&lt;li&gt;You test multiple windows/tabs&lt;/li&gt;
&lt;li&gt;You require &lt;strong&gt;Appium mobile testing&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 &lt;strong&gt;Cypress vs. Playwright — When to Choose Which&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Cypress over Playwright&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Debugging clarity and ease matter most&lt;/li&gt;
&lt;li&gt;You prefer a highly opinionated, developer-focused workflow&lt;/li&gt;
&lt;li&gt;You mainly test Chromium browsers&lt;/li&gt;
&lt;li&gt;You want fast onboarding for UI developers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Playwright over Cypress&lt;/strong&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;multi-domain or multi-context&lt;/strong&gt; capability&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;Firefox + WebKit&lt;/strong&gt; support&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;scalable, fast CI execution&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You want to avoid Cypress’s architectural limitations&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I decided to stay with Playright for the time being, but still keeping an eye on Cypress, so if you’re choosing today:&lt;/p&gt;

&lt;h3&gt;
  
  
  🏆 &lt;strong&gt;Best all-around choice for modern automation:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;➡ &lt;strong&gt;Playwright&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🧱 &lt;strong&gt;Best for enterprises and multi-language environments:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;➡ &lt;strong&gt;Selenium&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🌱 &lt;strong&gt;Best for front-end-focused JavaScript teams:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;➡ &lt;strong&gt;Cypress&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>selenium</category>
      <category>playwright</category>
    </item>
    <item>
      <title>How to Seed Data in Drizzle (The Right Way)</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Mon, 01 Dec 2025 13:59:07 +0000</pubDate>
      <link>https://forem.com/teaganga/how-to-seed-data-in-drizzle-the-right-way-2m6d</link>
      <guid>https://forem.com/teaganga/how-to-seed-data-in-drizzle-the-right-way-2m6d</guid>
      <description>&lt;p&gt;&lt;em&gt;Best practices for Drizzle ORM, migrations, seed scripts, and system data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Seeding data is one of the most common tasks when building a backend—but if you're using &lt;strong&gt;Drizzle ORM&lt;/strong&gt;, the right way to seed data may not be obvious at first.&lt;/p&gt;

&lt;p&gt;Should you put seed data inside migrations?&lt;br&gt;
Should you generate seed migrations automatically?&lt;br&gt;
Should you write seed scripts instead?&lt;br&gt;
What goes where?&lt;/p&gt;

&lt;p&gt;This guide breaks down &lt;em&gt;exactly&lt;/em&gt; how to seed data in Drizzle the right way, along with example SQL and TypeScript migrations.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⭐ Why Seeding Data Matters
&lt;/h2&gt;

&lt;p&gt;Seeding is how you insert:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Essential system constants (roles, permissions, configuration defaults)&lt;/li&gt;
&lt;li&gt;Initial lookup tables&lt;/li&gt;
&lt;li&gt;Local development data&lt;/li&gt;
&lt;li&gt;Demo or sample data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drizzle handles schema migrations beautifully—but when it comes to seeding, there are a few rules that help you avoid headaches across environments.&lt;/p&gt;


&lt;h1&gt;
  
  
  &lt;strong&gt;1. Two Ways to Seed Data in Drizzle&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Drizzle supports seeding in two primary ways:&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;A) Seed data in a migration (SQL or TypeScript)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;→ Best for &lt;em&gt;system-required&lt;/em&gt; data like roles, permissions, default settings.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;B) Seed data using a seed script (your own script)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;→ Best for &lt;em&gt;environment-specific&lt;/em&gt; data like test users, fake orgs, or demo content.&lt;/p&gt;

&lt;p&gt;Understanding the difference is important.&lt;/p&gt;


&lt;h1&gt;
  
  
  &lt;strong&gt;2. When You Should Seed Data in Migrations&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Migrations should include &lt;strong&gt;static, deterministic, environment-independent data&lt;/strong&gt; that your application &lt;em&gt;must&lt;/em&gt; have to function.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default RBAC roles&lt;/li&gt;
&lt;li&gt;Permissions&lt;/li&gt;
&lt;li&gt;Role → permission mappings&lt;/li&gt;
&lt;li&gt;Default feature flags&lt;/li&gt;
&lt;li&gt;System-level configuration rows&lt;/li&gt;
&lt;li&gt;Enum-like lookup tables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These values belong in migrations &lt;strong&gt;&lt;em&gt;because they define the system itself&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  ✔️ Good migration seed examples
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;roles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Administrator'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'editor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Editor'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These values never change based on environment—so they belong in migrations.&lt;/p&gt;


&lt;h1&gt;
  
  
  &lt;strong&gt;3. When You Should &lt;em&gt;Not&lt;/em&gt; Seed Data in Migrations&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Never put environment-specific or temporary data inside migrations.&lt;/p&gt;

&lt;p&gt;These do &lt;strong&gt;NOT&lt;/strong&gt; belong in migrations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test users&lt;/li&gt;
&lt;li&gt;Demo accounts&lt;/li&gt;
&lt;li&gt;Sample websites&lt;/li&gt;
&lt;li&gt;Development-only data&lt;/li&gt;
&lt;li&gt;API keys or secrets&lt;/li&gt;
&lt;li&gt;Customer, agency, or tenant data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Migrations must remain &lt;strong&gt;stable&lt;/strong&gt;, &lt;strong&gt;repeatable&lt;/strong&gt;, and &lt;strong&gt;safe to run in production&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So anything environment-specific should go into a &lt;strong&gt;seed script&lt;/strong&gt;, not a migration.&lt;/p&gt;


&lt;h1&gt;
  
  
  &lt;strong&gt;4. Seeding Data Inside Drizzle Migrations&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;You can seed data using:&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 1 — SQL migration (&lt;code&gt;.sql&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Drizzle runs plain SQL migrations out of the box.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;rbap_roles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'platform_admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'platform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Platform Admin'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'partner_admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'partner'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Partner Admin'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customer_admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'customer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Customer Admin'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 2 — TypeScript migration (&lt;code&gt;.ts&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;If you prefer Drizzle's typed database client (and &lt;code&gt;sql&lt;/code&gt; tag):&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;sql&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="s2"&gt;drizzle-orm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="s2"&gt;`
    INSERT INTO rbap_roles (id, scope, name) VALUES
      ('platform_admin', 'platform', 'Platform Admin'),
      ('partner_admin', 'partner', 'Partner Admin'),
      ('customer_admin', 'customer', 'Customer Admin')
    ON CONFLICT (id) DO NOTHING;
  `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;down&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="s2"&gt;`
    DELETE FROM rbap_roles 
    WHERE id IN ('platform_admin', 'partner_admin', 'customer_admin');
  `&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;Both options work perfectly.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;5. Does Drizzle Auto-Generate Seed Migrations?&lt;/strong&gt;
&lt;/h1&gt;

&lt;h3&gt;
  
  
  ❌ No.
&lt;/h3&gt;

&lt;p&gt;Drizzle generates &lt;strong&gt;schema change&lt;/strong&gt; migrations only.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create table&lt;/li&gt;
&lt;li&gt;add column&lt;/li&gt;
&lt;li&gt;drop index&lt;/li&gt;
&lt;li&gt;change constraint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drizzle &lt;strong&gt;does not&lt;/strong&gt; generate migrations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inserts&lt;/li&gt;
&lt;li&gt;seed data&lt;/li&gt;
&lt;li&gt;role lists&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;enums implemented via lookup tables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you must write these manually (SQL or TS).&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;6. Creating a Seed Script for Local or Dev Data&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;For data you want in development but &lt;em&gt;not&lt;/em&gt; in production, create your own simple script:&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;seed.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&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="s2"&gt;./db&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;users&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="s2"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev-user-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dev Tester&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;Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ts-node seed.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or add a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ts-node seed.ts"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps dev/test data separate from production migrations.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;7. Best Practices for Seeding in Drizzle&lt;/strong&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Put system-level constants inside migrations&lt;/strong&gt;: Roles, permissions, status codes, lookup tables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Put environment-specific data inside seed scripts&lt;/strong&gt;: Demo data, test users, dev orgs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never mix schema migrations with app data&lt;/strong&gt;: Avoid creating partner agencies, customer orgs, or users inside migrations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;ON CONFLICT DO NOTHING&lt;/code&gt; for idempotency&lt;/strong&gt;: Ensures migrations stay safe and re-runnable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep migrations deterministic&lt;/strong&gt;: Every run should produce the same final result.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Final Takeaway&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Seeding in Drizzle is extremely flexible—once you follow the right boundaries.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Migration&lt;/th&gt;
&lt;th&gt;Seed Script&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create roles&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create demo users&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insert default permissions&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insert dev-only test data&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Populate lookup tables&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the data defines the &lt;em&gt;system&lt;/em&gt;, put it in a migration.&lt;br&gt;
If the data defines the &lt;em&gt;environment&lt;/em&gt;, put it in a seed script.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>drizzle</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>What Is SIEM? Understanding Its Role in the Modern Cybersecurity Ecosystem</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Thu, 27 Nov 2025 16:11:20 +0000</pubDate>
      <link>https://forem.com/teaganga/what-is-siem-understanding-its-role-in-the-modern-cybersecurity-ecosystem-h05</link>
      <guid>https://forem.com/teaganga/what-is-siem-understanding-its-role-in-the-modern-cybersecurity-ecosystem-h05</guid>
      <description>&lt;p&gt;In today’s hyper-connected world, organizations generate an overwhelming amount of data—from user activity and network traffic to application logs and cloud telemetry. Hidden in that data are the early warning signs of cyber threats. The challenge is making sense of it all.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;SIEM&lt;/strong&gt;, or &lt;strong&gt;Security Information and Event Management&lt;/strong&gt;, enters the picture.&lt;/p&gt;

&lt;p&gt;A SIEM platform acts as the &lt;em&gt;central nervous system&lt;/em&gt; of an organization’s security operations. It collects, correlates, and analyzes security events from across your entire IT and OT environment, helping security teams detect threats, investigate incidents, and maintain compliance.&lt;/p&gt;

&lt;p&gt;But SIEM isn’t just a tool—it’s a strategic component of the broader cybersecurity stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What Exactly Is SIEM?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A SIEM system performs three fundamental functions:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Log Collection &amp;amp; Centralization&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;SIEM aggregates logs and events from many different sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;firewalls&lt;/li&gt;
&lt;li&gt;servers&lt;/li&gt;
&lt;li&gt;applications&lt;/li&gt;
&lt;li&gt;cloud services&lt;/li&gt;
&lt;li&gt;identity platforms&lt;/li&gt;
&lt;li&gt;OT devices&lt;/li&gt;
&lt;li&gt;security tools (EDR, IDS, WAF, DLP, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of analyzing each log in isolation, SIEM provides a &lt;em&gt;single, unified view&lt;/em&gt; of everything happening across the organization.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Correlation &amp;amp; Detection&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Raw logs on their own aren't enough to reveal threats. A SIEM uses built-in detection rules, machine learning, and correlation logic to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;detect abnormal behavior&lt;/li&gt;
&lt;li&gt;identify attack patterns&lt;/li&gt;
&lt;li&gt;connect events across systems&lt;/li&gt;
&lt;li&gt;generate actionable alerts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enables earlier detection of threats like lateral movement, privilege escalation, or data exfiltration.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Analytics, Reporting &amp;amp; Compliance&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;SIEM platforms provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dashboards for visibility&lt;/li&gt;
&lt;li&gt;audit reports&lt;/li&gt;
&lt;li&gt;long-term data retention&lt;/li&gt;
&lt;li&gt;forensic search capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps with compliance requirements such as ISO 27001, NIS2, SOC 2, PCI-DSS, and many more.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Where SIEM Fits in the Bigger Cybersecurity Context&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;To understand SIEM fully, it’s important to see where it sits in the broader ecosystem. SIEM is not an isolated product—it's the &lt;em&gt;hub&lt;/em&gt; that connects the entire security stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1. SIEM as the Heart of Security Operations (SOC)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A SIEM typically sits at the center of a &lt;strong&gt;Security Operations Center (SOC)&lt;/strong&gt;. Analysts rely on SIEM dashboards, alerts, and logs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;monitor the environment in real-time&lt;/li&gt;
&lt;li&gt;identify suspicious activity&lt;/li&gt;
&lt;li&gt;investigate incidents&lt;/li&gt;
&lt;li&gt;escalate or respond to threats&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without a SIEM, SOC teams would be blind.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;2. SIEM and SOAR: From Detection to Automated Response&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;SIEM identifies threats.&lt;br&gt;
&lt;strong&gt;SOAR (Security Orchestration, Automation, and Response)&lt;/strong&gt; takes action.&lt;/p&gt;

&lt;p&gt;SIEM + SOAR together enable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automated ticketing&lt;/li&gt;
&lt;li&gt;enrichment (WHOIS, threat intel, geolocation)&lt;/li&gt;
&lt;li&gt;containment actions (disable user, isolate host)&lt;/li&gt;
&lt;li&gt;orchestrated response playbooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many modern SIEMs (e.g., Splunk, Microsoft Sentinel) now include SOAR features natively.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3. SIEM Within the Modern Detection &amp;amp; Response Stack&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s how SIEM relates to other security technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EDR/XDR&lt;/strong&gt; → provides endpoint-level telemetry (process, memory, behavior)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NDR&lt;/strong&gt; → monitors network traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FIM, IDS/IPS&lt;/strong&gt; → detect changes and intrusions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firewalls &amp;amp; WAF&lt;/strong&gt; → enforce network boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM/IDP&lt;/strong&gt; → handles identity and access events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Security Tools&lt;/strong&gt; → monitor SaaS and multi-cloud environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;All of these feed into the SIEM.&lt;/strong&gt;&lt;br&gt;
SIEM becomes the aggregation and analysis layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;4. SIEM in OT (Operational Technology) Security&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In industrial environments—energy, manufacturing, transportation, utilities—SIEM plays a unique role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;collecting logs from PLCs, SCADA systems, HMIs, and OT firewalls&lt;/li&gt;
&lt;li&gt;correlating behavior across IT and OT networks&lt;/li&gt;
&lt;li&gt;integrating with ICS threat detection platforms&lt;/li&gt;
&lt;li&gt;ensuring compliance with critical infrastructure standards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OT environments often operate in highly segmented networks, making SIEM one of the few windows into cross-domain visibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5. SIEM in a DevSecOps &amp;amp; API-Driven World&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Modern SIEMs aren’t just log viewers; they’re programmable platforms.&lt;/p&gt;

&lt;p&gt;They integrate with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RESTful APIs&lt;/li&gt;
&lt;li&gt;CI/CD pipelines&lt;/li&gt;
&lt;li&gt;data transformations (Logstash, Fluentd, Kafka)&lt;/li&gt;
&lt;li&gt;cloud event streams&lt;/li&gt;
&lt;li&gt;container orchestration systems like Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows security teams to automate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ingestion pipelines&lt;/li&gt;
&lt;li&gt;enrichment steps&lt;/li&gt;
&lt;li&gt;alert response&lt;/li&gt;
&lt;li&gt;data normalization&lt;/li&gt;
&lt;li&gt;custom detection rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, SIEM is evolving into a fully integrated part of the DevSecOps toolchain.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Why SIEM Still Matters—and More Than Ever&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;With expanding attack surfaces and increasing regulatory pressure, SIEM has become essential for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;real-time threat detection&lt;/li&gt;
&lt;li&gt;compliance and audit readiness&lt;/li&gt;
&lt;li&gt;end-to-end visibility&lt;/li&gt;
&lt;li&gt;incident response&lt;/li&gt;
&lt;li&gt;unified monitoring across cloud, on-prem, and OT systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As environments grow more complex and distributed, SIEM remains one of the few technologies capable of giving security teams a complete picture.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;Closing Thoughts&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;A SIEM is much more than a log collector—it’s the analytical brain of modern security operations. It sits at the intersection of IT, OT, cloud, and identity systems, providing the context needed to detect threats before they become incidents.&lt;/p&gt;

&lt;p&gt;In an era where digital operations are expanding faster than ever, a well-implemented SIEM isn’t just useful—it’s indispensable.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bicep / ARM vs Terraform</title>
      <dc:creator>teaganga</dc:creator>
      <pubDate>Thu, 27 Nov 2025 16:05:20 +0000</pubDate>
      <link>https://forem.com/teaganga/bicep-arm-vs-terraform-53k1</link>
      <guid>https://forem.com/teaganga/bicep-arm-vs-terraform-53k1</guid>
      <description>&lt;p&gt;Bicep and ARM templates are Terraform alternatives, but only within the Azure ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Bicep / ARM vs Terraform — What’s the difference?&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Bicep&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Azure-specific DSL for IaC.&lt;/li&gt;
&lt;li&gt;Compiles into ARM templates.&lt;/li&gt;
&lt;li&gt;First-class support by Microsoft.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Best choice if you're &lt;strong&gt;100% Azure&lt;/strong&gt; and want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tight integration with Azure&lt;/li&gt;
&lt;li&gt;Fast updates when new Azure features come out&lt;/li&gt;
&lt;li&gt;Simple syntax compared to ARM JSON&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;ARM Templates&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Low-level JSON IaC for Azure.&lt;/li&gt;
&lt;li&gt;Verbose &amp;amp; harder to maintain.&lt;/li&gt;
&lt;li&gt;Bicep is meant to replace writing ARM JSON manually.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Terraform&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud-agnostic&lt;/strong&gt; IaC tool.&lt;/li&gt;
&lt;li&gt;Works with Azure, AWS, GCP, VMware, Kubernetes, etc.&lt;/li&gt;
&lt;li&gt;Uses HCL (HashiCorp Configuration Language).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Great for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-cloud environments&lt;/li&gt;
&lt;li&gt;Advanced orchestration&lt;/li&gt;
&lt;li&gt;Rich ecosystem of providers &amp;amp; modules&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔍 &lt;strong&gt;So is Bicep an alternative to Terraform?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, but only if your infrastructure is Azure-only.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're Azure-only → &lt;strong&gt;Bicep is a strong alternative&lt;/strong&gt;, simpler and more native.&lt;/li&gt;
&lt;li&gt;If you're multi-cloud → &lt;strong&gt;Terraform is the better choice&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Quick Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Bicep&lt;/th&gt;
&lt;th&gt;ARM Templates&lt;/th&gt;
&lt;th&gt;Terraform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloud support&lt;/td&gt;
&lt;td&gt;Azure only&lt;/td&gt;
&lt;td&gt;Azure only&lt;/td&gt;
&lt;td&gt;Multi-cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Simple DSL&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;td&gt;HCL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native Azure support&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Very good (slightly slower updates)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State management&lt;/td&gt;
&lt;td&gt;Azure handles state&lt;/td&gt;
&lt;td&gt;Azure handles state&lt;/td&gt;
&lt;td&gt;Terraform state backend (S3, Azure blob, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modularity&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-cloud workflows&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🧭 When to choose which?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Bicep&lt;/strong&gt; if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You work primarily in Azure.&lt;/li&gt;
&lt;li&gt;You want clean, readable IaC.&lt;/li&gt;
&lt;li&gt;You want the newest Azure features immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose &lt;strong&gt;Terraform&lt;/strong&gt; if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You operate across multiple clouds.&lt;/li&gt;
&lt;li&gt;You want very strong modules and ecosystem support.&lt;/li&gt;
&lt;li&gt;You need complex workflows, reusable patterns, and a mature toolchain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  More about Bicep and ARM
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bicep&lt;/strong&gt; and &lt;strong&gt;ARM templates&lt;/strong&gt; are both ways to define &lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt; for deploying Azure resources.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ARM templates&lt;/strong&gt; = JSON-based, older, more verbose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bicep&lt;/strong&gt; = Newer, simpler, domain-specific language that &lt;em&gt;compiles into&lt;/em&gt; ARM JSON.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧱 ARM Templates (Azure Resource Manager Templates)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ARM templates&lt;/strong&gt; are JSON files that describe Azure resources you want to deploy.&lt;br&gt;
They are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON-based (structured but verbose)&lt;/li&gt;
&lt;li&gt;Deterministic (same template = same deployment result)&lt;/li&gt;
&lt;li&gt;Supported everywhere in Azure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example snippet (ARM JSON):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.Storage/storageAccounts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"apiVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-01-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mystorage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eastus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Standard_LRS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"StorageV2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hard to read and write&lt;/li&gt;
&lt;li&gt;Lots of nested syntax&lt;/li&gt;
&lt;li&gt;Painful for complex deployments&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🟦 Bicep (Next-Gen IaC for Azure)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bicep&lt;/strong&gt; is a domain-specific language (DSL) from Microsoft made to replace ARM templates.&lt;/p&gt;

&lt;p&gt;Key traits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Much more concise &amp;amp; readable&lt;/li&gt;
&lt;li&gt;Strong type checking &amp;amp; IntelliSense in VS Code&lt;/li&gt;
&lt;li&gt;Modular &amp;amp; reusable&lt;/li&gt;
&lt;li&gt;Converts to ARM JSON under the hood (so &lt;em&gt;same engine&lt;/em&gt;, better language)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example snippet (Bicep):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: 'mystorage'
  location: 'eastus'
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;60–80% fewer lines than ARM&lt;/li&gt;
&lt;li&gt;Easier learning curve&lt;/li&gt;
&lt;li&gt;Better tooling (VS Code extension)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔁 Relationship Between Them
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bicep → compiles to → ARM JSON&lt;/li&gt;
&lt;li&gt;ARM engine → executes the deployment&lt;/li&gt;
&lt;li&gt;Anything ARM can do, Bicep can do (because it uses the same backend)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convert existing ARM templates → Bicep (&lt;code&gt;bicep decompile&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Convert Bicep → ARM JSON (&lt;code&gt;bicep build&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🥇 Which should you use today?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bicep is the recommended IaC language for Azure&lt;/strong&gt;&lt;br&gt;
ARM templates still work, but Bicep is easier and more productive.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>etl</category>
      <category>datascience</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
