DEV Community

Cover image for Marble.js vs. Express.js: Comparing Node.js web frameworks
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Marble.js vs. Express.js: Comparing Node.js web frameworks

Written by David Ekanem ✏️

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), you may want to pause before reading this. I’d recommend Conal Elliott’s work, 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 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, 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 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 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.

Features and Criteria Marble.js v3 Express v4
Minimum supported Node.js version ≥v8.0 ≥v0.10.0
TypeScript support
Inbuilt middleware
Core package size 102 kB 208 kB
HTTP helpers
Modular design
I/O handling
Support for fp-ts@2.0
Async readers
Request body parsing
Logging - pluggable, out-of-the-box-solution for server/service logging.
RxJS support

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.

$ mkdir MarbleJsApp
Enter fullscreen mode Exit fullscreen mode

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

$ code
Enter fullscreen mode Exit fullscreen mode

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

$ yarn init -y
Enter fullscreen mode Exit fullscreen mode

Install the @marblejs/http module:

$ yarn add @marblejs/core @marblejs/http fp-ts rxjs
Enter fullscreen mode Exit fullscreen mode

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.

$ mkdir ExpressJsApp
Enter fullscreen mode Exit fullscreen mode

Open up the ExpressJsApp folder.

$ code
Enter fullscreen mode Exit fullscreen mode

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

$ yarn init -y
Enter fullscreen mode Exit fullscreen mode

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

$ yarn add express
Enter fullscreen mode Exit fullscreen mode

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:

$ touch index.ts http.listener.ts api.effects.ts
Enter fullscreen mode Exit fullscreen mode

In our index.ts file, we first create a Marble app instance with the createServer method from the @marblejs/http module; the createServer method is a wrapper around the Node.js server creator.

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();
Enter fullscreen mode Exit fullscreen mode

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

$ yarn add typescript ts-node
Enter fullscreen mode Exit fullscreen mode

Add the following script to our package.json file:

 "scripts": {
    "start": "ts-node index.ts"
  },
Enter fullscreen mode Exit fullscreen mode

Start the server:

$ yarn start
Enter fullscreen mode Exit fullscreen mode

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.

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

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

Run the Express application with the following command:

$ node app.js
Enter fullscreen mode Exit fullscreen mode

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.

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}`);
});
Enter fullscreen mode Exit fullscreen mode

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 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:

$  yarn add @marblejs/middleware-logger @marblejs/middleware-body
Enter fullscreen mode Exit fullscreen mode

And define the httpListener like so:

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,
});
Enter fullscreen mode Exit fullscreen mode

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

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!' }),
  )));
Enter fullscreen mode Exit fullscreen mode

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, which runs 5000000 requests with 125 concurrent connections with the following command:

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

$ bombardier -c 125 -n 5000000 http://127.0.0.1:1337/
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

Benchmarking Express.js HTTP performance

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

$ bombardier -c 125 -n 5000000 http://127.0.0.1:1338/
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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 ✔️ 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.

LogRocket Network Request Monitoring

LogRocket 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.

Top comments (0)