Bidirectional bridge between PHP and Node.js — call Node.js handlers from PHP and PHP handlers from Node.js. Framework agnostic with first-class Laravel and Symfony support.
Works with: Laravel, Symfony, Slim, Zend/Laminas, vanilla PHP ↔ Express, Next.js, Nuxt.js, Fastify, NestJS.
Your PHP App Your Node App
(Laravel / Symfony / Slim) (Express / Next / Nuxt)
PhpBridge ◄──── HTTP ────► NodeBridge
:5556 :5555
register() ← Node can call register() ← PHP can call
call() → calls Node call() → calls PHP
Both sides register named handlers and communicate over local HTTP. JSON payloads, shared-secret auth, timeout handling, middleware support.
composer require anandpilania/php-node-bridge<?php
require 'vendor/autoload.php';
use PhpNodeBridge\Bridge\PhpBridge;
$bridge = new PhpBridge([
'port' => 5556,
'nodePort' => 5555,
'secret' => 'my-secret',
]);
// Register handlers Node.js can call
$bridge
->register('invoice.create', function (array $payload): array {
// your DB logic here
return ['id' => 123, 'total' => 99.99];
})
->register('user.find', function (array $payload): array {
return ['id' => $payload['id'], 'name' => 'Alice'];
});
// Call Node.js (e.g. generate a PDF)
$pdf = $bridge->call('pdf.generate', [
'template' => 'invoice',
'data' => ['invoiceId' => 123],
]);
// Start listener (blocking — run as a separate daemon)
$bridge->listen();The service provider is auto-discovered via composer.json. Just install and publish config:
composer require anandpilania/php-node-bridge
php artisan vendor:publish --tag=bridge-configBRIDGE_PORT=5556
NODE_BRIDGE_PORT=5555
NODE_BRIDGE_HOST=127.0.0.1
BRIDGE_SECRET=your-super-secret-key
BRIDGE_TIMEOUT=10// app/Providers/AppServiceProvider.php
public function boot(PhpBridge $bridge): void
{
$bridge->register('invoice.create', function (array $p): array {
return Invoice::create($p)->toArray();
});
}use PhpNodeBridge\Laravel\Bridge; // Facade
class DocumentController extends Controller
{
public function sign(Request $request)
{
$result = Bridge::call('esign.sign', $request->all());
return response()->json($result);
}
public function sendEmail(Request $request)
{
Bridge::fire('email.send', $request->all()); // fire-and-forget
return response()->json(['queued' => true]);
}
}php artisan bridge:listen// config/bundles.php
return [
PhpNodeBridge\Symfony\PhpNodeBridgeBundle::class => ['all' => true],
];# config/packages/php_node_bridge.yaml
php_node_bridge:
port: '%env(int:BRIDGE_PORT)%'
node_port: '%env(int:NODE_BRIDGE_PORT)%'
node_host: '%env(NODE_BRIDGE_HOST)%'
secret: '%env(BRIDGE_SECRET)%'
timeout: 10
log_level: 'info'use PhpNodeBridge\Bridge\PhpBridge;
class DocumentController extends AbstractController
{
public function __construct(private PhpBridge $bridge) {}
#[Route('/api/pdf', methods: ['POST'])]
public function pdf(Request $request): JsonResponse
{
$result = $this->bridge->call('pdf.generate', json_decode($request->getContent(), true));
return $this->json($result);
}
}php bin/console bridge:listen| Option | Type | Default | Description |
|---|---|---|---|
port |
int | 5556 |
Port this PHP bridge listens on |
nodePort |
int | 5555 |
Port the Node bridge listens on |
nodeHost |
string | 127.0.0.1 |
Node bridge host |
secret |
string | null |
Shared secret for auth (recommended) |
timeout |
int | 10 |
Default call timeout in seconds |
logLevel |
string | 'info' |
silent/error/warn/info/debug |
Register a handler Node.js can call.
Register multiple handlers: ['name' => fn, ...].
Remove a handler.
Call a handler on the Node.js side.
Fire-and-forget call to Node.js.
Add middleware: function(string $name, array $payload): void. Throw to reject.
Start listening (blocking). Run in a separate daemon process.
List all registered handler names.
| Class | When |
|---|---|
BridgeException |
Base — connection failure, generic errors |
HandlerNotFoundException |
Requested handler not registered |
TimeoutException |
Call exceeded timeout |
AuthException |
Wrong or missing shared secret |
; /etc/supervisor/conf.d/php-bridge.conf
[program:php-node-bridge]
command=php /var/www/html/artisan bridge:listen
directory=/var/www/html
autostart=true
autorestart=true
startretries=3
user=www-data
stdout_logfile=/var/log/supervisor/bridge.log
stderr_logfile=/var/log/supervisor/bridge-err.log[Unit]
Description=PHP Node Bridge
After=network.target
[Service]
User=www-data
WorkingDirectory=/var/www/html
ExecStart=/usr/bin/php artisan bridge:listen
Restart=always
[Install]
WantedBy=multi-user.targetservices:
php:
build: ./php-app
environment:
NODE_BRIDGE_HOST: node
BRIDGE_SECRET: ${BRIDGE_SECRET}
ports:
- "8000:8000"
- "5556:5556"
node:
build: ./node-app
environment:
PHP_BRIDGE_HOST: php
BRIDGE_SECRET: ${BRIDGE_SECRET}
ports:
- "3000:3000"
- "5555:5555"php tests/run.phpMIT