Back to Home

SSE notifications in SPA: real time without polling

The article describes the architecture of asynchronous notifications in SPA based on SSE and Mercure. Integration with CDC for tracking DB changes, reuse of OpenAPI schemas for typing. Code examples and diagrams for developers.

SSE Push-notifications in SPA: architecture and code
Advertisement 728x90

Asynchronous Data Updates in SPAs Using SSE and Mercure

Frontend applications require constant data updates to reflect backend changes. Standard API calls capture the state at the moment of response, but the backend continues to process events: account top-ups, content moderation, support replies. To address this, push mechanisms based on SSE are used, integrated with the existing OpenAPI specification.

The system combines a pull approach (periodic requests) with push (real-time notifications). SSE provides a unidirectional event stream over HTTP/2, minimizing load compared to polling.

Use Cases for Asynchronous Notifications

Asynchronous notifications are critical in business processes:

Google AdInline article slot
  • Balance top-up: Displaying the current state for immediate purchases.
  • Content moderation: Updating status after review.
  • Support replies: Instant notification about a new ticket.

In a typical case, the user—an advertiser or contractor—tracks request counters: the number requiring action, unread comments, placement checks. This data is available via the API endpoint /rest/User/info (operationId: getInfo).

/rest/User/info:
  get:
    tags:
      - User
    summary: Returns information about the current user
    operationId: getInfo
    responses:
      200:
        description: Information about the current user
        content:
          application/json:
            schema:
              type: object
              properties:
                id:
                  type: integer
                  format: int32
                  minimum: 0
                locale:
                  type: string
                  enum: [ru, en]
                login:
                  type: string
                  minimum: 3
                  maximum: 320
                seoCounters:
                  $ref: '#/components/schemas/SeoCounters'

The SeoCounters schema defines the structure:

SeoCounters:
  type: object
  properties:
    nofRequests:
      type: integer
      format: int32
      minimum: 0
    nofLinksStatusNeedApprove:
      type: integer
      format: int32
      minimum: 0
    nofLinksWithUnreadComments:
      type: integer
      format: int32
      minimum: 0
    nofChanges:
      type: integer
      format: int32
      minimum: 0
    nofPlacementCheck:
      type: integer
      format: int32
      minimum: 0

Integrating CDC and Data Platform

Changes in the user_counters table are tracked via CDC (Change Data Capture). The modification stream is sent to the Data Platform—a corporate ETL system. From there, events are pushed to the UI via an SSE hub.

Google AdInline article slot

The architecture uses a hexagonal model: the OpenAPI specification automatically generates REST adapters and TypeScript types. For the frontend, the types look like this:

/* Generated by orval */
export interface SeoCounters {
  nofRequests?: number;
  nofLinksStatusNeedApprove?: number;
  nofLinksWithUnreadComments?: number;
  nofChanges?: number;
  nofPlacementCheck?: number;
}

export type GetInfo200 = {
  id: number;
  locale: 'ru' | 'en';
  login: string;
  seoCounters?: SeoCounters;
};

These types are reused for processing SSE events, ensuring type safety.

Implementing SSE in the Application

SSE (Server-Sent Events) is a standard for server pushes, supported by browsers. EventSource connects to the endpoint:

Google AdInline article slot
const evtSource = new EventSource('/sse-endpoint', {
  withCredentials: true,
});

evtSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  // Update the store with typed data
  updateSeoCounters(data.seoCounters);
};

For authorization, topics, and scaling, Mercure is used—an open-source hub based on SSE. The hub accepts publications via REST with JWT and broadcasts to subscribers.

Example publication:

curl -d 'topic=https://example.com/books/1' \
     -d 'data={"foo": "updated value"}' \
     -H 'Authorization: Bearer <JWT>' \
     -X POST https://hub/.well-known/mercure

JWT defines publish/subscribe rights for topics. Mercure is deployed as a container with an external database, integrating with CDC streams.

Advantages of the Approach

  • Schema Reuse: OpenAPI structures are used in pull and push without duplication.
  • Real-Time: Changes are reflected instantly without polling.
  • Scalability: The Mercure hub distributes the load.

List of key counters for monitoring:

  • nofRequests — action requests.
  • nofLinksStatusNeedApprove — links awaiting approval.
  • nofLinksWithUnreadComments — unread comments.
  • nofPlacementCheck — placement checks.

Key Takeaways

  • SSE with Mercure provides typed push notifications without WebSocket overhead.
  • CDC captures database changes in real-time for ETL processes.
  • Auto-generating TypeScript types from OpenAPI ensures data consistency.
  • The approach scales for high-load systems with thousands of users.
  • JWT in Mercure manages access to notification topics.

— Editorial Team

Advertisement 728x90

Read Next