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

v0.2.2 2026-02-23 23:32 UTC

This package is auto-updated.

Last update: 2026-02-28 14:53:41 UTC


README

A Laravel-to-Bun bridge that lets you call JavaScript/TypeScript functions from PHP over Unix sockets.

Requirements

  • PHP 8.2+ with the sockets extension
  • 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

  1. bun:serve starts one or more Bun processes, each with a bundled TypeScript worker
  2. Each worker scans your functions directory and registers all exported functions
  3. PHP communicates with Bun over Unix sockets using length-prefixed binary frames
  4. The BunBridge singleton maintains persistent socket connections and round-robins across workers

Support

If this saved you time, consider supporting the project:

Buy Me A Coffee

License

MIT