Error handling

Error handling

Lets take a look again at the previous example of authorization middleware.

auth.middleware.ts
import { HttpError, HttpStatus } from '@marblejs/core';
const authorize$: HttpMiddlewareEffect = req$ =>
req$.pipe(
switchMap(req => iif(
() => !isAuthorized(req),
throwError(new HttpError('Unauthorized', HttpStatus.UNAUTHORIZED)),
of(req),
)),
);

Marble.js comes with dedicated HttpError class for defining a request related errors that can be catched easily via error middleware. Using RxJS built-in throwError method, we can throw an error and catch it on an upper level (eg. directly inside Effect or ErrorEffect).

HttpError class resides in @marblejs/core package. The constructor takes as a first parameter an error message and as a second parameter a HttpStatus code that can be a plain JavaScript number or TypeScript enum type that can also be imported via core package. Optionally you can pass the third argument, which can contain any other error related values.

new HttpError('Forbidden', HttpStatus.FORBIDDEN, { resource: 'index.html' });

404 error handling

404 (Not Found) responses are not the result of an error, so the error handler will not capture them. This behavior is because a 404 response simply indicates the absence of matched Effect in request lifecycle; in other words, request didn't find a proper route. All you need to do is to define a dedicated Effect at the very end of the effects stack to handle a 404 response.

const notFound$ = r.pipe(
r.matchPath('*'),
r.matchType('*'),
r.useEffect(req$ => req$.pipe(
mergeMap(() =>
throwError(new HttpError('Route not found', HttpStatus.NOT_FOUND))
)
)));

The HttpEffect above handles all paths and all method types and throws an 404 error code that can be intercepted via HttpErrorEffect.

notFound$ Effects can be placed in any layer you want, eg. you can have multiple Effects that will handle missing routes in dedicated organization levels of your API structure.

Custom error handling effect

By default Marble.js comes with simple and lightweight error handling Effect. Because middlewares and Effects are based on the same generic interface, your error handlers can work very similar.

error.middleware.ts
const customError$: HttpErrorEffect<ThrownError> = (req$, res, meta) =>
req$.pipe(
mapTo(meta.error),
map(error => ({
status: error.status
body: error.data
}),
);

As any other Effect, error handler maps the stream of errored requests to objects of type HttpEffectResponse (status, body, headers). The HttpErrorEffect can retrieve from the third argument an intercepted error object which can be used for error handling-related logic.

To connect the custom error handler, all you need to do is to attach it to error$ property in httpListener config object.

http.listener.ts
httpListener({
middlewares,
effects,
// Custom error effect 👇
error$: customError$,
});