Pushing Data to Clients Using the Mercure Protocol

Dec 07, 2025 2 min read 423 views
Pushing Data to Clients Using the Mercure Protocol

Overview

Broadcasting data in real-time from servers to clients is an essential requirement for many modern web and mobile applications.

Common Use Cases:

  • Creating UIs that react live to changes made by other users
  • Notifying users when asynchronous jobs complete
  • Building chat applications

What is Mercure?

Mercure is an open protocol specifically designed to publish updates from servers to clients. It's a modern and efficient alternative to:

  • Timer-based polling
  • WebSocket

Key Features:

  1. Built on SSE: Uses Server-Sent Events, supported natively in modern browsers
  2. Authorization Mechanism: Built-in authentication system
  3. Automatic Reconnection: With recovery of lost updates
  4. Presence API: Know which users are connected
  5. Connection-less Push: For smartphones
  6. Auto-discoverability: Clients can automatically discover and subscribe to updates

Installation

Installing the Symfony Bundle:

composer require mercure

Running a Mercure Hub:

Mercure relies on a Hub (central dispatcher) to manage persistent connections. The Symfony app publishes updates to the Hub, which broadcasts them to clients.

[Symfony App] --POST--> [Mercure Hub] --SSE--> [Clients]

Configuration

Essential Environment Variables:

Variable Description
MERCURE_URL Local URL for the Hub (used by Symfony app)
MERCURE_PUBLIC_URL Public URL (used by browser JavaScript)
MERCURE_JWT_SECRET Secret key for signing JWTs

Configuration Example (YAML):

# config/packages/mercure.yaml
mercure:
    hubs:
        default:
            url: '%env(string:MERCURE_URL)%'
            public_url: '%env(string:MERCURE_PUBLIC_URL)%'
            jwt:
                secret: '%env(string:MERCURE_JWT_SECRET)%'

Basic Usage

Publishing:

namespace App\Controller;

use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;

class PublishController extends AbstractController
{
    public function publish(HubInterface $hub): Response
    {
        // Create a new update
        $update = new Update(
            'https://example.com/books/1',  // Topic
            json_encode(['status' => 'OutOfStock'])  // Data
        );

        // Publish the update
        $hub->publish($update);

        return new Response('Published!');
    }
}

Notes:

  • First parameter (topic): Unique identifier for the resource (usually a URL or IRI)
  • Second parameter: Update content (JSON or XML recommended)

Subscribing:

In a Twig template:

<script>
const eventSource = new EventSource("{{ mercure('https://example.com/books/1') }}");
eventSource.onmessage = event => {
    // Called every time an update is published
    console.log(JSON.parse(event.data));
}
</script>

Subscribing to Multiple Topics:

const eventSource = new EventSource("{{ mercure([
    'https://example.com/books/1',
    'https://example.com/books/2',
    'https://example.com/reviews/{id}'  // URI Template
]) }}");

Discovery

Symfony can expose the Hub URL in an HTTP header:

use Symfony\Component\Mercure\Discovery;

class DiscoverController extends AbstractController
{
    public function discover(Request $request, Discovery $discovery): JsonResponse
    {
        // Adds: Link: <https://hub.example.com/.well-known/mercure>; rel="mercure"
        $discovery->addLink($request);

        return $this->json(['@id' => '/books/1']);
    }
}

Authorization

Private Updates:

To send updates only to authorized users:

$update = new Update(
    'https://example.com/books/1',
    json_encode(['status' => 'OutOfStock']),
    true  // Private update
);

Subscribing to Private Updates:

<script>
const eventSource = new EventSource(
    "{{ mercure('https://example.com/books/1', { subscribe: 'https://example.com/books/1' }) }}",
    { withCredentials: true }  // Important for sending cookies
);
</script>

Testing

Unit Testing:

use Symfony\Component\Mercure\MockHub;

$hub = new MockHub(
    'https://internal/.well-known/mercure',
    new StaticTokenProvider('foo'),
    function(Update $update): string {
        return 'id';
    }
);

API Platform Integration

use ApiPlatform\Core\Annotation\ApiResource;

#[ApiResource(mercure: true)]  // Enable automatic broadcasting
#[ORM\Entity]
class Book
{
    public string $name = '';
    public string $status = '';
}

This enables automatic update broadcasting when any book is created, modified, or deleted.


Summary

Concept Explanation
Mercure Protocol for real-time data pushing
Hub Intermediary server managing connections
Topic Unique identifier for the updated resource
SSE Browser technology for listening to updates
JWT Token for authentication and authorization

source: symfony docs v 6.4

AM

Author Amer Malik Mohammed

Full-Stack Developer with 2+ years of experience in object-oriented PHP development (Symfony), JavaScript and MySQL. Specialized in e-commerce solutions (Shopware 5/6), REST API development and process automation in agile teams.

Contact author