laravel-queue-bridge
v1.0.0
Published
Serialize and deserialize Laravel Queue messages (PHP serialized) in Node.js/TypeScript
Maintainers
Readme
laravel-queue-bridge
Bridge between Laravel Queue and Node.js / TypeScript.
Laravel Queue publishes jobs to RabbitMQ as a JSON envelope wrapping a PHP serialize() string. This zero-dependency library lets Node.js services work with that format in both directions:
- Consumer — deserialize incoming Laravel Queue messages into plain JS objects
- Producer — serialize and create messages that Laravel workers can
unserialize()natively
Install
npm install laravel-queue-bridgeAI assistant integration (optional)
Install docs for your AI coding assistant so it understands how to use this library:
npx laravel-queue-bridge install # all supported AI tools
npx laravel-queue-bridge install claude # Claude Code only
npx laravel-queue-bridge install copilot # GitHub Copilot only
npx laravel-queue-bridge install cursor # Cursor only
npx laravel-queue-bridge install codex # OpenAI Codex only
npx laravel-queue-bridge install windsurf # Windsurf only| AI Tool | Files installed |
|---------|----------------|
| Claude Code | .claude/skills/ + .claude/docs/ |
| GitHub Copilot | .github/copilot-instructions.md |
| Cursor | .cursor/rules/ |
| OpenAI Codex | AGENTS.md |
| Windsurf | .windsurfrules |
Re-run after upgrading the package to get the latest docs.
Use case 1: NestJS as Consumer (receive from Laravel)
Laravel dispatches a job to RabbitMQ. Your NestJS service consumes the queue and processes it.
Laravel side (Producer)
// App\Jobs\UserChangedJob
class UserChangedJob implements ShouldQueue
{
private string $type; // "create" | "update" | "delete"
public $payload; // user data array or userId (int)
public function __construct(string $type, $payload)
{
$this->type = $type;
$this->payload = $payload;
}
}
// Dispatch
dispatch(new UserChangedJob('create', [
'id' => 100,
'name' => 'Nguyen Van A',
'email' => '[email protected]',
]))->onQueue('contact');Laravel serializes this into a RabbitMQ message:
{
"uuid": "a1b2c3d4-...",
"job": "Illuminate\\Queue\\CallQueuedHandler@call",
"data": {
"commandName": "App\\Jobs\\UserChangedJob",
"command": "<PHP serialized string — contains private/protected visibility prefixes>"
},
"attempts": 0
}The command field contains a PHP serialize() output like:
O:23:"App\Jobs\UserChangedJob":2:{
s:29:"<\0>App\Jobs\UserChangedJob<\0>type";s:6:"create";
s:7:"payload";a:3:{s:2:"id";i:100;s:4:"name";s:12:"Nguyen Van A";...}
}
<\0>= null byte. Private properties use\0ClassName\0key, protected use\0*\0key.
NestJS side (Consumer)
import { parseLaravelQueueMessage } from 'laravel-queue-bridge';
// Inside your RabbitMQ consumer
channel.consume('contact', (msg) => {
const { jobName, shortName, properties } = parseLaravelQueueMessage(msg.content);
console.log(jobName); // "App\\Jobs\\UserChangedJob"
console.log(shortName); // "UserChangedJob"
console.log(properties); // { type: "create", payload: { id: 100, name: "Nguyen Van A", ... } }
// The library automatically:
// - Parses the JSON envelope
// - Deserializes the PHP serialize() string inside data.command
// - Strips PHP visibility prefixes (\0ClassName\0 for private, \0*\0 for protected)
// - Returns clean JS object with plain property names
channel.ack(msg);
});With TypeScript generics
interface UserChangedProps {
type: 'create' | 'update' | 'delete';
payload: { id: number; name: string; email: string } | number;
}
const msg = parseLaravelQueueMessage<UserChangedProps>(raw);
msg.properties.type; // 'create' | 'update' | 'delete'
msg.properties.payload; // typed payloadUse case 2: NestJS as Producer (send to Laravel)
Your NestJS service publishes a message. A Laravel worker consumes it and runs the job's handle() method.
NestJS side (Producer)
import { createLaravelQueueMessage } from 'laravel-queue-bridge';
const message = createLaravelQueueMessage({
jobName: 'App\\Jobs\\CompanyChangedJob',
properties: {
action: 'create',
payload: { id: 1, name: 'ABC Corporation', tel: '03-1234-5678' },
},
// Must match the PHP class property visibility exactly
visibility: {
action: 'private', // private $action in PHP
payload: 'protected', // protected $payload in PHP
},
});
channel.publish(exchange, 'tnet', Buffer.from(message));The library produces this message:
{
"uuid": "auto-generated",
"job": "Illuminate\\Queue\\CallQueuedHandler@call",
"data": {
"commandName": "App\\Jobs\\CompanyChangedJob",
"command": "<PHP serialized object with visibility prefixes>"
},
"attempts": 0
}The generated command contains proper PHP visibility prefixes:
O:26:"App\Jobs\CompanyChangedJob":2:{
s:34:"<\0>App\Jobs\CompanyChangedJob<\0>action";s:6:"create"; ← private
s:10:"<\0>*<\0>payload";a:3:{s:2:"id";i:1;s:4:"name";...} ← protected
}Laravel side (Consumer) — what the worker receives
// App\Jobs\CompanyChangedJob
class CompanyChangedJob implements ShouldQueue
{
private string $action; // ← visibility MUST match
protected $payload; // ← visibility MUST match
public function __construct(string $action, $payload)
{
$this->action = $action;
$this->payload = $payload;
}
public function handle(): void
{
// Laravel unserialize() maps the message directly to this class instance.
// $this->action === "create"
// $this->payload === ["id" => 1, "name" => "ABC Corporation", "tel" => "03-1234-5678"]
match ($this->action) {
'create' => $this->onCreate($this->payload),
'update' => $this->onUpdate($this->payload),
'delete' => $this->onDelete($this->payload),
};
}
}Laravel's queue worker calls unserialize() on data.command, which reconstructs the CompanyChangedJob instance with $action = "create" and $payload = [...] — then calls handle().
Why visibility matters
PHP serialize() encodes property visibility into the key using null-byte prefixes:
| Visibility | Serialized key | Example |
|------------|---------------|---------|
| public | propName | s:6:"action"; |
| protected | \0*\0propName | s:10:"\0*\0payload"; |
| private | \0ClassName\0propName | s:34:"\0App\Jobs\CompanyChangedJob\0action"; |
If visibility doesn't match, unserialize() will create the object but the properties will be null because PHP can't map the keys to the correct class properties.
More examples
// Job with all public properties — no visibility needed (default)
createLaravelQueueMessage({
jobName: 'App\\Jobs\\CompanyBatchUpdatedJob',
properties: { payload: [{ id: 1, name: 'Updated' }] },
});
// Job with protected property
createLaravelQueueMessage({
jobName: 'App\\Jobs\\UserServiceActivationChangeJob',
properties: { payload: { userId: 100, serviceCode: 'contact', type: 'add' } },
visibility: { payload: 'protected' },
});
// Forward slashes work too (auto-converted to backslashes)
createLaravelQueueMessage({
jobName: 'App/Jobs/UserChangedJob',
properties: { type: 'delete', payload: 100 },
visibility: { type: 'private' },
});API Reference
parseLaravelQueueMessage<T>(raw)
Parse a raw Laravel Queue message into a typed object.
| Parameter | Type | Description |
|-----------|------|-------------|
| raw | Buffer \| string | Raw message from the queue broker |
Returns LaravelQueueMessage<T>
interface LaravelQueueMessage<T = Record<string, any>> {
uuid: string; // Job UUID assigned by Laravel
jobName: string; // "App\\Jobs\\UserChangedJob"
shortName: string; // "UserChangedJob"
properties: T; // Deserialized job properties (visibility prefixes stripped)
attempts: number; // Number of retry attempts
}Throws PhpUnserializeError on malformed input.
createLaravelQueueMessage(options)
Create a Laravel Queue compatible JSON string ready to publish.
interface CreateLaravelQueueMessageOptions {
jobName: string; // PHP FQCN (accepts / or \\)
properties: Record<string, any>; // Job properties
visibility?: PhpVisibility | Record<string, PhpVisibility>; // Default: 'public'
uuid?: string; // Auto-generated if omitted
attempts?: number; // Default: 0
}
type PhpVisibility = 'public' | 'protected' | 'private';Returns string — JSON string.
phpUnserialize(serialized)
Low-level: deserialize a PHP serialize() string into a JavaScript value.
phpUnserialize('s:5:"hello";'); // "hello"
phpUnserialize('i:42;'); // 42
phpUnserialize('a:2:{s:2:"id";i:1;s:4:"name";s:5:"Alice";}'); // { id: 1, name: "Alice" }| PHP type | JS result |
|----------|-----------|
| s (string) | string |
| i (integer) | number |
| d (double) | number |
| b (boolean) | boolean |
| N (null) | null |
| a (array) | Record<string, any> |
| O (object) | Record<string, any> (class name discarded, visibility stripped) |
Handles: UTF-8 multi-byte strings, INF / -INF / NAN, nested structures, visibility prefixes.
phpSerialize(value)
Low-level: serialize a JavaScript value into a PHP serialize() compatible string.
phpSerialize('hello'); // 's:5:"hello";'
phpSerialize(42); // 'i:42;'
phpSerialize({ id: 1 }); // 'a:1:{s:2:"id";i:1;}'
phpSerialize([10, 20]); // 'a:2:{i:0;i:10;i:1;i:20;}'
phpSerialize('Hello 世界'); // 's:12:"Hello 世界";' (byte length, not char length)phpSerializeObject(className, properties, options?)
Low-level: serialize as a PHP object (O:) with class name and optional visibility.
phpSerializeObject('App\\Jobs\\MyJob', { action: 'test' }, {
visibility: { action: 'private' },
});
// O:15:"App\Jobs\MyJob":1:{s:23:"\0App\Jobs\MyJob\0action";s:4:"test";}PhpUnserializeError
Custom error thrown when deserialization fails.
try {
phpUnserialize('INVALID');
} catch (err) {
if (err instanceof PhpUnserializeError) {
err.message; // Human-readable error with position
err.position; // Character offset where parsing failed
err.source; // The original serialized string
}
}Exports
| Export | Direction | Description |
|--------|-----------|-------------|
| parseLaravelQueueMessage | Laravel → Node.js | Parse Laravel Queue message |
| createLaravelQueueMessage | Node.js → Laravel | Create Laravel Queue message |
| phpUnserialize | PHP → JS | Deserialize PHP string |
| phpSerialize | JS → PHP | Serialize to PHP string |
| phpSerializeObject | JS → PHP | Serialize as PHP object with visibility |
| PhpUnserializeError | — | Error class for parse failures |
| LaravelQueueMessage | — | TypeScript interface (parse result) |
| LaravelQueueEnvelope | — | TypeScript interface (raw JSON envelope) |
| CreateLaravelQueueMessageOptions | — | TypeScript interface (create options) |
| PhpVisibility | — | 'public' \| 'protected' \| 'private' |
| SerializeObjectOptions | — | TypeScript interface (visibility options) |
Requirements
- Node.js >= 14
- Zero runtime dependencies
License
MIT
