It is a common requirement to encounter the necessity of having various operations needed around incoming HTTP requests to your server. In Marble.js middlewares are streams of side-effects that can be composed and plugged-in to our request lifecycle to perform certain actions before reaching the designated Effect.

Building your own middleware

Because everything here is a stream, also plugged-in middlewares are based on similar Effect interface. By default, framework comes with composable middlewares like: logging, request body parsing or request validator. Below you can see how simple can look the dummy HTTP request logging middleware.

const logger$ = (req$: Observable<HttpRequest>, res: HttpResponse): Observable<HttpRequest> =>
tap(req => console.log(`${req.method} ${req.url}`)),

In the example above we get the stream of requests, then tap the console.log side effect and returns the same stream as a response of our middleware pipeline. The middleware, in comparison to basic effect, must return the request at the end.

If you prefer shorter type definitions, you can use a HttpMiddlewareEffect function interface.

const logger$: HttpMiddlewareEffect = (req$, res) =>
// ...

In order to use our custom middleware, what we need to do is to attach the defined middleware to httpListener config.

const middlewares = [
// ๐Ÿ‘‡ our custom middleware
const app = httpListener({ middlewares, effects });

Parametrized middleware

There are some cases when our custom middleware needs to be parametrized - for example dummy logger$ middleware should console.log request URL's conditionally. To achieve this behavior we have to make our middleware function curried, where the last returned function should conform toHttpMiddlewareEffect interface.

interface LoggerOpts {
showUrl?: boolean;
const logger$ = (opts: LoggerOpts = {}): HttpMiddlewareEffect => req$ =>
tap(req => console.log(`${req.method} ${opts.showUrl ? req.url : ''}`)),

The improved logging middleare, can be composed like in the following example:

const middlewares = [
// ๐Ÿ‘‡ our custom middleware
logger$({ showUrl: true }),

Sending a response earlier

Some types of middlewares need to send a HTTP response earlier. In this case Marble.js exposes a dedicated res.send method which allows to send a HTTP response using the same common interface that we use for sending a response inside API Effects. The mentioned method returns an empty Observable (Observable that immediately completes) as a result, so it can be composed easily inside middleware pipeline.

const middleware$: HttpMiddlewareEffect = (req$, res) =>
switchMap(() => res.send({ body: ๐Ÿ’ฉ, status: 304, headers: /* ... */ }),

If the HTTP response is sent ealier than inside the target Effect, the execution of all following middlewares and Effects will be skipped.

Middlewares composition

In Marble.js you can compose middlewares in four ways:

  • globally (inside httpListener configuration object),

  • inside grouped effects (via combineRoutes function),

  • or by composing it directly inside Effect request pipeline.

via Effect

There are many scenarrios where we would like to apply middlewares inside our API Effects. One of them is to authorize only specific endpoints. Going to meet the requirements, Marble.js allows us to compose them using dedicated use operator, directly inside request stream pipeline.

Lets say we have an endpoint for getting list of all users registered in the system, but we would like to make it secure, and available only for authorized users. All we need to do is to compose authorization middleware using dedicated for this case use operator which takes as an argument our middleware.

import { use, r } from '@marblejs/core';
import { authorize$ } from './auth.middleware';
const getUsers$ = r.pipe(
// ๐Ÿ‘‡ here...
r.useEffect(req$ => req$.pipe(
// ๐Ÿ‘‡ or here...
// ...

Using r.pipe operators, the middlwares can be composed in two ways. The first one doesn't infer the returned HttpRequest type of chained middlewares.

The example implementation of authorize$ middleware can look like in the following snippet:

const authorize$: HttpMiddlewareEffect = req$ =>
mergeMap(req => iif(
() => !isAuthorized(req),
throwError(new HttpError('Unauthorized', HttpStatus.UNAUTHORIZED)),

As you probably noticed, auth.middleware introduces an example use case of error handling. You can read more about it in dedicated Error handling chapter.

via combineRoutes

There are some cases where you have to compose a bunch of middlewares before grouped routes, eg. to authorize only a selecteted group of endpoints. Instead of composing middlewares for each route separately, using use operator, you can also compose them via extended second parameter incombineRoutes() function.

const api$ = combineRoutes('api/v1', {
middlewares: [ authorize$ ],
effects: [ user$, movie$, actor$ ],

via httpListener

If your middleware should operate globally, eg. in case of request logging, the best place is to compose it inside httpListener. In this case the middleware will operate for each request that will go through your HTTP server.

const middlewares = [
const effects = [
// ...
export default httpListener({ middlewares, effects });

The stacking order of middlewares inside httpListener and combineRoutes matters, because middlewares are run sequentially (one after another).