Oxicode / marble

Marble.js - functional reactive HTTP middleware framework built on top of Node.js platform, TypeScript and RxJS library.

Home Page:http://marblejs.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Marble.js logo

Travis-CI status npm version Codecov coverage Maintained with lerna

Functional reactive HTTP middleware framework built on top of Node.js platform, TypeScript and RxJS library.

Table of content

  1. Philosophy
  2. Installation
  3. Getting started
  4. Effects
  5. Routes composition
  6. Middlewares
  7. Custom error handling
  8. Examples
  9. Roadmap

Philosophy

If you don't have any experience with functional reactive programming, we strongly recommend to gain some basic overview first with ReactiveX intro or with The introduction to Reactive Programming you've been missing written by @andrestaltz.

If we think closely how typical HTTP API works we can quickly recognize that it deals with streams of asynchronous events also called as HTTP requests. Describing it very briefly - typically each request needs to be transformed into response that goes back to the client (which is our event initiator) using custom middlewares or designated endpoints. In reactive programming world, all those core concepts we can translate into very simple marble diagram:

Marble.js core concept

In this world everything is a stream. The core concept of Marble.js is based on the event flow of marble diagrams which are used to visually express time based behavior of HTTP streams. Ok, but why the heck we need those observables? Trends come and go, but asynchronously nature of JavaScript and Node.js platform constantly evolves. With reactive manner we can deliver complex features faster by providing the ability to compose complex tasks with ease and with less amount of code. If you have ever worked with libraries like Redux Observable, @ngrx/effects or other libraries that leverages functional reactive paradigm, you will feel like in home. Still there? So lets get started!

Installation

Marble.js requires node v8.0 or higher:

$ npm i @marblejs/core rxjs

or if you are a hipster:

$ yarn add @marblejs/core rxjs

Getting started

The bootstrapping consists of two very simple steps: HTTP handler definition and HTTP server configuration.

httpListener is the starting point of every Marble.js application. It includes definitions of all middlewares and API effects.

const middlewares = [
  logger$,
  bodyParser$,
];

const effects = [
  endpoint1$,
  endpoint2$,
  ...
];

const app = httpListener({ middlewares, effects });

Because Marble.js is built on top of Node.js platform and doesn't create any abstractions for server bootstrapping - all you need to do is to call createServer with initialized app and then start listening to given port and hostname.

const httpServer = http
  .createServer(app)
  .listen(PORT, HOSTNAME);

Effects

Effect is the main building block of the whole framework. Using its generic interface we can define API endpoints (so called: Effects), middlewares and error handlers (see next chapters). The simplest implementation of API endpoint can look like this:

const endpoint$: Effect = request$ => request$
  .pipe(
    mapTo({ body: `Hello, world!` })
  );

The sample Effect above matches every HTTP request that passes through request$ stream and responds with Hello, world! message. Simple as hell, right?

Every API Effect request has to be mapped to object which can contain attributes like body, status or headers. If status code or headers are not passed, then API by default will respond with 200 status and application/json Content -Type header.

A little bit more complex example can look like this:

const postUser$: Effect = request$ => request$
  .pipe(
    matchPath('/user'),
    matchType('POST'),
    map(req => req.body),
    switchMap(Dao.postUser),
    map(response => ({ body: response }))
  );

The framework by default comes with two handy operators for matching urls (matchPath) and matching method types (matchType). The example above will match every POST request that matches to /user url. Using previously parsed POST body (see $bodyParser middleware) we can map it to sample DAO which returns a response object as an action confirmation.

The matchType operator can also deal with parameterized URLs like /foo/:id/bar

Routes composition

Every API requires composable routing. With Marble.js routing composition couldn't be easier:

// user.controller.ts

const getUsers$: Effect = request$ => request$
  .pipe(
    matchPath('/'),
    matchType('GET'),
    // ...
  );

const postUser$: Effect = request$ => request$
  .pipe(
    matchPath('/'),
    matchType('POST'),
    // ...
  );

export const user$ = combineRoutes(
  '/user',
  [ getUsers$, postUser$ ],
);

// api.controller.ts

import { user$ } from 'user.controller.ts';

const root$: Effect = request$ => request$
  .pipe(
    matchPath('/'),
    matchType('GET'),
    // ...
  );

const foo$: Effect = request$ => request$
  .pipe(
    matchPath('/foo'),
    matchType('GET'),
    // ...
  );

const api$ = combineRoutes(
  '/api/v1',
  [ root$, foo$, user$ ],
);

Effects above will be mapped to following API endpoints:

GET    /api/v1
GET    /api/v1/foo
GET    /api/v1/user
POST   /api/v1/user

Middlewares

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 logger$: Effect<HttpRequest> = (request$, response) => request$
  .pipe(
    tap(req => console.log(`${req.method} ${req.url}`)),
  );

There are two important differences compared to API Effects:

  1. stream handler takes a response object as a second argument
  2. middlewares must return a stream of requests at the end of middleware pipeline

In the example above we are getting the stream of requests, tapping console.log side effect and returning the same stream as a response of our middleware pipeline. Then all you need to do is to attach the middleware to httpListener config.

const middlewares = [
  logger$,
];

const app = httpListener({ middlewares, effects });

Custom error handling

By default Marble.js comes with simple and lightweight error handling middleware. Because Middlewares and Effects are based on the same generic interface, your error handling middlewares works very similar to normal API Effects.

const error$: Effect<EffectResponse, ThrownError> = (request$, response, error) => request$
  .pipe(
    map(req => ({
      status: // ...
      body:  // ...
    }),
  );

As any other Effects, error middleware maps the stream of errored requests to objects of type EffectsResponse (status, body, headers). The difference is that it takes as a third argument an intercepted error object which can be used for error handling-related logic.

To connect the custom middleware, all you need to do is to attach it to errorMiddleware property in httpListener config object.

const app = httpListener({
  middlewares,
  effects,

  // Custom error middleware:
  errorMiddleware: error$,
});

Examples

To view the example project structure, clone the Marble.js repository and install the dependencies:

$ git clone git://github.com/marblejs/marble.git
$ cd marble/example
$ npm i

To run example just execute following command inside root repository folder:

$ npm run start

Roadmap

Marble.js is not yet a final and production ready product. Its APIs can improve over time when reaching stable version 1.0.0. But in the meantime you can play easily and contribute to the growing community of functional reactive programming freaks.

  • core mechanics
  • custom middlewares
  • custom error handlers
  • composable routing
  • intercepting url parameters (via matchPath operator) (v0.3.0)
  • ability to compose midddlewares inside Effect pipeline (v0.3.0)
  • intercepting query parameters (v0.3.0)
  • more middlewares! (can think about moving logger$ and bodyParser$ outside core library)
  • testing utilities
  • improved, dedicated documentation (to move outside README)

Authors

Józef Flakus

Contributors

Sebastian Musial

Patryk Domałeczny

License

marble.js is MIT licensed

About

Marble.js - functional reactive HTTP middleware framework built on top of Node.js platform, TypeScript and RxJS library.

http://marblejs.com

License:MIT License


Languages

Language:TypeScript 96.3%Language:JavaScript 2.5%Language:Shell 0.7%Language:Makefile 0.4%