Sarıkaya Dev Logo

Building Event-Driven Microservices in Laravel with NATS JetStream and Serverless Functions

Mahmut Sarıkaya 4 min read 1 Views 0
Building Event-Driven Microservices in Laravel with NATS JetStream and Serverless Functions

Why Event-Driven Microservices Matter

Can a Laravel monolith keep up with a surge of 10,000 requests per second during a flash sale? In most real‑world cases the answer is no, because synchronous request‑response cycles create bottlenecks and make scaling unpredictable. An event‑driven microservice architecture decouples producers from consumers, allowing each service to scale independently, retry failures, and evolve without breaking contracts. The pattern also aligns with modern DevOps pipelines where deployments happen continuously and latency budgets are measured in milliseconds.

Setting Up NATS JetStream for Laravel

NATS JetStream adds durable streaming, message replay, and at‑least‑once delivery on top of the lightweight NATS core. Before writing any Laravel code, ensure the server meets the following requirements: Linux or macOS, Docker 19.03+, and at least 2 GB of RAM. Installation can be performed with a single Docker command, which guarantees the same version across development and CI environments.

docker run -d --name nats-jetstream -p 4222:4222 -p 8222:8222 nats:latest -js

After the container is running, verify the health endpoint at http://localhost:8222/. The dashboard shows streams, consumers, and message statistics, which are useful during debugging.

Integrating Laravel Events with JetStream

Laravel already ships with a powerful event system. To bridge it with NATS, install a lightweight wrapper such as php-nats/jetstream. The package registers a service provider that resolves a JetStream facade. Below is a minimal configuration in config/services.php:

return [
    'nats' => [
        'host' => env('NATS_HOST', '127.0.0.1'),
        'port' => env('NATS_PORT', 4222),
    ],
];

Next, create a listener that publishes the event to a JetStream subject. The listener uses the official NATS PHP client; notice the double backslashes required for JSON escaping.

use Nats\Connection;
use Nats\JetStream\Context;
use Illuminate\Support\Facades\Log;

class OrderCreatedListener
{
    public function handle($event)
    {
        $nc = new Connection('nats://'.config('services.nats.host').':'.config('services.nats.port'));
        $js = $nc->jetstream();
        $payload = json_encode([
            'order_id' => $event->order->id,
            'status'   => $event->order->status,
        ]);
        $js->publish('orders.created', $payload);
        Log::info('Published order.created event to JetStream');
    }
}

Register the listener in EventServiceProvider and fire the event from any controller or service class. Because JetStream stores the message, a consumer that starts later can still replay the entire history.

Deploying Serverless Functions as Consumers

Serverless platforms such as AWS Lambda, Google Cloud Functions, or Azure Functions can act as JetStream consumers without managing any server. The following Node.js example shows a Lambda handler that receives a base64‑encoded NATS message via the NATS‑to‑HTTP bridge (NATS server can expose a HTTP gateway). The function logs the order and returns a 200 status.

exports.handler = async (event) => {
    const msg = Buffer.from(event.data, 'base64').toString();
    console.log('Received order:', msg);
    // Business logic like updating a read model can go here
    return { statusCode: 200 };
};

Deploy the function with the official CLI, then create a JetStream consumer that pushes messages to the function’s endpoint. The consumer configuration includes deliver_policy: "all" to guarantee that every historic event is processed, and ack_wait: 30s to handle transient network glitches.

Testing the Flow Locally

Laravel provides php artisan test for unit and integration tests. To verify the end‑to‑end pipeline, spin up a Docker network that contains the Laravel app, NATS JetStream, and a lightweight HTTP server that mimics the serverless endpoint. The test script publishes a fake OrderCreated event, then asserts that the mock endpoint received a JSON payload with the correct order_id. Because JetStream guarantees at‑least‑once delivery, the test can be run repeatedly without flakiness.

public function testOrderCreatedIsPublished()
{
    Http::fake([
        'http://localhost:8080/*' => Http::response('', 200),
    ]);

    $order = Order::factory()->create();
    event(new OrderCreated($order));

    // Give JetStream a moment to forward the message
    sleep(1);

    Http::assertSent(function ($request) use ($order) {
        $data = json_decode($request->body(), true);
        return $data['order_id'] === $order->id;
    });
}

The test confirms that the Laravel listener correctly serializes the event, JetStream stores it, and the serverless consumer processes it. Adjust the sleep duration or use a more sophisticated polling mechanism for CI pipelines where timing is critical.

Conclusion

By coupling Laravel’s expressive event system with NATS JetStream’s durable streaming and a serverless consumer layer, teams can build truly decoupled microservices that scale on demand. The stack eliminates the need for heavyweight brokers, reduces operational overhead, and keeps latency under 50 ms in most benchmarks. Start with the Docker‑based JetStream setup, add the PHP client, and gradually migrate high‑traffic use cases to serverless consumers for cost‑effective scaling.

Sources

Official NATS JetStream Documentation

Laravel Event Broadcasting Guide

AWS Lambda Developer Guide

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #Laravel #NATS JetStream #event-driven architecture #microservices #serverless
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

3 + 4 =