Real Time web applications with the Server Sent Events protocol. Symfony, ApiPlatform, Angular, Akita and Mercure
Tags: symfony, apiplatform, angular, mercure
Published: 2020-02-19 09:45:16

In this post i will show you how to create a modern reactive application using:
* PHP 7.4
* Symfony 5
* Api Platform 2.5
* Mercure
* Angular
* Akita

The work of this post is inspired by [Stefano ALLETTI post](https://stefanoalletti.wordpress.com/2019/06/17/just-another-poc-of-symfony-on-steroids%E2%80%A8-api-platform-vue-js-mercure-and-panther/)

Server-Sent Events (SSE) is a standard that enabled Web servers to push data in real time to clients. 

The idea is simple: a browser can subscribe to a stream of events generated by a server, receiving updates whenever a new event occurs. This led to the birth of the popular EventSource interface, which accepts an HTTP stream connection and keeps the connection open while retrieving available data from it. The connection is kept open until closed by calling EventSource.close().

Like a good self-respecting Frenchman, I love wine. That's why I decided to make an application allowing me to know in real time the availability of my favorite wine.
In this post, we will learn how to use SSE by building a live winestore product availability demo application with Symfony, Angular and Mercure. [You can find the final code of the application in this GitHub repository.](https://github.com/vichanse/symfony-angular-mercure)

## Building a Real-Time App with Server-Sent Events

In order to learn how to use SSE, we are going to develop a live wine stock availability application. The application will consist of a simple web page showing a list of wines and two modals, one showing wine details and another one for editing the wine quantity.

List      | Details   | Update
--------- | --------- | ---------
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/srv95r003nh21852xwf3.png)|![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jb5qfjygd9wy8gn6jtor.png)|![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/os4deu44bkdururzbtah.png)

Through this real-time app, we can find wine status availability and after implementing Server-Sent Events, we will see automagically updates when the status changes.

### Building Real-Time Backends with Server-Sent Events

Create a folder called **winestore**

For simplicity, i cloned the latest stable version of API Platform (2.5.4) into this folder. You can find inspiration from the project Github repository above.

```bash
$ mkdir winestore
```

#### Install Symfony and Api Platform

Firstly, make sure you have installed PHP 7.2.5 or a higher version and the Composer package manager to create a new Symfony application. after that, create a new project by executing the following command in the terminal:

```bash 
$ composer create-project symfony/skeleton api 
```

Now let's install some necessary bundles with composer

* Api Platform

```bash 
$ composer req api 
```

Make sure your database configurations are set in the .env file

```bash
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8"
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
DATABASE_URL=postgres://api-platform:!ChangeMe!@db/api?server_version=12
###< doctrine/doctrine-bundle ###
```

* Maker bundle to quickly build entities

```bash 
$ composer require symfony/maker-bundle --dev
```

Let's create three entities : [Wine](https://github.com/vichanse/symfony-angular-mercure/blob/master/api/src/Entity/Wine.php), [Comment](https://github.com/vichanse/symfony-angular-mercure/blob/master/api/src/Entity/Comment.php) and [MediaObject](https://github.com/vichanse/symfony-angular-mercure/blob/master/api/src/Entity/MediaObject.php) and we define them as **ApiResource**


* Hautelook/alice bundle for managing fixtures with Alice and Faker

```bash 
$ composer require --dev hautelook/alice-bundle 
```
Create a fixture file [wines.yaml](https://github.com/vichanse/symfony-angular-mercure/blob/master/api/fixtures/wines.yaml)

After this create and update the database

```bash
$ bin/console doctrine:schema:drop --force --no-interaction
$ bin/console doctrine:schema:create --no-interaction
```

And load your fixtures with this following command

```bash
$ bin/console hautelook:fixtures:load --no-interaction --purge-with-truncate -vvv
```

Opening the Swagger-Frontend you can now play around adding, deleting and viewing wines.

If you are using the symfony server, you need to open http://localhost:8000/api. If you use docker, the url is http://localhost:8080/api

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fiquihusgbpgzlj65vfj.png)

#### Mercure : Real-time made easy😛

> [Mercure](https://mercure.rocks/) is an open protocol allowing to push data updates to web browsers and other HTTP clients in a convenient, fast, reliable and battery-friendly way. It is especially useful to publish real-time updates of resources served through web APIs, to reactive web and mobile applications.

If you're following along using the API Platform distribution, you don't need to install mercure as it is already installed and configured.

If you installed from scratch using symfony flex you must:

##### 1) Install mercure

```bash
$  composer require mercure
```

##### 2) Download and run a Mercure hub

To manage persistent connections, Mercure relies on a Hub: a dedicated server that handles persistent SSE connections with the clients. The Symfony app publishes the updates to the hub, that will broadcast them to clients.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lcj43rkywopmx6933euc.png)

An official and open source (AGPL) implementation of a Hub can be downloaded as a static binary from [Mercure.rocks](https://mercure.rocks/pricing). Put the downloaded hub at the root of the project.

###### Run the following command to start it:

```bash
$  ./mercure --jwt-key='!changeMe!' --addr='localhost:3000' --allow-anonymous --cors-allowed-origins='*'
```

###### Configuration

The preferred way to configure the Mercure is by using environment variables.

Set the URL of your hub as the value of the MERCURE_PUBLISH_URL env var. The .env file of your project has been updated by the Flex recipe to provide example values. Set it to the URL of the Mercure Hub (http://localhost:3000/.well-known/mercure by default).

In addition, the Symfony application must bear a JSON Web Token (JWT) to the Mercure Hub to be authorized to publish updates.

This JWT should be stored in the **MERCURE_JWT_TOKEN** environment variable.

To create your token got to [jwt.io](https://jwt.io/). There you need to fill out two things, the payload and the secret.

The JWT must be signed with the same secret key as the one used by the Hub to verify the JWT (*!changeMe!* in our example). Its payload must contain at least the following structure to be allowed to publish:

```bash
{
    "mercure": {
        "publish": []
    }
}
```

Now that you're ready to use Mercure, you must add the **Mercure** option to the Wine ApiResource entity:

```php
/**
 * @ApiResource(mercure=true,normalizationContext={"groups"={"read"}},denormalizationContext={"groups"={"write"}})
 * @ORM\Entity(repositoryClass="App\Repository\WineRepository")
 */
class Wine
{
...
```

In this way API Platform will dispatch the updates to the Mercure hub every time a Wine is created, updated or deleted.

### Building Angular with Server-Sent Events

First thing, we need to generate an angular application. This is made very easy using the [angular cli](https://cli.angular.io/)

Go in the **winestore** folder and execute following commands:

```sh
# if you don't already have the cli installed
$ npm install -g @angular/cli
# generate new app
$ ng new api
# cd into the app
$ cd api
# Start it up
$ ng serve
```

Open your web-browser of choice and navigate to http://localhost:4200 (4200 is the default port for angular apps, but you can specify whatever port your heart desires). If all went well, you should see the angular default landing page.

In this post i will focus on Angular and SSE. A full example of the client application can be found [here](https://github.com/vichanse/symfony-angular-mercure/tree/master/client)

#### SSE and Angular

On the client side, we use the [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) to receive the stream events. This object has 3 eventHandler methods:
1. `EventSource.onerror` called when an error occurs,
2. `EventSource.onmessage` called when a message event is received
3. `EventSource.onopen` called when an open event is received

##### EventSource Observable Wrapper
To make it reactive and fit in an Angular application, it’s better to wrap it in an RxJS Observable.

Here is the `sse.service.ts` file

```typescript
import { Injectable, NgZone } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
    providedIn: 'root',
})
export class SseService {
    constructor(private zone: NgZone) {}
    private getEventSource(url: string): EventSource {
        return new EventSource(url);
    }

    getServerSentEvent(url: string): Observable<MessageEvent> {
        return new Observable(observer => {
            const eventSource = this.getEventSource(url);

            eventSource.onmessage = event => {
                this.zone.run(() => observer.next(event));
            };
        });
    }
}
```

The service method `getServerSentEvent`can then be subscribed like any other observable:

```typescript
this.sseService.getServerSentEvent('url/parameters').subscribe(data => console.log(data));
```

#### State Management with Akita

In order to keep track of what has been updated, we are going to use akita as a state management.

[Akita](https://netbasal.gitbook.io/akita/) is a state management pattern, built on top of RxJS. It is based on objected-oriented design so developers with OOP experience should feel right at home.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rg0m2gtjywr4g2uh9g8k.png)

👉🏾 [10 Reasons Why You Should Start Using Akita as Your State Management Solution](https://engineering.datorama.com/10-reasons-why-you-should-start-using-akita-as-your-state-management-solution-66b63d033fec)

Akita has an API [runStoreAction](https://netbasal.gitbook.io/akita/stores/event-based-apis-support) that simplify and improve the experience of working with event-based APIs such as Mercure in our example.

#### Setting Up Akita

Adding Akita to our project is easy. We can use the NG add schematic by running the following command:

```console
ng add @datorama/akita
```

The above command adds Akita, Akita's dev-tools, and Akita's schematics into our project. The next step is to create a store. We need to maintain a collection of wines, so we scaffold a new entity feature:

```console
ng g af wines
```

This command generates a wines store, a wines query, a wines service, and a wine model for us. Those files can be found [here](https://github.com/vichanse/symfony-angular-mercure/tree/master/client/src/app/features/wines/state)

#### Using `runStoreAction`in our service


```typescript
...
import { runStoreAction, StoreActions } from '@datorama/akita';

@Injectable({ providedIn: 'root' })
export class WinesService extends NgEntityService<WinesState> {
...
sync() {
        const url = new URL(APP_CONFIG.merculeHubUrl);
        url.searchParams.append('topic', `${APP_CONFIG.apiWineUrl}/{id}`);

        return this.sseService.getServerSentEvent(`${url}`).pipe(
            tap(event => {
                const data = JSON.parse(event.data);
                const stock = data.stock;
                this.notificationService.add();
                runStoreAction('wines', StoreActions.UpdateEntities, {
                    payload: {
                        data: { stock },
                        entityIds: data.id,
                    },
                });
            }),
        );
    }
...
}
```

In our service `sync` method we subscribe to server-sent events and update our store by running store action `UpdateEntities`

## Summary

In this post, we used Server-Sent Events to develop a real-time application that simulates product availability in a winestore. During the development, we had the opportunity to explore Symfony, Api Platform, Mercure, Angular and Akita. 
There is a lot more to develop such modern reactive applications. This post provides a good starting point but i highly recommend going through the docs and trying out different tools.

📢 An example project containing all of the above can be found [here](https://github.com/vichanse/symfony-angular-mercure)


Have you enjoyed the post? Please share🤘🏾.