Asynchronous Processing with Queues in Laravel
Improve application performance by offloading heavy tasks to queues. Learn how to configure drivers, create job classes, and dispatch background jobs.
Previously in this course, we explored Introduction to Laravel Events and Listeners to decouple our business logic. While events help organize code, they are synchronous by default—the listener runs immediately during the request. Today, we add asynchronous processing to our arsenal, allowing us to move heavy operations into the background to keep our API responses snappy.
Why Queues Matter for Performance
In a standard web request, the user waits for the server to finish every operation before receiving a response. If your project board API needs to generate a PDF report, send an email notification, or process a large image upload, the user is stuck staring at a loading spinner.
By using queues, we capture these tasks as "jobs" and store them in a database or a service like Redis. A background worker process picks these jobs up and executes them independently of the main request. This is the secret to scaling API design for asynchronous processing and maintaining low latency.
Configuring Your Queue Driver
Laravel abstracts the queue system through the QUEUE_CONNECTION setting in your .env file. For development, the sync driver is the default, which executes jobs immediately (blocking the request).
To move to true background processing, you should switch to database or redis. For our project board, let's use the database driver:
- Run
php artisan queue:tableto create the migration. - Run
php artisan migrateto create thejobstable. - Update your
.env:QUEUE_CONNECTION=database.
Once configured, any job you dispatch will be inserted into this table, waiting for a worker to process it.
Creating and Dispatching Job Classes
Laravel provides a simple artisan command to generate a job class: php artisan make:job ProcessTaskReport. This creates a class in app/Jobs.
Jobs are essentially plain PHP classes with a handle() method. This is where your heavy lifting resides. Here is how we define a job to handle a task-related report in our project board:
PHPnamespace App\Jobs; use App\Models\Task; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class ProcessTaskReport implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function __construct(public Task $task) {} public function handle(): void { #6A9955">// Simulate a heavy task, like generating a PDF sleep(5); logger("Report generated for task: {$this->task->id}"); } }
The ShouldQueue interface is the critical piece; it tells Laravel that this class should be handled asynchronously rather than immediately.
Dispatching the Job
You trigger the job from your controller or service layer using the dispatch() method:
PHPpublic function store(Request $request) { $task = Task::create($request->validated()); #6A9955">// Offload the heavy report generation ProcessTaskReport::dispatch($task); return response()->json(['message' => 'Task created, report processing.'], 202); }
Hands-on Exercise
- Generate a Job: Create a job named
SendTaskNotificationthat accepts aUsermodel. - Implement Logic: Inside the
handle()method, useLog::info()to simulate sending an email. - Dispatch: Trigger this job inside your
TaskServiceafter a task is successfully created. - Test: Run
php artisan queue:workin a separate terminal window and create a task via your API. Watch your terminal logs to see the job execute in the background.
Common Pitfalls
- Forgetting the
ShouldQueueInterface: If you omit this, your code will run synchronously, defeating the purpose of using queues. - Database Bloat: With the
databasedriver, successful jobs remain in the table. Usephp artisan queue:pruneto clear out old, completed jobs. - Serialization Issues: When you pass a model to a job, Laravel uses
SerializesModels. If you delete the model from the database before the job runs, the job will fail. Always ensure your data integrity logic accounts for this, as discussed in Database transactions for data integrity. - Worker Latency: If you don't run
php artisan queue:work(or a supervisor process), your jobs will sit in the database forever. In production, always use a process monitor like Supervisor or Laravel Horizon.
Recap
Queues are your primary tool for improving performance in high-load applications. By configuring a driver like database, creating ShouldQueue classes, and dispatching them from your service layer, you decouple the user experience from heavy background work. When implemented alongside reliable background jobs, your application becomes significantly more resilient.
Up next: We will explore how to group these tasks together using Job Chaining and Batching to handle complex, multi-step workflows.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.