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. Below you can see how easily looks the dummy implementation of API requests logging middleware.

const dommyLogger$: Effect<HttpRequest> = (request$, response) =>
request$.pipe(
tap(req => console.log(`${req.method} ${req.url}`)),
);

There are two important differences compared to API Effects:

  • stream handler can take a response object as a second argument

  • middleware must return a stream of requests at the end of the pipeline

In the example above we get the stream of requests, tap console.log side effect and return the same stream as a response of our middleware pipeline. Then all we need to do is to attach the middleware to httpListener config.

const middlewares = [
dommyLogger$,
];
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 we need to curry our middleware by creating some kind of middleware factory, where the last returned function should conform toEffect<HttpRequst> generic interface.

const logger$ = (opt: { url: boolean } = {}): Effect<HttpRequest> => request$ =>
request$.pipe(
tap(req => console.log(`${req.method} ${opt.url && req.url}`)),
);

Which can be composed like in the following example:

const middlewares = [
bodyParser$,
logger$({ url: true }),
];

Middlewares composition

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

  • globally (inside httpListener configuration object),

  • inside grouped effects (via combineRoutes function),

  • or by composing it directly inside Effect request pipeline.

There are many case why 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 to do this using dedicated use operator responsible for composing middlewares 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.

user.controller.ts
import { authorize$ } from 'auth.middleware`;
const getUsers$: EffectFactory
.mathPath('/')
.matchType('GET')
.use(req$ => req$.pipe(
// 👇 middleware composition
use(authorize$),
// ...
);

Where example implementation of auth.middleware can look like in the following example:

auth.middleware.ts
const authorize$: Effect<HttpRequest> = request$ =>
request$.pipe(
switchMap(req => iif(
() => !isAuthorized(req),
throwError(new HttpError('Unauthorized', HttpStatus.UNAUTHORIZED)),
of(req),
)),
);

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

Available middlewares

In order to be more lightweight as possible, @marblejs/core package doesn't come with build-in request body parser and request logging. The core decision is to make it as more composable as possible via dedicated Marble.js middleware packages:

Request logging:

Request body parsing:

Request validation: