Express was once the most popular framework for developing web applications using Node.js. However, the framework has decreased active development in recent years, resulting in a lack of support for modern JavaScript features. At the same time, many new frameworks that adopt different approaches to Node.js application development emerge, and Fastify is one of them.
This article will explore why Fastify has become a compelling alternative to Node.js web application development. We will learn how to avoid rewriting existing Express applications from scratch and instead move to Fastify step by step. After studying this article, you will be able to confidently migrate your existing Express applications and start taking advantage of the Fastify framework.
The following conditions must be met when reading this article:
All the sample code in this article can be found on GitHub and you can browse, download and experiment with it.
The video version of this article is also available on my website.
fastify-express
Plug-in facilitates the gradual migration from Express to Fastify by allowing the use of Express middleware and routing in the Fastify framework. If you are familiar with building Node.js applications using Express, you may be wondering what the benefits of migrating your existing Express applications to Fastify. Here are some important reasons to consider migration:
Verification and logging out of the box. These features are usually required when building web applications. When using Fastify, there is no need to select and integrate libraries for these tasks, as it provides us with these capabilities. We will learn more about these features later in this article.
Native support for asynchronous code. Fastify natively handles Promise and supports async/await. This means that the route will capture uncatched rejected promises for us. This allows us to write asynchronous code safely. It also allows us to do some concise things, such as automatically sending the return value of the routing handler function as the response body:
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
Automatically parse and serialize JSON. We don't need to configure Fastify to parse the JSON request body, nor do we need to serialize the object to JSON for response. It will automatically handle all of this for us:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
Developer friendly. With a clear and expressive API, and excellent support for TypeScript, Fastify is designed with developer experience in mind.
Fast speed. We do not want frameworks to be the source of performance bottlenecks in our applications. The good news is that Fastify is built to achieve high performance. The Fastify benchmark shows how it compares to other Node.js web frameworks.
Actively developing. The Fastify framework is under active development. Release improvements and bug/security fixes regularly.
We want to be sure that it still works as expected after the application is migrated to Fastify. One thing that helps us spot errors or identify unexpected changes is API integration testing.
Integration testing tests the components of the application in a different way than unit testing. Unit tests individual components' functionality. Integration testing allows us to verify the behavior of multiple components working together.
If we write API integration tests for Express applications, we want to be able to run the same test after migrating the application to Fastify. When writing integration tests for APIs, the following points need to be considered:
We won't go into detail about how to implement API integration tests in this article, but you should consider writing them before doing framework migrations.
The fastify-express plugin adds full Express compatibility to Fastify. It provides a use() method that we can use to add Express middleware and routes to our Fastify server. This gives us the option to gradually migrate parts of our existing Express application to Fastify.
This is an example of an Express router:
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
We can then use fastify-express to add our existing Express router to the Fastify server instance:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
After a moment, we will explore the details of all these working as we start migrating the application to Fastify.
It is important to note that using the fastify-express plugin is not a long-term solution. If we want to get the full benefits of Fastify, we eventually need to migrate our Express-specific application code. However, the fastify-express plugin provides us with the opportunity to migrate to Fastify in phases.
We will build a sample Express application and then migrate it to using the Fastify framework. Now let's look at its code.
First, let's create a new project:
<code>// src/routes.js const router = express.Router(); router.get("/:user_id", function getUser(request, response, next) { response.json({}); }); export default router; </code>
We will then run this command in the terminal to install the dependencies required by our Express application:
<code>// src/server.js import Fastify from "fastify"; import ExpressPlugin from "fastify-express"; import routes from "./routes.js"; const fastify = Fastify(); await fastify.register(ExpressPlugin); fastify.use("/user", routes); await fastify.listen(3000); </code>
Finally, open package.json and add the following line above the scripts section:
<code>mkdir express-to-fastify-migration cd express-to-fastify-migration npm init -y </code>
This will allow us to load the ES module in our application.
We will create an Express router instance to help us encapsulate our routing and middleware. Routers in Express can be used to help us organize applications into discrete modules. For example, we might have a router for /user routing and another for /address routing. We'll see later how this helps us to phase out Express applications to Fastify.
Let's create a router instance and add some middleware to it:
<code>npm install express cors </code>
In the above code, we configured two Express middleware examples:
These middleware tools will run against any requests made by any routes we define on this router.
Now that we have configured the middleware, we can add the first route to our router:
<code>"type": "module", </code>
In a real application, the above routing handler function will verify the data it receives and then call the database to create a new user record. For this example, we are sending data received as the response body.
Now we will add a route to retrieve users:
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
Like POST routing, the above routing handler usually calls the database to retrieve user data, but for this example, we have hardcoded an object to send in the response body.
Finally, we will export the router object so that we can import it in another module:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
Now we will create an app module:
<code>// src/routes.js const router = express.Router(); router.get("/:user_id", function getUser(request, response, next) { response.json({}); }); export default router; </code>
In this module, we define a function that creates a new Express server instance. We then add the router object to the server instance.
Finally, we will create a server module. This module uses the buildApp() function we defined in the app module to create a new Express server instance. It then starts our Express server by configuring it to listen on port 3000:
<code>// src/server.js import Fastify from "fastify"; import ExpressPlugin from "fastify-express"; import routes from "./routes.js"; const fastify = Fastify(); await fastify.register(ExpressPlugin); fastify.use("/user", routes); await fastify.listen(3000); </code>
We now have a complete runnable Express application that we can run in the terminal:
<code>mkdir express-to-fastify-migration cd express-to-fastify-migration npm init -y </code>
In another terminal, we can use cURL to make a request to the API to confirm whether it is working:
<code>npm install express cors </code>
We should receive a response as follows:
<code>"type": "module", </code>
Now we have a fully-featured Express application that we will migrate to using the Fastify framework.
We need to install three dependencies:
Let's run this command in the terminal to install them:
<code>// src/routes.js import express from "express"; import cors from "cors"; const router = express.Router(); router.use(express.json()); router.use(cors({ origin: true })); </code>
You can view the differences in these code changes on GitHub.
Now that we have the dependencies installed, we need to refactor our app module. We will change it to:
This is what we look like after we make these changes:
<code>// src/routes.js router.post("/", function createUser(request, response, next) { const newUser = request.body; if (!newUser) { return next(new Error("Error creating user")); } response.status(201).json(newUser); }); </code>
You can view the differences in these code changes on GitHub.
You will notice in the above code that when we create a Fastify server instance, we are passing the logger option. This enables Fastify's built-in logging feature. We'll learn more about this later.
Now we need to change our server module to work with the Fastify server instance:
<code>// src/routes.js router.get("/:user_id", function getUser(request, response, next) { const user = { id: request.params.user_id, first_name: "Bobinsky", last_name: "Oso", }; response.json(user); }); </code>
You can view the differences in these code changes on GitHub.
Since Fastify natively supports Promise, in the above code we can use await and then use Fastify's built-in logging feature to capture and log any errors.
Our application now uses Fastify to route requests and send responses. It's full-featured, but our routing is still using Express. In order to fully migrate to Express, we need to migrate our routes to using Fastify as well.
Routing in Express applications is encapsulated in Express routers. We refactored this router to a Fastify plugin. Plug-in is a feature of Fastify that allows us to encapsulate routing and any related features.
We will start by removing some Express-specific rows to refactor our routing module (src/routes.js):
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
Then we need to change the default module export to an asynchronous function that accepts the Fastify server instance. This is the foundation of the Fastify plugin. The rest of the code in our routing module will be moved into this plugin function:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
In order for our middleware and routing to work with Fastify, we need to change:
After making all these changes, our routing module is now a Fastify plugin with Fastify routing:
<code>// src/routes.js const router = express.Router(); router.get("/:user_id", function getUser(request, response, next) { response.json({}); }); export default router; </code>
Now we need to change our app module (src/app.js) to use the plugin we exported from the routing module. This means replacing the fastify.use() call with a call to fastify.register():
<code>// src/server.js import Fastify from "fastify"; import ExpressPlugin from "fastify-express"; import routes from "./routes.js"; const fastify = Fastify(); await fastify.register(ExpressPlugin); fastify.use("/user", routes); await fastify.listen(3000); </code>
You can view the differences in these code changes on GitHub.
Our example Express application has only one router, so we can migrate all routes in the application to using Fastify at one time. However, if we had a larger Express application with multiple routers, we could migrate each router to Fastify step by step at a time.
Our application is in good condition and we have almost completely migrated it from Express to Fastify. One more thing to be migrated: our use of the cors Express middleware package. We installed the fastify-cors plugin before, and now we need to add it in our application to replace the cors middleware.
In our routing module (src/routes.js), we need to replace the import of the cors middleware:
<code>mkdir express-to-fastify-migration cd express-to-fastify-migration npm init -y </code>
Then we need to replace the call to fastify.use() with the call to fastify.register():
<code>npm install express cors </code>
Note that when we register the plugin with Fastify, we need to pass the plugin function and option objects as separate parameters.
Since we no longer use the use() function provided by the fastify-express plugin, we can remove it completely from our application. To do this, let's delete the following lines from our app module (src/app.js):
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
You can view the differences in these code changes on GitHub.
The migration of our application from Express to Fastify has been completed! We can now remove Express-related dependencies by running this command in the terminal:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
You can view the differences in these code changes on GitHub.
Now that we have completely migrated the application to Fastify, it is a good time to check if everything is still working as expected. Let's run the same command we ran before when the application was using Express.
First, we will run the application in the terminal:
<code>// src/routes.js const router = express.Router(); router.get("/:user_id", function getUser(request, response, next) { response.json({}); }); export default router; </code>
Then, in another terminal, we will use cURL to make a request to the API to confirm that it works as expected:
<code>// src/server.js import Fastify from "fastify"; import ExpressPlugin from "fastify-express"; import routes from "./routes.js"; const fastify = Fastify(); await fastify.register(ExpressPlugin); fastify.use("/user", routes); await fastify.listen(3000); </code>
We should receive a response as follows:
<code>mkdir express-to-fastify-migration cd express-to-fastify-migration npm init -y </code>
Our example Express application uses only some middleware functions, but our real-world Express application may use more. As we can see, the fastify-express plugin allows us to continue using Express middleware (if needed). This allows us to delay rewriting our own custom Express middleware to Fastify plugin. But how do we replace third-party Express middleware?
Luckily, Fastify provides a healthy ecosystem of plugins. Here are some popular Express middleware packages that we can replace with the Fastify plugin:
Some Fastify plugins are direct ports or wrappers for their Express counterparts. This means we don't usually need to change the configuration options passed to the Fastify plugin.
You can find a complete list of plugins on the Fastify ecosystem page.
Now that we have started getting familiar with Fastify by migrating Express applications, it is time to start looking at other Fastify features that we can benefit from.
Fastify provides request verification function. It uses Ajv (another JSON schema validator) in the background, which allows us to define validation rules using JSON Schema.
This is an example of using JSON mode to verify the request body on a POST route:
<code>npm install express cors </code>
Verification errors are automatically formatted and sent as JSON response:
<code>"type": "module", </code>
Learn more in the Fastify verification and serialization documentation.
Log logging in Node.js applications can negatively affect performance in production environments. This is because serializing and transferring log data elsewhere (for example, to Elasticsearch) involves many steps. High optimization in this application is very important.
Log logging is fully integrated into Fastify, which means we don't need to spend time selecting and integrating loggers. Fastify uses fast and flexible logger: pino. It generates logs in JSON format:
<code> app.get("/user/:id", async (request) => await getUser(request.params.id)); </code>
When we create a Fastify server instance, we can enable logging and customize the options passed to pino. Fastify will then automatically output the log message as shown above. The logger instance is available on a Fastify server instance (for example, fastify.log.info("...")) and all request objects (for example, request.log.info("...")).
Learn more in the Fastify logging documentation.
Fastify provides a setErrorHandler() method that allows us to explicitly specify the error handling function. This is different from Express, where an error handling middleware can only be distinguished by the parameters it accepts (err, req, res, next) and must be added in a specific order.
For complete flexibility, we can specify different Fastify error handlers in different plugins. Learn more in the Fastify error documentation.
Decorators are a powerful feature in Fastify that allow us to customize core Fastify objects—such as our Fastify server instance—as well as request and reply objects. Here is an example of a basic decorator:
<code> app.get("/user/:id", async (request, reply) => { const name = request.body.name; reply.send({ user: { name } }); }); </code>
The decorator allows us to use content like database connections or view engines throughout the Fastify application. Learn more in the Fastify Decorator documentation.
In this article, we learned how to migrate existing Node.js applications from Express to Fastify. We've already learned how the fastify-express plugin can help us gradually migrate existing applications. This allows us to start benefiting from the features provided by Fastify, even if parts of our application are still using Express.
The following are some resources that you might find useful when migrating from Express to Fastify:
Express and Fastify are both Node.js web frameworks, but they have some key differences. Express is a minimalist web application framework that provides a simple interface to build web applications and APIs. It has been around for a long time, with a huge community and rich middleware. Fastify, on the other hand, is a newer framework focused on providing the best developer experience with minimal overhead and a powerful plug-in architecture. It is designed very fast, hence the name, and benchmarks show that it can handle more requests per second than Express.
Migrating from Express to Fastify includes several steps. First, you need to install Fastify and replace the Express instance in the application with a Fastify instance. You then need to replace Express-specific middleware with Fastify plugin or custom code. You also need to update your route to use Fastify's routing system. Finally, you need to update your error handling code to use Fastify's error handling mechanism.
Fastify has its own middleware system, but it also supports Express-style middleware through the "middie" plugin. However, using Express middleware in Fastify can affect performance, so it is recommended to use Fastify plugins or custom code where possible.
Fastify has a built-in error handling mechanism that you can use to handle errors in your application. You can define custom error handlers for a specific route or for the entire application. Fastify also supports the async/await syntax, which makes error handling more direct than Express.
Hook is a powerful feature in Fastify that allows you to run custom code at different stages of the request/response lifecycle. You can use hooks to modify requests or responses, perform authentication, record requests, and more. Fastify supports multiple hooks, including "onRequest", "preHandler", "onSend", and "onResponse".
Plugin is a key feature of Fastify that allows you to extend the functionality of your application. You can use plugins to add new features, integrate with other services, or encapsulate application logic. Fastify has a rich plugin ecosystem, and you can create your own plugins as well.
Fastify has a powerful routing system that supports parameters, query strings, wildcards, etc. You can define a route using the "route" method, which accepts an option object that specifies the route's methods, URLs, handlers, and other options.
In Fastify, you can send a response using the "reply" object passed to the routing handler. There are several ways to send responses to the "reply" object, including "send", "code", "header", and "type". Fastify also automatically serializes JSON responses for performance.
Fastify supports request and response verification using JSON Schema. You can define patterns for routes, and Fastify will automatically verify incoming requests and outgoing responses based on these patterns. This helps detect errors early and improve application reliability.
Fastify is designed to be fast and efficient. It uses a lightweight architecture, supports HTTP/2 and HTTP/3, and has a powerful plug-in system that minimizes overhead. Fastify also automatically serializes JSON responses and supports request and response verification, which helps improve performance.
The above is the detailed content of How to Migrate Your App from Express to Fastify. For more information, please follow other related articles on the PHP Chinese website!