How ExpressJS may be Faster than Actix Web ( Rust ) in some cases.

Chris Kay
6 min readMay 28, 2023

--

Recently I posted an article where I compared 5 frameworks from 5 different languages and found out that Express handles static file serving 20x faster than any other framework ( rust, c++ included ). This amazed me, so I decided to dig deeper and perform multiple load tests and present you with the results in the form of magnificent graphs.

Catchy!

What I had found

While other frameworks took about 43ms to respond Express took ~2ms. Note that the load tests were performed in Rust. The index route only serves static files, meaning no heavy computation is done by the language, although, this time I also challenged the language speed by performing some intense stuff let’s say. You can find more about the research here with the Github repo at the bottom.

Probably One of the Most Controversial Graphs.

What is this research about?

I wanted to delve deeper by testing more thoroughly and more in general but I will only be testing Rust ( Actix Web ) and ExpressJS. Here is what I will be testing the frameworks on:

  1. Static file serving.
  2. A lot of MySQL Queries.
  3. File upload handling.
  4. Concurrency testing.

The servers are going to be tested in various degrees of such tests, for example we will look the performance of the servers when statically serving a single 20KB and when serving 3 files of combined 20KB Storage. The actual number of files has a significant effect on the performance. For the MySQL query test every server upon a request on an arbitrary route it will create a variable amount of rows in a table called users e.g. /users-create/10, /users-create/100. Where every user has the following fields: email ( randomly generated string ), name ( randomly generated string ) and password ( random hashed value ). At last we will test file uploading, where the server receives a file and writes it to a local folder.

Why is it so complex?

I tested the same endpoints with different tools, either self-made or opensource and found some conflicting results... The tools I used:

  1. My rust program that is basically a for loop and it discards any data that gets sent as response, that doesn’t mean ( or I hope so ) it gives one framework an advantage over the other.
  2. Ab: Apache Benchmark, thank you Markus Sandén for suggesting the tool. One of the popular, if not the most popular, benchmark tool
  3. Wrk: This is also a popular benchmark tool, it is more modern.
  4. Siege: Benchmark tool
  5. Firefox Network tab, I don’t think there is any difference between the Firefox and the other Browsers’ network tab, it is very informative and can say a lot about the timings.

The conflicting results were those of ab against the other tools’ results and my rust program, I will note every result conflicting or not and if you can make anything more out of these results please comment it.

Results:

Concurrency results

Version 1: Here happens a divergence. These results were gained from: my loadtester, Wrk and Siege ( Siege for some reason couldn’t connect with my Rust server, so the results only apply to ExpressJS ). Honestly when I went on Firefox I noticed something happening with probably server caching, but I couldn’t validate.

Version 1 results

version 2: This side has some more believable results, this side consists of ab. Apache benchmark reported Actix Web being faster, specifically ab reported that ExpressJS and Actix Web could perform 1897.15 requests/sec 11684.24 requests/sec respectively, which makes Rust almost 5 times faster than ExpressJS. The average time for a request being: 6284ms for ExpressJS and 855ms for Actix Web.

Static File Upload on the Browser

The Firefox network tab reported that for ExpressJS the receiving time was almost 0ms every time and the waiting time was 1–2ms, while Actix Web would occasionally have 40–80ms receiving times. I had the `disable cache` checkbox enabled so I prevented any caching from Firefox, but that didn’t change anything, even if I restarted the ExpressJS server it would still have a ~0 receiving timing.

Mysql integration

I think it would be more practical to report how they will perform when inserting rows in a user table and a post table, although I used hard-coded values. The special thing about inserting to the user table is that the server has to hash a password and this turned out to be an advantage for Rust. The post table’s only feature is that it has a foreign key, the user ID.

The routes

There are 2 routes: /create-user/{number} & /create-post/{uid}/{number}. They are pretty explanatory. The routes don’t insert all of them at once but do a loop, in a real occasion there wouldn’t be a client wanting to create many users at once, but you also wouldn’t have a loop, since we *know how each server handles concurrency I thought to just put a loop for a better interface.

NOTE: the query API for NodeJS is not asynchronous so at first the average time per batch ( no matter the batch ) was ~15ms, that’s because NodeJS doesn’t wait for the queries to end until it sends a response, this could lead to confusing problems. I used the util.promisify API to convert to an async function.

The results

The user insert results are not so surprising…

Average Time per batch of user insert queries.

But the post insert results are trickier…

Average Time per batch of user insert queries.

As I said above, I didn’t perform tests only once and with what sanity I have, I say that the MySQL NodeJS API is faster than that of Rust ( mysql ) and bcrypt is slow. I also did concurrent requests but the results were almost the same.

Bruteforce Loops

This test load tests one endpoint: /heavy/{iter}. Where iter is multiplied by 100,000 and a for loop executes 1+1 for that many times:

for _ in 0..100_000*iter { 1 + 1 }
for(let i = 0; i < 100_000*iter; i++) { 1 + 1 }

Note: As mentioned by @Hamish Moffatt rust ignores the for loop because it doesn’t have any effect, however, nodejs doesn’t and leads to nodejs being 2.5x for 10x more iterations.

results

1e⁶ total iterations
1e⁷ total iterations

The controversial thing is that for 10x iterations Rust remained still ( 1ms ) while ExpressJS ( NodeJS ) average request time tripled but managed to perform the same across every concurrency test.

File Uploading

The final test I did was to upload a 100M file to the server and let the server store the file in an uploads directory/folder.

As expected Rust ( Actix Web ) was faster than ExpressJS ( NodeJS ) by a magnitude of 2.

--

--