Fastify is known for its performance and the plugin ecosystem to extend its functionality. You can see the list of fastify plugin ecosystems in Fastify Ecosystem. But why should we need a plugin anyway? Is it any different from just using a standard library from npm? How about the performance when using a plugin? You will find out the answer in this article.
If you are confused about fastify, I suggest you to see my previous article first here: Fastify vs Express Performance. This will give you an idea what is fastify and why we explore more about fastify here.
Test Scenario
To demonstrate and compare the implementation of the plugin and non-plugin, we have to do a test just like before. All the application is installed on my notebook with this spec:
- CPU: Intel Core i3-6006U, 2 cores, 4 threads.
- RAM: 16 GB
- OS: Ubuntu 18.04 WSL
- Node Version: 14.17.2
- Express Version: 4.17.2
- Fastify Version: 3.25.3
- ApacheBench Version: 2.3
The application we used for the load testing is ab
(ApacheBench). The ab
will hit the fastify server as much as 10k
times with 100
concurrency with this command:
ab -n 10000 -c 100 -H "Authorization: Bearer {token}" http://localhost:3000/getproducts
Notice that we use the Authorization
header because one of the plugins that we will test is related to jwt
.
The webserver will first verify the bearer token from the request. If the token is verified it will return the list of products (20 rows) that it gets from the database query. We use the MySQL database in this test. You can see the test topology below:
ab ------> Fastify ------> MySQL
In this test, We will compare:
- NodeJS using
fastify
framework withmysql2
andfast-jwt
library. - Nodejs using
fastify
framework withfastify-mysql
andfastify-jwt
plugin (the plugin usemysql2
andfast-jwt
under the hood).
Fastify with No Plugin Code
This is the app.js
using fastify
with no plugin implementation:
const fastify = require("fastify");
const mysql = require("mysql2/promise");
const { createVerifier } = require("fast-jwt");
const verify = createVerifier({
key: async () => process.env.JWT_SECRET,
});
const app = fastify();
const pool = mysql.createPool({
connectionLimit: 100,
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
});
app.get("/getproduct", async (req, res) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
res.status(403).send({ message: "please provide token" });
return;
}
const decoded = await verify(token);
if (!decoded) {
res.status(403).send({ message: "token not valid" });
return;
}
const [results] = await pool.query("SELECT * FROM product");
res.send({ results });
});
app.listen(3000);
Notice that we use mysql2
and fast-jwt
in a normal way, not integrated with the fastify framework.
Fastify with Plugin Code
This is the app.js
using fastify
with plugin implementation:
const fastify = require("fastify");
const app = fastify();
app.register(require("fastify-mysql"), {
connectionLimit: 100,
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
promise: true,
});
app.register(require("fastify-jwt"), {
secret: process.env.JWT_SECRET,
});
app.get("/getproduct", async (req, res) => {
const decoded = await req.jwtVerify();
if (!decoded) {
res.status(403).send({ message: "token not valid" });
return;
}
const [results] = await app.mysql.query("SELECT * FROM product");
res.send({ results });
});
app.listen(3000);
Compared to no-plugin source code, this is way cleaner. The mysql2
and fast-jwt
is still used, but it’s already integrated with the fastify framework using fastify-mysql
and fastify-mysql
.
The Test Result
In this segment, we will show you the result of the test (10k requests, 100 concurrent). The test scenario is done 10 times, this is the test results:
Time Taken For Tests
On average, fastify with plugin implementation finish the test in 6.403 second while fastify with non-plugin implementation finishes the test in 6.387 second. As we can see that the plugin will make the process slower by 16 ms, but it is very little that we can almost ignore the difference.
Requests Per Second
We also can see that using the plugin is handling the requests slightly lower here. On average, the fastify with the plugin handles 1566.56 requests per second, while the fastify with non-plugin handles 1571.33 requests per second.
Conclusion
There are some key points when I was doing this experiment:
- We can see from this test that although the average performance is slightly lower, sometimes the plugin implementation can still beat the non-plugin implementation on a single test. So, I conclude that the performance difference can be ignored.
- Some plugins have seamless integration with fastify, for example, the
fastify-jwt
. The syntax is so beautiful that you don’t have to extract the bearer token from the request header like we used to do before. You can directly verify the bearer token from thereq
object with fastify. Look at this code difference between no plugin vs plugin implementation:... const token = req.headers.authorization?.split(" ")[1]; if (!token) { res.status(403).send({ message: "please provide token" }); return; } const decoded = await verify(token); if (!decoded) { res.status(403).send({ message: "token not valid" }); return; } ...
vs
... const decoded = await req.jwtVerify(); if (!decoded) { res.status(403).send({ message: "token not valid" }); return; } ...
- Before using fastify, I’m using
jsonwebtoken
for handling jwt in my express framework, because it is the popular choice. On the other hand, fastify choosefast-jwt
as their plugin instead. Why? Performance! Fastify has already chosen the best performance library for you.
That’s it! Now you know the difference between a plugin and no plugin implementation in fastify. For me, I prefer to use the plugin for the sake of beautiful code because the performance difference can be ignored. How about you?