g4t / laravel-bee-queue
A simple, fast, Redis-backed job queue for Laravel — inspired by bee-queue
Requires
- php: ^8.1
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/events: ^10.0|^11.0|^12.0|^13.0
- illuminate/redis: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
README
A simple, fast, Redis-backed job queue for Laravel — a PHP port of bee-queue (Node.js).
Jobs pushed from JS bee-queue can be consumed by the PHP worker and vice versa — they share the exact same Redis data layout.
Installation
composer require g4t/laravel-bee-queue php artisan vendor:publish --tag=bee-queue-config php artisan vendor:publish --tag=bee-queue-assets
bee-queue-assetscopies the dashboard logo intopublic/vendor/bee-queue/. Run this once after install.
Configuration
Publish the config file and set your .env keys:
| Key | Default | Description |
|---|---|---|
BEE_QUEUE_DEFAULT |
default |
Default queue name |
BEE_QUEUE_REDIS_CONNECTION |
default |
Redis connection from config/database.php |
BEE_QUEUE_PREFIX |
bq |
Redis key prefix |
BEE_QUEUE_CONCURRENCY |
1 |
Worker concurrency |
BEE_QUEUE_TIMEOUT |
60 |
Job timeout in seconds |
BEE_QUEUE_STALL_INTERVAL |
5000 |
Stall check interval in ms |
BEE_QUEUE_RETRY_ATTEMPTS |
3 |
Max retry attempts |
BEE_QUEUE_RETRY_BACKOFF |
fixed |
fixed or exponential |
BEE_QUEUE_RETRY_DELAY |
5 |
Retry delay in seconds |
BEE_QUEUE_REMOVE_ON_SUCCESS |
false |
Delete job data after success |
BEE_QUEUE_REMOVE_ON_FAILURE |
false |
Delete job data after failure |
BEE_QUEUE_DASHBOARD_PATH |
bee-queue |
URL path for the dashboard |
Retry backoff strategies
fixed— waits the sameBEE_QUEUE_RETRY_DELAYseconds between every attempt.exponential— doubles the delay on each attempt:delay × 2^(attempt - 1). Useful when downstream services need time to recover.
Basic Usage
Creating & enqueuing a job
use G4T\BeeQueue\Facades\BeeQueue; BeeQueue::createJob(['user_id' => 42, 'action' => 'send_welcome_email']) ->retries(3) ->backoff('exponential') // 'fixed' or 'exponential' ->retryDelay(5) ->delay(30) // run 30 seconds from now ->timeout(60) ->save(); // On a named queue BeeQueue::queue('emails') ->createJob(['to' => 'alice@example.com']) ->save();
Processing Jobs
Option 1 — Closure handler (quick/inline)
$worker = BeeQueue::worker(); $worker->process(function ($job) { // $job->data contains your payload $job->reportProgress(50); // do work... $job->reportProgress(100); });
Option 2 — Handler class with --handler flag
Create a handler class implementing JobContract:
// app/Jobs/ProcessMessage.php namespace App\Jobs; use G4T\BeeQueue\Contracts\JobContract; use G4T\BeeQueue\Job; class ProcessMessage implements JobContract { public function __construct(private Job $job) {} public function handle(): void { $data = $this->job->data; // process $data... } }
Run the worker with it:
php artisan bee-queue:work --handler=App\\Jobs\\ProcessMessage php artisan bee-queue:work emails --handler=App\\Jobs\\ProcessMessage
This handler receives every job on that queue regardless of data content — ideal for jobs pushed from JS bee-queue.
Option 3 — Auto-resolve handler from job data
Include a class key when pushing from PHP:
BeeQueue::createJob([ 'class' => App\Jobs\ProcessMessage::class, 'text' => 'hello', ])->save();
Run the worker without --handler — it will auto-resolve the class:
php artisan bee-queue:work
Routing multiple job types in one handler
public function handle(): void { match ($this->job->data['action'] ?? null) { 'send_email' => $this->sendEmail(), 'resize_image' => $this->resizeImage(), default => \Log::warning('Unknown action', $this->job->data), }; }
Artisan Commands
# Start a worker (runs forever) php artisan bee-queue:work # Start on a named queue php artisan bee-queue:work emails # With a specific handler class php artisan bee-queue:work --handler=App\\Jobs\\ProcessMessage # Process one job and exit php artisan bee-queue:work --once # Show queue health stats php artisan bee-queue:stats php artisan bee-queue:stats emails
Dashboard
The package ships with a built-in web dashboard for monitoring and managing your queues.
Setup
# Publish the dashboard logo asset (required once)
php artisan vendor:publish --tag=bee-queue-assets
Accessing the dashboard
Visit /bee-queue in your browser (or whatever path you set via BEE_QUEUE_DASHBOARD_PATH).
Features
- Stats cards — live counts for waiting, active, succeeded, failed, and delayed jobs. Click any card to filter the job list.
- Job table — shows job ID, status badge, payload data, timestamp, and progress bar.
- Retry — re-queues a failed job back to the waiting list with a fresh timestamp.
- Delete — removes a job from all Redis keys permanently.
- Auto-refresh — page reloads every 5 seconds with a countdown timer.
- Queue switcher — switch between named queues from the header input.
Middleware & path
Configure the dashboard in config/bee-queue.php:
'dashboard' => [ 'path' => env('BEE_QUEUE_DASHBOARD_PATH', 'bee-queue'), 'middleware' => ['web'], // add 'auth' or your own middleware here ],
To protect the dashboard behind authentication:
'middleware' => ['web', 'auth'],
Events
Listen to these events in your EventServiceProvider:
| Event | Properties | Description |
|---|---|---|
G4T\BeeQueue\Events\JobSucceeded |
$job |
Job completed successfully |
G4T\BeeQueue\Events\JobFailed |
$job, $exception |
Job exhausted all retries |
G4T\BeeQueue\Events\JobRetrying |
$job, $exception, $nextAttempt |
Job is being retried |
G4T\BeeQueue\Events\JobProgress |
$job, $progress |
Job reported progress |
use G4T\BeeQueue\Events\JobFailed; use G4T\BeeQueue\Events\JobSucceeded; Event::listen(JobSucceeded::class, function ($event) { \Log::info('Job done', ['id' => $event->job->id]); }); Event::listen(JobFailed::class, function ($event) { \Log::error('Job failed', [ 'id' => $event->job->id, 'error' => $event->exception->getMessage(), ]); });
Cross-compatibility with JS bee-queue
This package uses the exact same Redis data layout as the Node.js bee-queue library, so you can mix producers and consumers across languages:
// Node.js — push a job const Queue = require('bee-queue'); const queue = new Queue('default'); queue.createJob({ text: 'Hello from Node' }).save();
# PHP — consume it php artisan bee-queue:work --handler=App\\Jobs\\ProcessMessage
Redis Data Layout
bq:{queue}:id — STRING — auto-increment job ID counter
bq:{queue}:jobs — HASH — field=id, value=JSON job payload
bq:{queue}:waiting — LIST — IDs of jobs waiting to be processed
bq:{queue}:active — LIST — IDs of jobs currently being processed
bq:{queue}:succeeded — SET — IDs of successfully completed jobs
bq:{queue}:failed — SET — IDs of failed jobs
bq:{queue}:delayed — ZSET — IDs of delayed jobs (scored by run-at timestamp)
bq:{queue}:stallBlock — STRING — TTL-based lock for stall detection
Each job is stored as a JSON string in the jobs hash:
{
"data": { "your": "payload" },
"options": {
"timestamp": 1779186989503,
"stacktraces": [],
"retries": 3,
"backoff": "exponential",
"retryDelay": 5,
"timeout": 60,
"delay": null
},
"status": "succeeded",
"progress": 0
}
License
MIT

