Marble.js vs. Express.js: Comparing Node.js web frameworks
Tags: javascript, webdev, discuss, programming
Published: 2021-11-16 14:45:42

**Written by [David Ekanem](https://blog.logrocket.com/author/davidekanem/)** ✏️

In this article, we’ll compare Express.js — one of the most commonly used server-side frameworks for building web applications with Node.js — against Marble.js, another, newer, reactive server-side Node.js framework.

## Prerequisites

If you’re not familiar with [functional reactive programming (FRP)](https://en.wikipedia.org/wiki/Functional_reactive_programming), you may want to pause before reading this. I’d recommend [Conal Elliott’s work](https://github.com/conal/essence-and-origins-of-frp), which contains a lot more precise information.

The general gist of FRP is that it’s a general paradigm for describing dynamic (a.k.a., time-varying) information and the successive side effects of sequential execution. Conal Elliott captures the meaning of FRP with this statement:

> _FRP is concurrent without running afoul of the theoretical and pragmatic rats’ nest that plagues imperative concurrency_.

Andre Staltz, the author of the Cycle.js framework and a core contributor to RxJS, gives this wonderful explanation as well:

> _Reactive programming raises the level of abstraction of code, so one can focus on the interdependence of events that define the business logic, rather than having to constantly fiddle with a large amount of implementation details._

This allows for explicit execution of our programs. Once execution is started on a process on a new thread, it should be capable of being controlled.

Now, let’s get on with our comparison.

## Marble.js vs. Express.js: A basic comparison

### What is Marble.js?

[Marble.js](https://github.com/marblejs/marble) is a functional reactive Node.js framework for building server-side applications. It’s based on TypeScript and RxJS and relies on the reactive paradigm, with functional sugar on top of the Node platform.

The main building block of Marble.js is [an Effect](https://docs.marblejs.com/http/effects), or a function that returns a stream of events.

Due to the event-based nature of Marble.js, when a service performs an action that other services might be interested in, that service produces an event, which is a record of the performed action. Other services consume those events to perform the actions needed as a result of the event.

An Effect is suitable for implementing distributed event-handling systems, and as such, Marble.js can be used for general message-driven architecture (MDA) solutions, like WebSockets or microservices, in addition to operating over basic HTTP protocols.

### What is Express.js?

[Express.js](https://expressjs.com/) is a fast, flexible, and minimalistic Node.js web development framework that can design single-page, multi-page, and hybrid applications with a set of robust features.

Express offers [lots of middleware](https://blog.logrocket.com/express-middleware-a-complete-guide/) and other inbuilt support to make API creation easy, as well as support for third-party modules for interfacing with databases. These advantages allow you to use nearly any form of database with Express.

For a high-level comparison of Marble v3 and Express v4, let’s look at the table below.

<table class=" aligncenter">

<tbody>

<tr>

<td>Features and Criteria</td>

<td>Marble.js v3</td>

<td>Express v4</td>

</tr>

<tr>

<td>Minimum supported Node.js version</td>

<td>≥v8.0</td>

<td>≥v0.10.0</td>

</tr>

<tr>

<td>TypeScript support</td>

<td>✅</td>

<td>❌</td>

</tr>

<tr>

<td>Inbuilt middleware</td>

<td>✅</td>

<td>✅</td>

</tr>

<tr>

<td>Core package size</td>

<td>102 kB</td>

<td>208 kB</td>

</tr>

<tr>

<td>HTTP helpers</td>

<td>✅</td>

<td>✅</td>

</tr>

<tr>

<td>Modular design</td>

<td>✅</td>

<td>✅</td>

</tr>

<tr>

<td>I/O handling</td>

<td>✅</td>

<td>✅</td>

</tr>

<tr>

<td>Support for fp-ts@2.0</td>

<td>✅</td>

<td>❌</td>

</tr>

<tr>

<td>Async readers</td>

<td>✅</td>

<td>✅</td>

</tr>

<tr>

<td>Request body parsing</td>

<td>✅</td>

<td>✅</td>

</tr>

<tr>

<td>Logging - pluggable, out-of-the-box-solution for server/service logging.</td>

<td>✅</td>

<td>✅</td>

</tr>

<tr>

<td>RxJS support</td>

<td>✅</td>

<td>✅</td>

</tr>

</tbody>

</table>

## Creating our Marble.js and Express.js directories

We are going to create a “Hello, World!” program using Marble.js and Express.js to outline how routing is handled in Marble.js and Express.js and run a benchmarking tool on the created web applications to see which of the two frameworks is faster.

As we noted above, Express.js requires Node ≥v0.10.0, while Marble.js requires Node ≥v8.0\. I will be using Yarn as my package manager and the Windows operating system.

In your desired project directory. create the folder that will hold your Marble application.

```bash
$ mkdir MarbleJsApp
```

Open up your `MarbleJsApp` folder in your favorite code editor; I will be using VS Code.

```bash
$ code
```

Once it’s open, open the terminal window and run the command:

```bash
$ yarn init -y
```

Install the `@marblejs/http` module:

```bash
$ yarn add @marblejs/core @marblejs/http fp-ts rxjs
```

Every @marblejs/* package requires @marblejs/core + fp-ts + rxjs to be installed first.

In another project directory, create the folder that will hold your Express application.

```bash
$ mkdir ExpressJsApp
```

Open up the `ExpressJsApp` folder.

```bash
$ code
```

In the terminal window, run the command to create the `package.json` file.

```bash
$ yarn init -y
```

Now, open your `ExpressJsApp` folder and the terminal window and install Express.

```bash
$ yarn add express
```

## Creating a Marble.js server

Since version 4.0, the `@marblejs/http` package contains all HTTP-related APIs, and we need to install this package in order for Marble to carry out the two basic configurations we need for creating our server-side application. The two basic configurations are the HTTP listener definition and the HTTP server configuration.

We are going to create three base files in our project’s root directory:

*   `index.ts`
*   `http.listener.ts`
*   `api.effects.ts`

Open up the terminal window and input these commands to create the files:

```bash
$ touch index.ts http.listener.ts api.effects.ts
```

In our `index.ts` file, we first create a Marble app instance with the `createServer` method from the [`@marblejs/http module`](https://docs.marblejs.com/other/api-reference/marblejs-http); the `createServer` method is a wrapper around the Node.js server creator.

```javascript
import { createServer } from '@marblejs/http';
import { IO } from 'fp-ts/lib/IO';
import { listener } from './http.listener';

const server = createServer({
  port: 1337,
  hostname: '127.0.0.1',
  listener,
});

const main: IO<void> = async () =>
  await (await server)();

main();
```

To test-run the newly created server, we install the TypeScript compiler and `ts-node`:

```bash
$ yarn add typescript ts-node
```

Add the following script to our `package.json` file:

```javascript
 "scripts": {
    "start": "ts-node index.ts"
  },
```

Start the server:

```bash
$ yarn start
```

The server should be running. You can check using your browser by visiting `http://localhost:1337`.

## Creating an Express.js server

We create an `index.js` file in our `ExpressJSApp` folder and create a server that listens on `port 1338` for connections.

```javascript
const express = require("express");
const app = express();
const port = 1338;

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});
```

Run the Express application with the following command:

```bash
$ node app.js
```

We have been able to create our server with only a few lines of code — a sneak peek into the power and simplicity of Express.

We can open up our browser tab and paste in the URL `http://localhost:1338`. The app should respond with “Hello, world!”

## Marble.js vs. Express.js: Routing

Let’s walk through how to handle routing in both Express.js and Marble.js.

Express.js contains a lot of helper functions in their `req` and `res` objects, which comes with methods such as `res.send`, `res.download`, `res.redirect`, and so on.

```javascript
const express = require("express");
const app = express();
const port = 1338;
app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});
```

By comparison, defining routes in Marble is done through defining an API effect. As a reminder, an effect is simply a stream of events that responds to incoming requests. In our case, it would return our “Hello, world!” message.

To build out our route in Marble, we open the `http.listener.ts` file to define our [`httpListener`](https://docs.marblejs.com/other/api-reference/marblejs-http/core-httplistener) program. This is the basic starting point of every Marble application, as it includes the definition of all global middleware and API effects.

We are then going to install two more important modules in our application: one for logging and one for parsing.

Install middleware packages with the following command:

```bash
$  yarn add @marblejs/middleware-logger @marblejs/middleware-body
```

And define the `httpListener` like so:

```javascript
import { httpListener } from '@marblejs/http';
import { logger$ } from '@marblejs/middleware-logger';
import { bodyParser$ } from '@marblejs/middleware-body';
import { api$ } from './api.effects';

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

const effects = [
  api$,
];

export const listener = httpListener({
  middlewares,
  effects,
});
```

Using the Effects interface, we can define API endpoints or event handlers. We are going to create an API effect that returns “Hello, world!” to the user.

Open up the `api.effects.ts` file. Here, we’ll create our first endpoint. In order to route our first `httpEffect`, we have to define the path and method with which the incoming requests should be matched.

Here, we are specifying the `matchPath` property as `("/")`. Our method with the `matchType` property is a `GET` method.

### Defining our first endpoint in `api.effects.ts`

```javascript
import { r } from '@marblejs/http';
import { mapTo } from 'rxjs/operators';

export const api$ = r.pipe(
  r.matchPath('/'),
  r.matchType('GET'),
  r.useEffect(req$ => req$.pipe(
     mapTo({ body: 'Hello, world!' }),
  )));
```

If we go back to `http://localhost:1337`, we should see our “Hello, world!” message being returned.

## Comparing performance benchmarks

**Hardware:**

*   Laptop: Lenovo Thinkpad W540
*   CPU: Intel ® Core(™) i7-4800MQ CPU @ 2.70GHz

**Conditions:**

*   125 connections
*   5000000 requests

### Benchmarking Marble.js HTTP performance

For testing, I’ll use the [Go Bombardier package](https://github.com/codesenberg/bombardier), which runs 5000000 requests with 125 concurrent connections with the following command:

Run the following command, specifying the URL of the Marble.js project.

```bash
$ bombardier -c 125 -n 5000000 http://127.0.0.1:1337/
```

Below is our result from running the test on our Marble.js application.

```plaintext
Bombarding http://127.0.0.1:1337/ with 5000000 request(s) using 125 connection(s)
 5000000 / 5000000 [=====================================================================================] 100.00% 2355/s 35m22s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec      2368.27     790.92    6658.29
  Latency       53.07ms    12.42ms   601.59ms
  HTTP codes:
    1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:   604.92KB/s
```

### Benchmarking Express.js HTTP performance

Run the following command, specifying the URL of the Express.js project.

```bash
$ bombardier -c 125 -n 5000000 http://127.0.0.1:1338/
```

Below is our result from running the test on our Express.js application.

```plaintext
Bombarding http://127.0.0.1:1338/ with 5000000 request(s) using 125 connection(s)
 5000000 / 5000000 [================] 100.00% 5738/s 14m31s
Done!
Statistics        Avg      Stdev        Max
  Reqs/sec      5745.13    1164.50   34982.51
  Latency       21.78ms     4.02ms   411.92ms
  HTTP codes:
    1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0
    others - 0
  Throughput:     1.65MB/s
```

The purpose of the benchmarking test is all about performance and comparing the performance of the two frameworks. With this in mind, the most important metric is the request count per second.

When considering this measurement, we want the highest possible value because the more requests per second the application can handle, the faster the application.

Here are the results of our benchmark tests:

| Framework                               | Marble.js | Express.js |
|-----------------------------------------|-----------|------------|
| Requests per second                     | 2368.27   | 5745.13    |
| Latency (ms)                            | 53.07     | 21.78      |
| Total time to handle 5,000,000 requests | 35 mins   | 14 mins    |

By this metric, Express.js has a higher latency. Looking at our benchmark test results, the Express.js application performed much better than the Marble.js application.

## Conclusion

Express.js is great for creating simple applications, as we have seen from creating our “Hello, world!” program, building out simple routing, and ease of learning.

In our benchmarking test, Express turned out to be much more capable of handling a higher number of requests than Marble.js.

Marble.js provides support for TypeScript with great support for the higher-level functions provided by RxJS, and with its dedicated `@marblejs/messaging` module, Marble.js offers a robust and unified architecture for building event-based microservices.

Express.js is great for building RESTful APIs, but when it comes to building applications with an event-driven architecture, Marble.js excels.

Express.js is still popular and will continue to be used in major projects and industries, but Marble.js is quite popular within the proponents of functional reactive programming. Hopefully, we will continue to see an increase in Marble’s usage among developers who are looking to incorporate its asynchronous nature into applications that rely on servers to handle heavy work and complex calculations without freezing the main thread.

---

## 200’s only :heavy_check_mark: Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, [try LogRocket](https://logrocket.com/signup/). 

[![LogRocket Network Request Monitoring](https://blog.logrocket.com/wp-content/uploads/2019/12/network-request-filter-2-1.png)]
(https://logrocket.com/signup/)

[LogRocket](https://logrocket.com/signup/) is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. [Start monitoring for free](https://logrocket.com/signup/).