Middlewares

Custom middleware

Like any other Effect, Marble.js defines a similar way for defining middlewares. In case of WebSocket protocol it is a function that transforms stream of incoming events 👉 stream of outgoing events.

Lets create a simple logger middleware like we previously did in case of HTTP.

logger.ws-middleware.ts
import { Event } from '@marblejs/core';
import { WsMiddlewareEffect } from '@marblejs/websockets';
export const logger$: WsMiddlewareEffect = event$ =>
event$.pipe(
tap(e => console.log(`type: ${e.type}, payload: ${e.payload}`)),
);
// or an equivalent
export const logger$ = (event$: Observable<Event>): Observable<Event> =>
event$.pipe(
tap(e => console.log(`type: ${e.type}, payload: ${e.payload}`)),
);

Similar to any other middleware or effect, we have to register it inside the listener.

webSocket.listener.ts
import { webSocketListener } from '@marblejs/websockets';
import { logger$ } from './logger.ws-middleware';
export const webSocketServer = webSocketListener({
middlewares: [logger$],
// ...
});

Middlewares composition

Similar to HTTP routes, you can compose middlewares in two ways:

  • globally (inside webSocketListener configuration object),

  • by composing it directly inside Effect event pipeline.

The use operator availabe in @marblejs/core package allows you to compose multiple types of middlewares - not only HTTP-related. Lets examine how can we compose the @marblejs/middleware-io middleware in event$ pipeline.

import { use } from '@marblejs/core';
import { WsEffect } from '@marblejs/websockets';
import { eventValidator$, t } from '@marblejs/middleware-io';
const User = t.type({
name: t.string,
age: t.number,
});
export const createUser$: WsEffect = event$ =>
event$.pipe(
matchEvent('CREATE_USER'),
use(eventValidator$(User)),
// ... event.payload: { name: string, age: number }
);