Welcome to the final part of our Express to Fastify series. In the previous installments, we explored the unique features and advantages of Fastify over Express. Now, you'll put what you've learned so far into practice by migrating an existing Express application to Fastify.
You'll avoid rewriting an entire application from scratch by gradually transitioning to Fastify. After reading this article, you'll have the skills to confidently migrate your Express applications and enjoy the benefits of the Fastify framework.
Let's get started!
Prerequisites
Before proceeding with the article, ensure that you have a recent version of Node.js and npm installed on your computer. You also need to install MongoDB and ensure that it is running on port 27017.
Setting Up the Demo Project
To illustrate the process of migrating from Express to Fastify, we have prepared a demo application. This application utilizes Express, Mongoose, and Pug to create a URL Shortener app as follows:
After you submit a URL through the form, the application generates a shortened URL that can be copied and used elsewhere.
Go ahead and clone the repository to your computer using the following command:
Afterward, cd
into the project directory and install the application
dependencies with npm
:
Once the process completes, rename the .env.sample
file to .env
and edit it
as follows:
The application is configured to use AppSignal for log management and error tracking, so you can easily monitor and troubleshoot any issues that may occur. AppSignal provides real-time alerts and detailed error reports, allowing you to quickly identify and fix the root cause of errors before they become major problems.
If you want to see this in action, sign up for an AppSignal account (you can do a 30-day free trial, no credit card required), then create a new application and copy the Push API Key under Push & Deploy in the APP SETTINGS page.
Once you've done that, replace the
<your_appsignal_push_api_key>
placeholder in the .env
file. Note that this
step is not required to complete the migration to Fastify.
You can then start the development server by executing the command below:
The following output indicates that the server started successfully:
You will also observe that your application's logs will start to appear in the logging dashboard in AppSignal:
From here, you can perform all kinds of actions to monitor and troubleshoot your application.
Now head over to http://localhost:3000 to see the application in action!
Examining the Project Structure
The URL Shortener application is composed of several files and directories as shown below:
Here's a brief explanation of the most important ones:
src/index.js
: The entry point of the application.src/app.js
: Where the Express app is configured.src/appsignal.js
: Initializes AppSignal integration.src/public/
: Contains static CSS and JavaScript files.src/views/
: Contains the Pug templates for the app.src/routes/routes.js
: Configures the application routes.src/config/
: Contains configuration for the Winston logger and environment variables.src/models/
: Has the schema definitions for Mongoose.src/controllers/
: Includes the business logic for the application routes.src/middleware/
: Contains some useful middleware functions.
We'll examine most of these files' content as we migrate our application to Fastify.
In the next section, we'll install Fastify in the project and create a new Fastify application that embeds the original Express app to kickstart our incremental migration.
Migrating from Express to Fastify
As mentioned in part 2 of this series, using the @fastify/express plugin is the quickest way to get your existing Express application working with Fastify. The plugin adds full Express compatibility to Fastify so that you can easily use any Express middleware — or even an entire Express application — with your Fastify instance, and it will just work with no changes required.
Go ahead and install the fastify
and @fastify/express
packages into your
project:
Once they are both installed, import them at the start of your app.js
file as
follows:
Afterward, create a new Fastify application instance near the bottom of the file
and register the fastifyExpress
plugin. Also, register the expressApp
on the Fastify instance, then change the file export from expressApp
to
fastifyApp
:
At this point, app.js
exposes a Fastify application that embeds a
fully-featured Express application with its own routes, middleware, and plugins.
Before testing your changes, you also need to modify your index.js
file as
shown below. You only need to change the part of the code that listens for
connections on the configured port:
With these changes in place, your application should continue working as before.
Fastify is now being used to start the server, but the entire service functionality remains in the Express application. In the next section, we'll start migrating our routes to Fastify.
Migrating Express Routes to Fastify
The Express router currently does our application routing, but we'll start
migrating the routes to Fastify in this section. Open your routes/routes.js
file and add the following code:
The fastifyRoutes
function represents a new plugin context that will contain
our application routes. To migrate an Express route to Fastify, you need to:
- Remove the route from the
express.Router
instance. - Register the route on the
fastify
instance in the plugin function. - Update the route itself so that it conforms to the Fastify API.
Let's follow the above steps for the /
route and see how far we get.
First,
delete the following line of code in the routes.js
file:
Next, register the /
route in fastifyRoutes
as follows:
Afterward, open the controllers/root.controller.js
and examine its content:
This route renders the src/views/home.pug
file and returns the resulting HTML
string to the client. In the app.js
file, Express is configured to work with
Pug templates through the following lines of code:
Since we've migrated our only application route that renders templates, the
above lines are no longer needed and can be safely deleted. But you now need to
configure Fastify to render the Pug template through the
@fastify/view plugin. It decorates
the Reply
interface with a method that we'll then use to render the template.
Go ahead and install the plugin first:
Then import it into the app.js
file shown below. You also need to import
pug
as well:
Below the fastifyApp
declaration, add the following lines to register the
fastifyView
plugin:
At this point, your Fastify application is now ready to work with Pug templates.
You only need to register the fastifyRoutes
plugin in your fastifyApp
instance as shown below:
You can also update the rootController.render
method as follows so that it
conforms with Fastify's naming convention:
With the above changes in place, the application should
continue working as before. The /
route is handled by Fastify, while
the other two routes remain routed through Express. In this manner, you can eventually
migrate all your Express routes to Fastify.
Migrating the Rest of the Express Routes
In this section, we'll migrate the remaining two Express routes to Fastify.
Start by updating the routes.js
file as follows:
Since Fastify supports schema validation with
Ajv, the validate
module is no longer
required on the /shorten
route, and we can specify the JSON schema directly on
the route. The controllers for both routes will largely remain the same, except
that the res
parameter is renamed to reply
as before:
Finally, head back to the app.js
file and fix the routes
import since
fastifyRoutes
is now the default export:
Now all our Express routes have been migrated over to Fastify. You can safely remove these lines:
Then replace the reference to fastifyRoutes
with routes
as shown below:
At this stage, you can test the application once again to confirm that it works just fine as before.
We've now successfully moved all our routes to Fastify without much trouble. In the next section, we will shift our handling of static files from Express to Fastify.
Serving Static Files with Fastify
At the moment, the static JavaScript and CSS files (located in /src/public
)
are being handled by the express.static
middleware, according to this line in
app.js
:
We can replace this middleware with the @fastify/static plugin, which you can install through the command below:
Once installed, import it into the file and register it on the fastifyApp
instance:
Then remove the reference to express.static
, and everything
should keep working the same as before (except that Fastify now handles
serving static files).
Replacing Express Middleware with Fastify Plugins
We've almost completed the migration to Fastify, but we still have some Express
bits to replace before removing the @fastify/express
compatibility layer. In
this section, we'll replace the following middleware with the corresponding
Fastify alternatives:
- helmet -> @fastify/helmet.
- compression -> @fastify/compress.
- cors -> @fastify/cors.
Install the plugins through the command below:
In your app.js
file, delete the following lines:
Then add the following lines in the appropriate location:
You can confirm that these work by making a request to the application root and inspecting the response headers through your browser DevTools.
At this point, we've replaced most of the Express middleware with Fastify plugins without affecting application functionality. What if you're unable to find a Fastify alternative to your Express middleware? You can use the techniques discussed in part 2 of this series to continue using the middleware even when the rest of the application has been migrated to Fastify.
In the next section, we'll turn our attention to the error handling middleware.
Error Handling in Fastify
In Express, error handling is done through middleware functions added
to an application's middleware stack. These functions have an additional err
parameter denoting the error being handled, and they are called when an error
occurs anywhere in the middleware stack or is thrown in a route
handler. The error handler for our Express app is located in
middleware/error.js
:
We also install and import the express-async-errors package so that async errors are caught by this middleware instead of crashing the program.
Finally, we use AppSignal's expressErrorHandler
middleware to automatically track unexpected errors:
Let's begin migrating our error handling to Fastify by removing the above lines
in the app.js
file. By default, Fastify automatically catches errors from both
synchronous and asynchronous routes, so no plugin replacement for
express-async-errors
is needed. You can customize the default error-handling
behavior in Fastify by providing a custom error handler through the
setErrorHandler()
method on a Fastify
instance. The provided handler needs
to have the following signature:
Let's modify our existing Express error handler middleware so that it can handle Fastify errors instead:
The code remains mostly the same, except that the function signature has been
modified, and schema validation errors are now detected differently. Also, the
sendError()
method replaces expressErrorHandler
as the way to track
unexpected application errors in AppSignal.
You may now set this function as the error handler for your routes using the
setErrorHandler()
method:
At this point, any error that occurs in your routes will be caught and processed
by the custom errorHandler
you provided. They'll also be tracked in AppSignal
so that you can be notified quickly of any issues that occur in your Node.js
application.
You can learn more about error handling in Fastify's documentation and on AppSignal's error tracking page.
Replacing Winston Logging with Pino
Our Express application uses Winston and Morgan for logging HTTP requests.
Fastify comes with Pino by default, but logging must be enabled for it to work:
When Fastify logging is enabled, you'll notice that Pino logs appear alongside Winston logs when requests are sent to the server:
At this point, you can remove the requestLogger
from the Express application
so that only Fastify logs remain. Go ahead and delete the following lines of
code from your app.js
file:
At this stage, we've completely migrated the Express application over to Fastify
so you can remove the express
and @fastify/express
packages as well:
Head to your
config/logger.js
file, and replace winston
with pino
as shown below:
Afterward, replace Fastify's default logger instance with the custom Pino logger:
At this point, all application logs are now produced through the same Pino instance, so the log formatting should be consistent:
Note that Fastify makes its logger available on the Server
and Request
types
so you can access it directly without importing from config/logger.js
. For
example:
Learn more about logging in Fastify and how to customize the Pino logger.
Testing Your Application
We've successfully completed our migration from Express to Fastify! You may now remove all the Express-related packages from your project through the command below:
You can also remove some of the files that are no longer needed:
Afterward, ensure you thoroughly test your application to confirm that it's working as expected, ideally through automated tests that you've previously set up. However, since our application is small and we don't have any automated tests, manual testing will suffice:
Wrapping Up
Thank you for following along with this series on migrating from Express to Fastify. We've covered a lot of ground, and I hope you found it helpful in deciding whether to switch to Fastify and how to approach the migration process.
Fastify offers many additional features that we couldn't cover in this series, so check out the Fastify official documentation to learn more. Also, you can find all the code samples we used in this GitHub repository.
I hope this series has equipped you with the knowledge and skills to start building performant and scalable web applications with Fastify. If you have any questions or feedback, please don't hesitate to reach out.
Until next time, happy coding!
P.S. If you liked this post, subscribe to our JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.
P.P.S. If you need an APM for your Node.js app, go and check out the AppSignal APM for Node.js.