How and why wrapping Laravel HTTP API request inside Laravel job?

Janez Cergolj

Hey there! Today we're talking about how to wrap Laravel Http API client requests in Laravel Jobs for improved efficiency. But first, let's go over the requirements - not all API requests are suitable for this method. Only use it for requests where an instant response isn't necessary.

Now, for the benefits

1. If your code has a bug, you can fix it and rerun the job.
2. If the API request fails (e.g. due to a 429 error), you can set it to retry after a specific time period.
3. You can control job execution using throttle middleware in Laravel, and monitor all jobs using Horizon with a REDIS queue worker. Failed jobs can easily be rerun in Horizon, or you can write a command to do it automatically.

Code

Here's an example of how you can use Laravel Jobs to get newly created candidates from one service and update their gender using another API.

A command

First, we'll create a TrackNewlyCreatedCandidatesCommand that dispatches a job to fetch all newly created candidate HTTP client requests.

<?php

namespace App\Console\Commands;

use App\Jobs\TrackNewlyCreatedCandidatesJob;
use Illuminate\Console\Command;

class TrackNewlyCreatedCandidatesCommand extends Command
{
    protected $signature = 'track-newly-created-candidates';

    protected $description = 'get newly created candidates';

    public function handle()
    {
        TrackNewlyCreatedCandidatesJob::dispatch();

        return Command::SUCCESS;
    }
}

TrackNewlyCreatedCandidatesJob job

Next, we have the TrackNewlyCreatedCandidatesJob that makes the first API request to fetch all newly created candidates. If the request is successful, it dispatches an independent UpdateCandidateGenderJob for each candidate.

<?php

namespace App\Jobs;

use App\Jobs\Bullhorn\UpdateCandidateGenderJob;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class TrackNewlyCreatedCandidatesJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function handle()
    {
        $response = Http::get(...);

        collect($response->json())->each(function ($candidate) {
            UpdateCandidateGenderJob::dispatch($candidate);
        });
    }
}

UpdateCandidateGenderJob job

Why dispatch for each candidate its UpdateCandidateGenderJob job?

Separation of Concerns

Jobs are ideal for adhering to the principle of single responsibility, where each class should have only one specific task. Smaller classes are easier to read, understand, modify, and test.

Handle Failed Jobs

In the event that one of the UpdateCandidateGenderJob jobs fails, the other jobs will still run. This code behaves like a batch rather than a chain of jobs. Each job runs independently, without relying on the outcome of others.

<?php

namespace App\Jobs\Bullhorn;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class UpdateCandidateGenderJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * @param  array  $candidate
     * @return void
     */
    public function __construct(public array $candidate)
    {
    }

    public function handle()
    {
        $response = Http::get();

        // ...
    }
}

That's it! This is why HTTP API client requests should be wrapped in Laravel Jobs. If you have any questions or want to know more about testing this approach or automatically retrying failed jobs, hit me up on Twitter.