Digging Deeper
Deferred Functions
Introduction
defer lets you run a callback after the HTTP response has been sent to the client. This is useful for work that the client doesn't need to wait for — sending emails, writing logs, triggering notifications, syncing data to a third-party service, etc.
It works by attaching the callback to the finish event of the current response object, so the response is delivered immediately and the deferred work runs in the background on the same Node/Bun process.
Basic usage
defer is a global helper available anywhere in your app code without any import.
The client receives { message: "Registered successfully" } immediately. The email is sent in the background afterwards.
Inside a FormRequest
A common pattern is calling defer inside a FormRequest save() method, keeping side-effects out of the controller:
The controller stays clean — it just calls request.save() and returns:
Inside a controller
You can call defer directly inside a controller action when you don't need a FormRequest:
Error handling
If the deferred callback throws, the error is caught and logged to the console. It does not propagate to the client — the response was already sent.
Deferred task failed: Error: SMTP connection refused
Make sure to handle retries or dead-letter queuing inside the callback if reliability matters. For durable background work, use the [Queue](../Digging Deeper/Queues.md) system instead.
How it works
defer() registers a callback on the current response's finish event. The callback runs after the response is fully sent to the client. response() returns the active Express response from the per-request container binding.
When to use defer vs queues
| Scenario | Use |
|---|---|
| Fire-and-forget within the same process | defer |
| Work that must survive a process restart | Queue |
| Work that needs retries on failure | Queue |
| Sending a welcome email after registration | Either — defer is simpler |
| Processing uploaded files | Queue |
| Sending a webhook to a slow third-party API | Queue |
defer is the lightweight option — zero setup, runs in the same process. Queues add durability and retry logic at the cost of a worker process and a queue driver.