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
fastifyframework withmysql2andfast-jwtlibrary. - Nodejs using
fastifyframework withfastify-mysqlandfastify-jwtplugin (the plugin usemysql2andfast-jwtunder 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 thereqobject 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
jsonwebtokenfor handling jwt in my express framework, because it is the popular choice. On the other hand, fastify choosefast-jwtas 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?