<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sheikh Shahzaman</title>
    <description>The latest articles on DEV Community by Sheikh Shahzaman (@shahzamandev).</description>
    <link>https://dev.to/shahzamandev</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%2F3848452%2F42ec2fce-4b9c-4483-97a6-a1ffda222802.jpg</url>
      <title>DEV Community: Sheikh Shahzaman</title>
      <link>https://dev.to/shahzamandev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shahzamandev"/>
    <language>en</language>
    <item>
      <title>Why We Switched from Direct API Calls to Kafka and What Broke Along the Way</title>
      <dc:creator>Sheikh Shahzaman</dc:creator>
      <pubDate>Wed, 15 Apr 2026 01:45:38 +0000</pubDate>
      <link>https://dev.to/shahzamandev/why-we-switched-from-direct-api-calls-to-kafka-and-what-broke-along-the-way-24a2</link>
      <guid>https://dev.to/shahzamandev/why-we-switched-from-direct-api-calls-to-kafka-and-what-broke-along-the-way-24a2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgam4zv9hk2ors90l0v3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgam4zv9hk2ors90l0v3.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; We migrated 10+ microservices from direct HTTP calls to Kafka event-driven communication. Reliability improved massively but the migration was harder than expected. Here are the real lessons including the mistakes.&lt;/p&gt;




&lt;p&gt;Our system started as a monolith. Then we split it into microservices. The services talked to each other using direct HTTP calls. Service A would POST to Service B which would POST to Service C. It worked fine when we had 3 services.&lt;/p&gt;

&lt;p&gt;Then we had 10.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Day Everything Cascaded
&lt;/h2&gt;

&lt;p&gt;One Tuesday morning our notification service crashed because of a memory leak. No big deal right? Restart it and move on.&lt;/p&gt;

&lt;p&gt;But the order service was calling the notification service directly during checkout. When notification service was down the order endpoint started timing out. Users could not place orders. The billing service was also calling notification service to confirm payment receipts. Billing started failing too.&lt;/p&gt;

&lt;p&gt;One crashed service took down three other services because they were all directly dependent on it.&lt;/p&gt;

&lt;p&gt;That was the day we decided to move to event-driven architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Set It Up
&lt;/h2&gt;

&lt;p&gt;The concept is simple. Instead of Service A calling Service B directly Service A publishes an event to Kafka. Service B listens for that event and processes it on its own time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: Direct coupling&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;markComplete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'billing-service/invoice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'notification-service/email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'analytics-service/track'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&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;// After: Event-driven&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;markComplete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;KafkaProducer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'order.completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'order_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'tenant_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'total'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'completed_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toIso8601String&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;The order service does not know or care who listens to that event. Billing creates an invoice. Notifications send an email. Analytics tracks a metric. Each service subscribes to the event independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Broke During Migration
&lt;/h2&gt;

&lt;p&gt;I wish I could say the migration was smooth. It was not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 1: Event ordering.&lt;/strong&gt; We assumed events would arrive in the order they were published. They mostly did. But when we had high throughput some consumers processed events out of order. An "order.updated" event arrived before "order.created" and the consumer crashed because the order did not exist yet.&lt;/p&gt;

&lt;p&gt;The fix was adding an event version number and having consumers check if they had already processed a newer version before applying changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 2: Duplicate events.&lt;/strong&gt; Kafka guarantees at-least-once delivery. That means consumers can receive the same event twice. We had a bug where a payment was processed twice because the consumer was not idempotent.&lt;/p&gt;

&lt;p&gt;The fix was adding a unique event ID and checking if we had already processed that ID before taking action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InvoiceConsumer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&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="nc"&gt;ProcessedEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'event_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createFromOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'order_id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nc"&gt;ProcessedEvent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'event_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&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;Problem 3: Debugging was harder.&lt;/strong&gt; With direct API calls you could trace a request from start to finish in one log. With events the flow is split across multiple services and multiple time periods. Finding out why an invoice was not created required checking logs in three different services.&lt;/p&gt;

&lt;p&gt;We solved this by adding a correlation ID to every event. When the order service publishes an event it includes a unique request ID. Every downstream consumer includes that same ID in their logs. Now you can search for one ID and see the entire flow across all services.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Patterns That Saved Us
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Dead letter queue.&lt;/strong&gt; When a consumer fails to process an event after 3 retries it goes to a dead letter topic. We have a dashboard that shows failed events and lets us replay them after fixing the bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema registry.&lt;/strong&gt; We define the structure of every event in a shared schema. If a producer tries to publish an event that does not match the schema it fails at publish time not at consume time. This prevented so many bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consumer lag monitoring.&lt;/strong&gt; We track how far behind each consumer is. If the notification consumer falls 10,000 events behind we get an alert. This caught performance issues before users noticed them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;After 3 months on event-driven architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero cascading failures. One service going down does not affect any other service.&lt;/li&gt;
&lt;li&gt;We can deploy services independently without coordinating with other teams.&lt;/li&gt;
&lt;li&gt;Adding a new consumer takes 30 minutes instead of modifying 5 different services.&lt;/li&gt;
&lt;li&gt;Event replay lets us reprocess historical data when we add new features.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Would Do Differently
&lt;/h2&gt;

&lt;p&gt;I would have implemented idempotency from day one not after the duplicate payment bug. Every consumer should be idempotent by default.&lt;/p&gt;

&lt;p&gt;I would have invested in better tooling earlier. A good event viewer that shows the flow of events across services would have saved weeks of debugging time.&lt;/p&gt;

&lt;p&gt;And I would not have migrated everything at once. We tried to move all 10 services in one sprint. It should have been gradual. Start with the least critical services and work toward the most critical.&lt;/p&gt;

&lt;p&gt;Event-driven architecture is powerful but it adds complexity. If you have 3 services that rarely fail direct API calls are probably fine. If you have 10+ services and reliability matters events are worth the investment.&lt;/p&gt;

&lt;p&gt;Have you migrated from direct API calls to event-driven architecture? What surprised you the most?&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How defer() in Laravel 11 Made Our API Responses 3x Faster Without Touching a Single Queue</title>
      <dc:creator>Sheikh Shahzaman</dc:creator>
      <pubDate>Sun, 29 Mar 2026 12:17:24 +0000</pubDate>
      <link>https://dev.to/shahzamandev/how-defer-in-laravel-11-made-our-api-responses-3x-faster-without-touching-a-single-queue-4j9o</link>
      <guid>https://dev.to/shahzamandev/how-defer-in-laravel-11-made-our-api-responses-3x-faster-without-touching-a-single-queue-4j9o</guid>
      <description>&lt;p&gt;TL;DR: Laravel 11 introduced defer() which runs code after the HTTP response is sent to the user. No queues, no job classes, no workers. Just wrap your fire-and-forget logic in defer() and your API becomes instantly faster.&lt;/p&gt;




&lt;p&gt;I spent two days last year trying to figure out why our order API endpoint was taking 1.2 seconds to respond. The order itself was being created in about 80ms. So where was the rest of the time going?&lt;/p&gt;

&lt;p&gt;Turns out we were sending a confirmation email, tracking an analytics event, syncing inventory with a third-party service, and clearing a cache key. All of that happened synchronously before the response was sent back to the user.&lt;/p&gt;

&lt;p&gt;The user did not care about any of those things completing before they saw their order confirmation. They just wanted to know their order went through.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Old Way: Queues for Everything
&lt;/h2&gt;

&lt;p&gt;The typical advice is to push these tasks onto a queue. Create a job class, dispatch it, run a queue worker, set up monitoring, handle failed jobs. For a large application with complex background processing that makes sense.&lt;/p&gt;

&lt;p&gt;But for simple fire-and-forget tasks like sending an email or tracking an event? That is a lot of infrastructure for something that should be simple.&lt;/p&gt;

&lt;p&gt;We had 14 different job classes in our application. Eight of them were single-method classes that just did one small thing. Each one had its own file, its own test, and its own entry in the failed jobs table. It felt like overkill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter defer()
&lt;/h2&gt;

&lt;p&gt;Laravel 11 added defer() and it changed how I think about background tasks. Here is how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/order'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;send&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;OrderConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Analytics&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'order_placed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;InventorySync&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:cart"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&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 response goes back to the user immediately after the order is created. Then Laravel runs all the deferred callbacks after the response is sent. The user never waits for them.&lt;/p&gt;

&lt;p&gt;No job classes. No queue workers. No Redis or database queue driver. Just a closure that runs after the response.&lt;/p&gt;

&lt;h2&gt;
  
  
  When To Use defer() vs Queues
&lt;/h2&gt;

&lt;p&gt;This is the part most articles get wrong. They either say "use defer for everything" or "always use queues." The reality is more nuanced.&lt;/p&gt;

&lt;p&gt;Use defer() when the task is simple and does not need retry logic. Sending a notification email, tracking an analytics event, clearing a cache, logging an activity. If it fails you do not need to retry it automatically.&lt;/p&gt;

&lt;p&gt;Use queues when the task is complex or needs reliability guarantees. Processing a payment, generating a large PDF, syncing thousands of records with an external API. If it fails you need to know about it and retry it.&lt;/p&gt;

&lt;p&gt;I made the mistake of using defer() for a webhook delivery early on. The webhook target was unreliable and about 10% of deliveries failed silently. There was no retry mechanism, no failed job record, no way to know it happened. I moved that back to a queue with 3 retries and the problem was solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Numbers
&lt;/h2&gt;

&lt;p&gt;After refactoring our order endpoint to use defer() for non-critical tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Response time dropped from 1.2 seconds to 280ms&lt;/li&gt;
&lt;li&gt;User-perceived performance improved dramatically&lt;/li&gt;
&lt;li&gt;We deleted 8 single-method job classes&lt;/li&gt;
&lt;li&gt;Queue worker load decreased because fewer jobs were being dispatched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The email still sends. The analytics event still tracks. The cache still clears. The user just does not wait for any of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Pattern I Use in Every Project
&lt;/h2&gt;

&lt;p&gt;I created a simple middleware that defers common request-level tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DeferCommonTasks&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ActivityLog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;MetricsCollector&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;trackRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&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;Every request automatically logs activity and tracks metrics without adding any latency to the response. The middleware runs once and every endpoint benefits from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Do Differently
&lt;/h2&gt;

&lt;p&gt;If I started over I would establish a clear rule from day one: if the task does not need retry logic and takes less than 5 seconds, use defer(). If it needs retries or takes longer, use a queue. Having that rule early would have prevented the 14 unnecessary job classes we ended up with.&lt;/p&gt;

&lt;p&gt;I also would not use defer() inside database transactions. If the transaction rolls back the deferred callback still runs because it executes after the response. This caused a bug where we were sending order confirmation emails for orders that failed to save. I learned that the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;defer() is not a replacement for queues. It is a replacement for the dozens of tiny job classes that exist only because you needed something to run "after" the response.&lt;/p&gt;

&lt;p&gt;One line of code. No infrastructure changes. Measurably faster responses.&lt;/p&gt;

&lt;p&gt;If you are on Laravel 11 or later and you are not using defer() yet you are probably making your users wait for things they do not need to wait for.&lt;/p&gt;

&lt;p&gt;What is the simplest performance win you have found in your Laravel applications?&lt;/p&gt;

</description>
      <category>api</category>
      <category>laravel</category>
      <category>performance</category>
      <category>php</category>
    </item>
    <item>
      <title>How defer() in Laravel 11 Made Our API Responses 3x Faster Without Touching a Single Queue</title>
      <dc:creator>Sheikh Shahzaman</dc:creator>
      <pubDate>Sun, 29 Mar 2026 01:19:09 +0000</pubDate>
      <link>https://dev.to/shahzamandev/how-defer-in-laravel-11-made-our-api-responses-3x-faster-without-touching-a-single-queue-1ogo</link>
      <guid>https://dev.to/shahzamandev/how-defer-in-laravel-11-made-our-api-responses-3x-faster-without-touching-a-single-queue-1ogo</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Laravel 11 introduced defer() which runs code after the HTTP response is sent to the user. No queues, no job classes, no workers. Just wrap your fire-and-forget logic in defer() and your API becomes instantly faster.&lt;/p&gt;




&lt;p&gt;I spent two days last year trying to figure out why our order API endpoint was taking 1.2 seconds to respond. The order itself was being created in about 80ms. So where was the rest of the time going?&lt;/p&gt;

&lt;p&gt;Turns out we were sending a confirmation email, tracking an analytics event, syncing inventory with a third-party service, and clearing a cache key. All of that happened synchronously before the response was sent back to the user.&lt;/p&gt;

&lt;p&gt;The user did not care about any of those things completing before they saw their order confirmation. They just wanted to know their order went through.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Old Way: Queues for Everything
&lt;/h2&gt;

&lt;p&gt;The typical advice is to push these tasks onto a queue. Create a job class, dispatch it, run a queue worker, set up monitoring, handle failed jobs. For a large application with complex background processing that makes sense.&lt;/p&gt;

&lt;p&gt;But for simple fire-and-forget tasks like sending an email or tracking an event? That is a lot of infrastructure for something that should be simple.&lt;/p&gt;

&lt;p&gt;We had 14 different job classes in our application. Eight of them were single-method classes that just did one small thing. Each one had its own file, its own test, and its own entry in the failed jobs table. It felt like overkill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter defer()
&lt;/h2&gt;

&lt;p&gt;Laravel 11 added defer() and it changed how I think about background tasks. Here is how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/order'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;send&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;OrderConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Analytics&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'order_placed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;InventorySync&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:cart"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&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 response goes back to the user immediately after the order is created. Then Laravel runs all the deferred callbacks after the response is sent. The user never waits for them.&lt;/p&gt;

&lt;p&gt;No job classes. No queue workers. No Redis or database queue driver. Just a closure that runs after the response.&lt;/p&gt;

&lt;h2&gt;
  
  
  When To Use defer() vs Queues
&lt;/h2&gt;

&lt;p&gt;This is the part most articles get wrong. They either say "use defer for everything" or "always use queues." The reality is more nuanced.&lt;/p&gt;

&lt;p&gt;Use defer() when the task is simple and does not need retry logic. Sending a notification email, tracking an analytics event, clearing a cache, logging an activity. If it fails you do not need to retry it automatically.&lt;/p&gt;

&lt;p&gt;Use queues when the task is complex or needs reliability guarantees. Processing a payment, generating a large PDF, syncing thousands of records with an external API. If it fails you need to know about it and retry it.&lt;/p&gt;

&lt;p&gt;I made the mistake of using defer() for a webhook delivery early on. The webhook target was unreliable and about 10% of deliveries failed silently. There was no retry mechanism, no failed job record, no way to know it happened. I moved that back to a queue with 3 retries and the problem was solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Numbers
&lt;/h2&gt;

&lt;p&gt;After refactoring our order endpoint to use defer() for non-critical tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Response time dropped from 1.2 seconds to 280ms&lt;/li&gt;
&lt;li&gt;User-perceived performance improved dramatically&lt;/li&gt;
&lt;li&gt;We deleted 8 single-method job classes&lt;/li&gt;
&lt;li&gt;Queue worker load decreased because fewer jobs were being dispatched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The email still sends. The analytics event still tracks. The cache still clears. The user just does not wait for any of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Pattern I Use in Every Project
&lt;/h2&gt;

&lt;p&gt;I created a simple middleware that defers common request-level tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DeferCommonTasks&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ActivityLog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nf"&gt;defer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;MetricsCollector&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;trackRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&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;Every request automatically logs activity and tracks metrics without adding any latency to the response. The middleware runs once and every endpoint benefits from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Do Differently
&lt;/h2&gt;

&lt;p&gt;If I started over I would establish a clear rule from day one: if the task does not need retry logic and takes less than 5 seconds, use defer(). If it needs retries or takes longer, use a queue. Having that rule early would have prevented the 14 unnecessary job classes we ended up with.&lt;/p&gt;

&lt;p&gt;I also would not use defer() inside database transactions. If the transaction rolls back the deferred callback still runs because it executes after the response. This caused a bug where we were sending order confirmation emails for orders that failed to save. I learned that the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;defer() is not a replacement for queues. It is a replacement for the dozens of tiny job classes that exist only because you needed something to run "after" the response.&lt;/p&gt;

&lt;p&gt;One line of code. No infrastructure changes. Measurably faster responses.&lt;/p&gt;

&lt;p&gt;If you are on Laravel 11 or later and you are not using defer() yet you are probably making your users wait for things they do not need to wait for.&lt;/p&gt;

&lt;p&gt;What is the simplest performance win you have found in your Laravel applications?&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
