ramonmalcolm10 / lara-bun
A Laravel-to-Bun bridge via Unix sockets
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/ramonmalcolm10/lara-bun
Requires
- php: ^8.2
- illuminate/console: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Suggests
- inertiajs/inertia-laravel: Required for Bun SSR gateway
README
A Laravel-to-Bun bridge that lets you call JavaScript/TypeScript functions from PHP over Unix sockets.
Requirements
- PHP 8.2+ with the
socketsextension - Laravel 11, 12, or 13
- Bun runtime
Installation
composer require ramonmalcolm10/lara-bun
The service provider and Bun facade are auto-discovered.
Setup
1. Create a functions directory
Place your TypeScript or JavaScript functions in resources/bun/:
resources/bun/
greet.ts
math.ts
Each exported function becomes callable from PHP:
// resources/bun/greet.ts export function greet({ name }: { name: string }) { return `Hello, ${name}!`; }
2. Start the bridge
php artisan bun:serve
This starts a Bun process that listens on a Unix socket, auto-discovers all .ts/.js files in your functions directory, and waits for calls from PHP.
Usage
Via dependency injection
use RamonMalcolm\LaraBun\BunBridge; class MyController extends Controller { public function index(BunBridge $bridge) { $greeting = $bridge->call('greet', ['name' => 'World']); return view('welcome', ['greeting' => $greeting]); } }
Via the Bun facade
use RamonMalcolm\LaraBun\Facades\Bun; $greeting = Bun::call('greet', ['name' => 'World']); $available = Bun::list(); $isRunning = Bun::ping();
API
| Method | Description |
|---|---|
call(string $function, array $args = []): mixed |
Call a Bun function by name |
ssr(array $page): array |
Render an Inertia page via SSR |
list(): array |
List all discovered function names |
ping(): bool |
Check if the Bun bridge is running |
disconnect(): void |
Close all socket connections |
Configuration
Publish the config file:
php artisan vendor:publish --tag=lara-bun-config
This creates config/bun.php:
return [ 'socket_path' => env('BUN_BRIDGE_SOCKET', '/tmp/bun-bridge.sock'), 'functions_dir' => env('BUN_BRIDGE_FUNCTIONS_DIR', resource_path('bun')), 'workers' => (int) env('BUN_WORKERS', 1), ];
| Option | Env Variable | Default | Description |
|---|---|---|---|
socket_path |
BUN_BRIDGE_SOCKET |
/tmp/bun-bridge.sock |
Base path for the Unix socket(s) |
functions_dir |
BUN_BRIDGE_FUNCTIONS_DIR |
resources/bun |
Directory to scan for functions |
workers |
BUN_WORKERS |
1 |
Number of Bun worker processes |
ssr.enabled |
BUN_SSR_ENABLED |
false |
Enable Bun-based Inertia SSR |
entry_points |
BUN_BRIDGE_ENTRY_POINTS |
[] |
Comma-separated paths to additional JS/TS bundles |
Multi-Worker Support
By default, Lara Bun runs a single Bun process. Under concurrent load, renderToString() blocks the event loop and requests queue up sequentially. Multi-worker mode spawns N independent Bun processes on separate Unix sockets, with PHP round-robining across them for parallel rendering.
BUN_WORKERS=4
php artisan bun:serve # Starting Bun bridge with 4 workers # Worker 0: /tmp/bun-bridge-0.sock # Worker 1: /tmp/bun-bridge-1.sock # Worker 2: /tmp/bun-bridge-2.sock # Worker 3: /tmp/bun-bridge-3.sock
Each worker is a fully isolated Bun process. If a worker crashes, it is automatically restarted. Requests that hit an unavailable worker fail over to the next one.
Socket naming
| Workers | Socket path(s) |
|---|---|
| 1 | /tmp/bun-bridge.sock |
| N | /tmp/bun-bridge-0.sock ... /tmp/bun-bridge-{N-1}.sock |
Recommended workers
A good starting point is matching your Octane worker count, or the number of CPU cores available for SSR rendering.
Artisan Command
# Start with default settings php artisan bun:serve # Override socket path php artisan bun:serve --socket=/tmp/my-socket.sock
Inertia SSR
Lara Bun can handle Inertia server-side rendering through the Unix socket instead of running a separate Node HTTP server.
1. Update your SSR entry point
The standard Inertia SSR entry point uses createServer() to start an HTTP server. For Lara Bun, export a render function instead:
React (resources/js/ssr.jsx):
import { createInertiaApp } from '@inertiajs/react' import ReactDOMServer from 'react-dom/server' export async function render(page) { return createInertiaApp({ page, render: ReactDOMServer.renderToString, resolve: name => { const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true }) return pages[`./Pages/${name}.jsx`] }, setup: ({ App, props }) => <App {...props} />, }) }
Vue (resources/js/ssr.js):
import { createInertiaApp } from '@inertiajs/vue3' import { renderToString } from 'vue/server-renderer' import { createSSRApp, h } from 'vue' export async function render(page) { return createInertiaApp({ page, render: renderToString, resolve: name => { const pages = import.meta.glob('./Pages/**/*.vue', { eager: true }) return pages[`./Pages/${name}.vue`] }, setup({ App, props, plugin }) { return createSSRApp({ render: () => h(App, props) }).use(plugin) }, }) }
Svelte (resources/js/ssr.js):
import { createInertiaApp } from '@inertiajs/svelte' import { render as svelteRender } from 'svelte/server' export async function render(page) { return createInertiaApp({ page, resolve: name => { const pages = import.meta.glob('./Pages/**/*.svelte', { eager: true }) return pages[`./Pages/${name}.svelte`] }, setup({ App, props }) { return svelteRender(App, { props }) }, }) }
The key difference: no createServer() import — just export the render function directly.
2. Build the SSR bundle
bun run build
This produces bootstrap/ssr/ssr.js.
3. Enable Bun SSR
Add to your .env:
BUN_SSR_ENABLED=true
The bundle path is read from Inertia's inertia.ssr.bundle config. If Inertia isn't configured, it falls back to bootstrap/ssr/ssr.mjs.
4. Start the bridge
php artisan bun:serve
The command automatically passes the SSR bundle (along with any other configured entry points) to the Bun worker. When inertiajs/inertia-laravel is installed, the Inertia\Ssr\Gateway binding is automatically replaced with BunSsrGateway, which routes render calls through the Unix socket.
No separate SSR server is needed — bun:serve handles both your custom functions and Inertia SSR.
Custom entry points
You can also load arbitrary JS/TS bundles beyond the SSR bundle using the BUN_BRIDGE_ENTRY_POINTS env var (comma-separated paths):
BUN_BRIDGE_ENTRY_POINTS=dist/my-bundle.js,dist/another.js
Each exported function from these files becomes callable via BunBridge::call().
Laravel Octane
If you're using Laravel Octane, add BunBridge to the warm array in config/octane.php to keep the socket connection alive across requests:
'warm' => [ ...Octane::defaultServicesToWarm(), \RamonMalcolm\LaraBun\BunBridge::class, ],
Performance
Benchmarked against Inertia's default HTTP SSR (php artisan inertia:start-ssr --runtime=bun) with 100 iterations, no warmup:
| Avg | Min | Max | PHP Memory | |
|---|---|---|---|---|
| Lara Bun (Unix Socket) | 2.39ms | 1.73ms | 4.75ms | +0MB |
| Inertia HTTP SSR (Bun) | 3.36ms | 2.32ms | 19.47ms | +12.5MB |
~30% faster with zero additional PHP memory overhead. Unix sockets skip the TCP stack entirely — communication is just memory copies in the kernel.
Worker memory
Each Bun worker uses ~10MB RSS under load. Memory plateaus at ~10MB under load, then GC kicks in and drops it back down to ~3MB. No memory leak — Bun's JavaScriptCore garbage collector is cleaning up properly. After 16,000 SSR renders the process is using less memory than when it started.
| Workers | Memory |
|---|---|
| 1 | ~10MB |
| 4 | ~40MB |
How It Works
bun:servestarts one or more Bun processes, each with a bundled TypeScript worker- Each worker scans your functions directory and registers all exported functions
- PHP communicates with Bun over Unix sockets using length-prefixed binary frames
- The
BunBridgesingleton maintains persistent socket connections and round-robins across workers
Support
If this saved you time, consider supporting the project:
License
MIT