Ep.09: Fastify (Basic App for Node Backend Frameworks Comparison)
Teolin Codreanu
Software Developer | Microservices / APIs in Node.js, JavaScript, TypeScript, Nest.js / Koa / Express etc
This article is part of an in-depth comparison series of the top Node frameworks. We'll cover all the important aspects: market share, learning curve, ecosystem, security and more. To compare them properly, we will build the exact same app in all these frameworks (plus Vanilla Node), observe all the steps along the way and then benchmark them as we progressively add more functionalities.
Table of contents
You've probably noticed that some of the frameworks we're comparing are radically different, like Nest.js. We've seen that Nest is built on top of Express or Fastify. This brings us to Fastify, an apparently simpler framework - with a twist. Well, several twists. In fact, quite a few, almost like a rock'n'roll tune. In the big 5 Whales Pool, Fastify plays the role of the fin-whale, slim and fast.
According to itself, Fastify is a "web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture". Errrm... what does that mean?
Well, it tries to make your life easier by providing support for async/await operations and dependency injection. Thus, fewer callbacks and less time spent managing dependencies. Just like Express and Hapi (the frameworks that inspired Fastify), it's minimalist and modular and it works very well with plugins: it sports 78 official plugins covering almost every need and plenty third-party ones. That may sound like a lot less compared with Express, but fewer options also mean less time trying and deciding, so it can sometimes be a good thing.
Their namesake claim is speed. Well, ahem... in the Node world, anything that claims it's blazing-fast, it's probably not. If you have a need for speed, you're barking at the wrong tree here, you should go for something closer to the metal like C++, Go or Rust. Node.js excels in building scalable and efficient network applications and offers an amazingly vast ecosystem of ready-made solutions that can make development easier and faster but does not shine when it comes to speed or computing prowess.
That being said, many benchmarks do place Fastify ahead of Koa, Express or even vanilla Node. Most of these benchmarks are synthetic, and I'm not completely sure they reflect real-life usage, so we're going to use the apps we're currently building to put that claim to the test later on.
If you're curious about what makes Fastify faster, it claims it's due to its superior serialisation through the use of schemas for routing. The standard JSON parser could indeed be slowing other frameworks, although I noticed others picked up on this and use alternatives (for example, if you look under the hood of Nest, you'll see it uses an alternative to JSON.stringify that is 4 to 6 times faster). Other sources of speed are async/await support, dependency injection, HTTP2 and 3 support, and radix-based routing.
Analysis (Fastify v4.27.0)
So, what are the stats? Let's dive in.
Schema-based routing
Fastify comes bundled with its own CLI, which you can install globally. I've tried to jumpstart our project with it, but it's not very useful, as it generates a few hello-world apps which are not what we need. So I started from scratch.
The second thing that I learned is that there are two ways to do routing, Express-style (no schema) and the Fastify-style (yup, schema):
Having a schema comes with benefits: type-safety, validation, and speed. However, since we have not yet added validation and type-guards to the app we built with the other frameworks, we won't add them here. This will probably reduce Fastify's advantage in benchmarks, compared to Express and others. When we'll add validation, we should see this advantage pop back into benchmark results. We'll see.
领英推荐
Basic Todo App
Transpiling the single-file-in-memory-storage-fully-synchronous version was a very straightforward affair, and it looks pretty much like Express:
There are a few differences you'll notice:
// Express:
res.status(500).json({ message: error.message })
// Fastify:
res.code(500).send({ message: error.message })
Everything else is the same.
Modular app variant
I had a harder time setting up the modular version of our app, as Fastify does not actually have a router. There are multiple solutions to this, I went with the one suggested by the documentation: using app.register() to register a specific set of routes.
The tricky bit was exporting them. Previously I just exported a router object, but Fastify wasn't thrilled with that and kept complaining it needs a function. So, I did what I had to do and came up with this ugly bastard:
That probably shows I have zero experience with Fastify, I bet there's a better way to do that. If you happen to be best buddies with the framework, have a look at the repo and suggest a better solution - I'd be happy to improve on that.
So, that being said, we now have two versions of our app, a single file and a modular version, both using in-memory storage, both completely synchronous, so this should allow us to benchmark the core performance of the framework, without any milliseconds lost to I/O.