Skip to content

Build1 publisher3 min readPublished

Fulmine.js swaps Express's linear router for C++ matching, and the win grows with the route table

The one-line Express 5 replacement claims 2x to 20x faster routing. Its author is explicit that the spread lands on thousand-route tables, not on hello world.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened

  • Fulmine.js is a drop-in replacement for Express 5 that runs on uWebSockets.js instead of node:http.
  • The swap is one line: const express = require("fulmine.js") instead of require("express"); existing middleware keeps working.
  • The author claims routing gets between 2x and 20x faster with Fulmine.js.
  • Express's router walks a list: every request goes down the layers in order until one matches, so the more routes registered, the more work every request does.
  • On a small application nobody notices the router cost; on an application with a thousand routes, which is what a generated API layer or a multi-tenant backend looks like, it is the main cost of the request, and it is paid on every request before the application's own code runs.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

Fulmine.js is presented as a drop-in replacement for Express 5 that runs on uWebSockets.js instead of node:http, swapped in by changing one require line [1][2]. The claim attached to it is that routing gets between 2x and 20x faster [3], and the interesting part is the shape of the curve rather than the number: the author says the advantage grows with the size of the application instead of shrinking [8]. The mechanism is not exotic. Express's router walks a list of layers in order until one matches, so every route you register adds work to every request [4]. On a small application nobody notices; on an application with a thousand routes, which is what the author describes as a generated API layer or a multi-tenant backend, it becomes the main cost of the request, and it is paid before your own code runs at all [5]. Fulmine registers routes on uWebSockets.js's own router, which matches the path in C++ and hands over a chain worked out at startup, so nothing is scanned per request [6]. That framing is also the honest limit on the headline figure. The 2x to 20x range is scoped to routing [3], and the author states the gap is smallest on hello world and largest on the big route tables [8]. If your service has forty routes, you are buying the small end of that spread, not the large one. The numbers are the project's own. They are given as spreads over the last nine CI runs, landing on three different runner shapes, all on Node 26, with the author noting that a single figure from a single run would not be honest [7]. He also concedes that numbers produced by a project about itself deserve suspicion and points at HttpArena, run on other people's rigs under their own rules [9]. The more consequential piece is the zero-JavaScript path. A handler simple enough to be read at registration time is compiled into a uWS response written once at startup and answered without entering JavaScript at all [10]. The conditions are strict: nothing in front of the route, and a single handler that only calls res.status, res.set, res.type, res.send, res.json, res.sendStatus or res.end with literal arguments [11]. The author's argument is that real services are full of exactly that: health and readiness probes, config endpoints, feature flags, robots.txt, .well-known files, static manifests, and that in a Kubernetes cluster the probes alone are a large share of requests and pure framework overhead [12]. You are not left guessing. `npx fulmine.js profile` reports the verdict per route, and the sample output shows 3 routes, 2 answered by uWS itself, 1 of them without running any JavaScript [13] - one route in three on the fully compiled path in that example [21]. The same output shows `GET /flights/:from-:to` falling back with the message that uWS cannot match this path on its own [14], which is the failure mode to expect. There is also an explain command for a single route, a Server-Timing middleware that surfaces the routing verdict in the browser network panel, and assertions you can put in a test so a route that drops off the fast path turns the build red [15]. On compatibility, every test in the suite runs against real Express first and then against Fulmine, with outputs required to match byte for byte, and Express 5's own suite is reported passing whole at 1130 passing, 0 failing on the pinned version [16]. helmet, cors, passport, morgan, multer, express-session, compression and express-rate-limit are not ported, they just run [17]. A second suite serves the same application twice across NestJS, Next.js as a custom server, Astro, SvelteKit, React Router v7, Apollo Server, tRPC and Angular SSR [18], with a NestJS adapter shipped in the package [19].

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories