Cara Membuat Scheduler dan Job Queues di Laravel

Laravel menyediakan fasilitas untuk beberapa arsitektur aplikasi yang kurang umum, salah satunya adalah Jobs dan Queue. di tulisan ini kita bahas cara mengimplementasikan data antrian, proses pekerjaan yang mengantri, plus Laravel scheduler yang membuat jadwal cron. Studi kasusnya, kita bikin task todo yang otomatis jalan di jam tertentu, lalu diantrekan ke queue.
Apa itu Queue dan Scheduler
Queue itu konsepnya seperti antrian di bank. satu orang dilayani pada satu waktu dari setiap antrian, dan setiap orang pada akhirnya sampai ke depan loket untuk dilayani. di pemrograman sama saja, aplikasi menambahkan “pekerjaan” ke antrian, lalu job worker mengambilnya satu per satu dan mengerjakan sesuai perintah. worker bisa menghapus pekerjaan, mengembalikannya ke antrian dengan penundaan, atau menandainya sukses.
Laravel memudahkan fitur queue ini dengan banyak driver, bisa Redis, beanstalkd, Amazon SQS, atau tabel database. ada juga driver sinkron agar pekerjaan jalan langsung tanpa benar-benar diantrekan, cocok untuk testing.
Sementara scheduler itu penjadwal. dia yang nentuin kapan sebuah task dijalankan, misalnya tiap menit atau tiap hari. jadi gambaran besarnya: scheduler memicu, queue yang mengeksekusi di belakang layar.
di studi kasus ini saya lanjut dari project Membuat REST API CRUD dengan Laravel serta JWT yang sudah kita buat sebelumnya.
Menyiapkan Database
Membuat Migration
kita butuh dua tabel, task untuk data todo dan task_scheduler untuk jadwalnya. jalankan generator migrate:
php artisan make:migration task
php artisan make:migration task_scheduler
isi kode migrate untuk task:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('task', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('description');
$table->integer('user_id');
$table->enum('status', ['WAIT', 'DONE'])->default('WAIT');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('task');
}
};
isi kode migrate untuk task_scheduler:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('task_scheduler', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('days');
$table->time('time');
$table->string('description');
$table->integer('user_id');
$table->text('log_executed');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('task_scheduler');
}
};
Penjelasan :
Schema::create('task_scheduler', ...): membuat tabel baru ketika command migrate dijalankan$table->id();: kolom id dengan auto increment$table->string('days');: kolom hari, disimpan sebagai string berisi daftar hari dipisah koma$table->time('time');: kolom jam dengan tipe dataTIME$table->text('log_executed');: menyimpan catatan kapan task terakhir dieksekusi$table->timestamps();: membuat kolomcreated_atdanupdated_atotomatis
setelah file migrate sesuai, jalankan:
php artisan migrate
Membuat Model
lanjut buat model untuk mapping data:
php artisan make:model Task
php artisan make:model TaskScheduler
isi app/Models/Task.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
use HasFactory;
protected $table = 'task';
protected $fillable = [
'name', 'description', 'user_id', 'status'
];
}
isi app/Models/TaskScheduler.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TaskScheduler extends Model
{
use HasFactory;
protected $table = 'task_scheduler';
protected $fillable = [
'name', 'days', 'time', 'description', 'user_id', 'log_executed',
];
}
Membuat Job
buat job dengan artisan:
php artisan make:job TaskSchedulerJob
Penjelasan :
- perintah di atas membuat file
app/Jobs/TaskSchedulerJob.php. isinya nanti yang mengerjakan task ketika antrian jalan
lalu edit app/Jobs/TaskSchedulerJob.php:
<?php
namespace App\Jobs;
use App\Models\Task;
use App\Models\TaskScheduler;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class TaskSchedulerJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $schedulerId;
public $userId;
public $taskData;
public function __construct($schedulerId, $userId, $taskData)
{
$this->schedulerId = $schedulerId;
$this->userId = $userId;
$this->taskData = $taskData;
}
public function handle(): void
{
// buat task dari data yang dikirim
$task = Task::create([
'name' => $this->taskData['name'],
'description' => $this->taskData['description'],
'user_id' => $this->userId,
'status' => 'DONE',
]);
// catat log eksekusi di scheduler
TaskScheduler::where('id', $this->schedulerId)
->update(['log_executed' => now()->toDateTimeString()]);
}
}
Penjelasan :
implements ShouldQueue: memberitahu Laravel bahwa job ini harus diantrekanhandle(): method yang dieksekusi worker ketika job diambil dari antrian- di dalam
handle()kita bikin task baru dan memperbaruilog_executedsupaya ada jejak eksekusinya
Menyiapkan Repository
saya pakai Repository Pattern, jadi akses database dipisah dari controller. buat file app/Repositories/TaskScheduleRepository.php:
<?php
namespace App\Repositories;
use App\Models\TaskScheduler;
use Illuminate\Support\Facades\Auth;
class TaskScheduleRepository
{
private $user;
public function __construct()
{
$this->user = Auth::user();
}
public function getAll()
{
return TaskScheduler::where('user_id', $this->user->id)->get();
}
public function getById($id)
{
return TaskScheduler::where('user_id', $this->user->id)
->where('id', $id)
->firstOrFail();
}
public function create($input)
{
$input['days'] = implode(',', $input['days']);
$input['user_id'] = $this->user->id;
return TaskScheduler::create($input);
}
public function update($input, $id)
{
$task = TaskScheduler::where('user_id', $this->user->id)->findOrFail($id);
$input['days'] = implode(',', $input['days']);
$task->update($input);
return $task;
}
public function delete($id)
{
$task = TaskScheduler::where('user_id', $this->user->id)->findOrFail($id);
$task->delete();
return $task;
}
}
Penjelasan :
implode(',', $input['days']): mengubah array hari (Monday, Tuesday) jadi string “Monday,Tuesday” untuk disimpan di kolomdaysfirstOrFail/findOrFail: otomatis melempar 404 kalau data tidak ditemukan, jadi tidak perlu blok try-catch berulang
Membuat Controller
buat controller:
php artisan make:controller TaskScheduleController
lalu isi app/Http/Controllers/TaskScheduleController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Repositories\TaskScheduleRepository;
use App\Models\TaskScheduler;
use App\Jobs\TaskSchedulerJob;
use Illuminate\Support\Facades\Log;
class TaskScheduleController extends Controller
{
public function index()
{
$repoTask = new TaskScheduleRepository;
$response = $repoTask->getAll();
return response()->json([
'status' => true,
'task' => $response,
], 200);
}
public function create(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string',
'days' => 'required|array|in:Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday',
'time' => 'required|string|date_format:H:i',
'description' => 'required|string',
]);
if ($validator->fails()) {
return response()->json([
'status' => false,
'message' => $validator->messages()->first(),
], 400);
}
$repoTask = new TaskScheduleRepository;
$response = $repoTask->create($request->all());
return response()->json([
'status' => true,
'message' => 'Berhasil Membuat Task Scheduler baru',
'task' => $response,
], 201);
}
public function show($id)
{
$repoTask = new TaskScheduleRepository;
$response = $repoTask->getById($id);
return response()->json([
'status' => true,
'task' => $response,
], 200);
}
public function update(Request $request, $id)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string',
'days' => 'required|array|in:Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday',
'time' => 'required|string|date_format:H:i',
'description' => 'required|string',
]);
if ($validator->fails()) {
return response()->json([
'status' => false,
'message' => $validator->messages()->first(),
], 400);
}
$repoTask = new TaskScheduleRepository;
$response = $repoTask->update($request->all(), $id);
return response()->json([
'status' => true,
'message' => 'Berhasil Update task',
'task' => $response,
], 200);
}
public function destroy($id)
{
$repoTask = new TaskScheduleRepository;
$repoTask->delete($id);
return response()->json([
'status' => true,
'message' => 'Berhasil Delete task',
], 200);
}
public function handleJob()
{
$day = now()->format('l');
$time = now()->setTimezone('+07:00')->format('H:i');
$schedulers = TaskScheduler::where('days', 'like', '%' . $day . '%')
->where('time', '=', $time)
->get();
$executedIds = [];
foreach ($schedulers as $scheduler) {
$taskData = [
'name' => $scheduler->name,
'description' => $scheduler->description,
];
// kirim ke antrian, bukan eksekusi langsung
TaskSchedulerJob::dispatch($scheduler->id, $scheduler->user_id, $taskData)
->onQueue('task_scheduler');
$executedIds[] = $scheduler->id;
}
Log::info('TaskScheduler handleJob dijalankan', ['ids' => $executedIds]);
return [
'status' => count($executedIds) > 0 ? 'success' : 'empty',
'total_executed_scheduler' => count($executedIds),
'executed_idSchedulers' => $executedIds,
];
}
}
Penjelasan :
- method
handleJobberfungsi sebagai trigger dari cron. dia mencari scheduler yang hari dan jamnya sesuai sekarang, lalu men-dispatch job ke queue TaskSchedulerJob::dispatch(...)->onQueue('task_scheduler'): mengirim job ke antrian bernamatask_scheduler, bukan mengeksekusi langsung
Membuat Route Endpoint
tambahkan di routes/api.php:
use App\Http\Controllers\TaskScheduleController;
Route::middleware('auth:sanctum')->group(function () {
Route::get('task-schedule', [TaskScheduleController::class, 'index']);
Route::post('task-schedule', [TaskScheduleController::class, 'create']);
Route::get('task-schedule/{id}', [TaskScheduleController::class, 'show']);
Route::put('task-schedule/{id}', [TaskScheduleController::class, 'update']);
Route::patch('task-schedule/{id}', [TaskScheduleController::class, 'update']);
Route::delete('task-schedule/{id}', [TaskScheduleController::class, 'destroy']);
});
// panggil manual tanpa menunggu cron, untuk testing di localhost
Route::get('task-schedule/handleJob', [TaskScheduleController::class, 'handleJob']);
Penjelasan :
- route CRUD dibungkus
auth:sanctumkarena butuh login - route
task-schedule/handleJobsengaja di luar middleware supaya gampang di-test dari browser, tinggal akses URL-nya langsung
Menjalankan Scheduler dan Queue
setelah semua siap, ada dua proses yang perlu jalan.
1. Menjalankan Queue Worker
queue worker yang mengambil job dari antrian. sintaks yang benar:
php artisan queue:work redis --queue=task_scheduler --tries=3 --timeout=0
Penjelasan :
queue:work redis: driver queue-nya Redis (kalau pakai database, ganti jadiqueue:work database)--queue=task_scheduler: nama antrian yang diproses, di sinitask_scheduler--tries=3: mencoba ulang maksimal 3 kali kalau job gagal--timeout=0: tanpa batas waktu eksekusi (hati-hati untuk job yang bisa infinite loop)
2. Menjalankan Scheduler
di Laravel 11, cukup jalankan perintah ini dan dia akan terus jalan memeriksa jadwal tiap menit:
php artisan schedule:work
untuk production, tambahkan entry cron di server supaya scheduler jalan terus:
* * * * * cd /path/to/rest-api-laravel && php artisan schedule:run >> /dev/null 2>&1
3. Mendaftarkan Jadwal di Kernel
untuk Laravel 10 ke bawah, daftarkan jadwal di app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->call('\App\Http\Controllers\TaskScheduleController@handleJob')->everyMinute();
}
di Laravel 11 ke atas, buat command artisan atau gunakan route handleJob yang sudah kita buat, lalu panggil dari cron.
Alur lengkapnya
- user membuat jadwal lewat API
POST /api/task-scheduledengan hari dan jam - scheduler (via cron /
schedule:work) memanggilhandleJobtiap menit handleJobmencari jadwal yang cocok dengan hari dan jam sekarang- setiap jadwal yang cocok di-dispatch ke queue
task_scheduler - queue worker mengambil job dan menjalankan
TaskSchedulerJob::handle(), task baru dibuat di tabeltask
Pertanyaan yang Sering Diajukan
Apa beda scheduler dan queue di Laravel?
Scheduler mengatur kapan task dijalankan (jadwal cron, misal tiap menit atau tiap hari jam 9). Queue mengatur bagaimana task berat dijalankan di belakang layar lewat worker, supaya request utama tidak diblokir.
Bagaimana cara menjalankan scheduler di Laravel localhost?
Jalankan php artisan schedule:work (Laravel 11+) atau schedule:run. Untuk production, tambahkan entry cron * * * * * cd /path/project && php artisan schedule:run >> /dev/null 2>&1.
Kenapa queue worker harus selalu berjalan?
Karena job yang di-dispatch hanya dieksekusi oleh worker. Kalau worker mati, job mengantri selamanya. Di production biasanya dipantau dengan supervisor supaya worker restart otomatis kalau mati.
Sintaks queue:work yang benar seperti apa?
php artisan queue:work redis --queue=task_scheduler --tries=3 --timeout=0. Perhatikan: driver (redis) ditulis sebagai opsi queue:work redis, bukan digabung setelah nama queue.

