Update User Benchmarks

These benchmarks are based on the Fastify benchmarks repo. Our goal is to perform similarly to Fastify as we consider it the industry standard in terms of performance.

Note that using an RPC-style router avoids possible performance overhead that can come from parsing parameters in the URL path and similar techniques used in REST APIS i.e. /api/user/:id.

What is tested?

This Update User benchmark involves validation and serialization on top of routing.

The test consists of an updateUser request where the fields of the user must be deserialized and validated and the response must be serialized.

The lastUpdate field is a date that must be transformed into a JS Date (deserialized) and validated, then a month is added to the Date and the updated user is sent back in the response.

export interface User {
  id: number;
  name: string;
  surname: string;
  lastUpdate: Date;
}

// ### mion ###
// the received user by the route is already validated and deserialized
// user.lastUpdate is already a js date instead and string (result of JSON.parse)
const routes = {
  updateUser: (ctx, user: User): User => {
    user.lastUpdate.setMonth(user.lastUpdate.getMonth() + 1);
    return user;
  },
} satisfies Routes;

// ### Express ###
// A plugin must be used to parse the json body
// validation must be done manually and user.lastUpdate must be deserialized manually into a date
// in this case developer would have to manually write `isUser` and `deserializeUser` functions. (check src code fo those functions)
app.post('/updateUser', function (req, res) {
  const rawUser = req.body?.updateUser;
  if (!isUser(rawUser)) throw 'app error, invalid parameter, not a user';
  const user = deserializeUser(rawUser);
  user.lastUpdate.setMonth(user.lastUpdate.getMonth() + 1);
  res.json(user);
});

Notes

We can observe how in this benchmark the requests take considerably more time compared with the 'hello world' benchmark. This is mostly because each request is spending extra time on validation and serialization.

For this specific test, the performance of the libraries used for validation and serialization might be way more important than the routing itself.

A manually written validation function has been used for any library that does not include a validation library by default.


Benchmarks

  • Machine: darwin x64 | 8 vCPUs | 16.0GB Mem
  • Node: v20.11.0
  • Run: Mon Jan 29 2024 22:19:00 GMT+0000 (Greenwich Mean Time)
  • Method: autocannon -c 100 -d 40.02 -p 10 localhost:3000 (two rounds; one to warm-up, one to measure)

Req (R/s)

Throughput (Mb/s)

Latency (ms)

Max Memory (Mb)

Memory Series (MB)


Results Table

VersionRouterReq (R/s)Latency (ms)Output (Mb/s)Max Memory (Mb)Max Cpu (%)ValidationDescription
http-node16.18.0โœ—18598.053.244.4779126โœ—bare node http server, should be the theoretical upper limit in node.js performance
mion.bun0.6.2โœ“17023.258.223.94111106โœ“mion using bun, automatic validation and serialization
fastify4.10.2โœ“16961.858.414.0987122-Validation using schemas and ajv. schemas are generated manually
mion0.6.2โœ“13936.071.213.85136139โœ“Automatic validation and serialization out of the box
restify11.1.0โœ“12776.677.703.28130125โœ—manual validation or third party tools
hapi21.3.2โœ“8870.8112.092.13103134โœ—validation using joi or third party tools
hono3.12.6โœ“5763.7172.661.39123134โœ—hono node server, manual validation or third party tools
express4.18.2โœ“4596.6213.241.10122126โœ—manual validation or third party tools
Benchmarks Repo