Migrating from Express to Fastify, Part 1

ByPaul Hendry

Published Fri Dec 01 2023

Express.js has for years been the dominant lightweight Web framework for Node.js, but over time its development has stalled, with its latest major version (5.0) still in pre-release nearly eight years after its first alpha release. There's a lot to be said for this sort of stability in a foundational dependency for a project, but it's worth assessing whether the added features of competing frameworks are worth making a switch. In this article we'll be looking at Fastify in particular, to understand what it has to offer compared to Express and how difficult it is to migrate an existing Express project.

Why Fastify?

Like Express, Fastify is lightweight and unopinionated, it's very popular (which bodes well for the longevity of the project), it has good documentation, and it provides comparable features (routing, templating, middleware, etc).

It has considerably more to offer, however:

A committed long-term support schedule

The LTS documentation makes it easy to see the end-of-life date and supported Node versions of a given release. For a foundational dependency like a Web framework, upgrades have the potential to be time-consuming and error-prone, so being able to do so on a predictable schedule is key.

Quality major version upgrade guides

The project has a history of providing comprehensive migration guides, which provides a lot of reassurance that future upgrades can be done smoothly, systematically and without introducing bugs.

Built-in async/await support

While it's a small feature, Fastify's route handlers support async/await syntax by default, catching errors and turning them into HTTP 500 responses. In Express, async errors are unhandled unless handled manually with a try/catch or with an added dependency like express-async-handler.

Built-in request validation

Something that's often missing from Express projects is comprehensive request validation, and this is a source of bugs and security vulnerabilities. Fastify has a robust validation system based on the JSON Schema specification, making validation as easy as adding a schema key to a route's options.

Large ecosystem of "core" plugins

Fastify's plugin ecosystem is comparable to Express, but one thing that sets it apart is that many of these plugins are "core" plugins maintained by the Fastify team themselves. A project's Achilles' heel can be a dependency on some community third-party library whose author has dropped off the radar, and yet this risk is not always apparent when developers are choosing to take on a new dependency. Having a large list of core plugins to choose from means having a lot of additional functionality at your disposal without increasing your project's dependence on third parties.

Built-in testing capabilities

Express does not provide any functionality intended for testing, which means projects tend to either

  1. start up a full Webserver and use a tool like Newman to test via real API requests,
  2. expose route handlers and test them independently of the routing, or
  3. skip integration tests entirely.

Integration testing against a running Webserver is slow, and it's brittle given how limited the options are for setting up test data or mocking external services. Testing route handlers is better, but it leaves a gap in test coverage (the untested routing/middleware/validation).

Fastify, on the other hand, provides testing utilities which allow for mock requests to be processed using a .inject() method, without spinning up a Webserver. This means tests are fast, and they allow for arbitrary setup/teardown logic (like opening database connections and loading test data), while still being test framework independent.

TypeScript compatibility

There are TypeScript typings for Express, and Fastify is similarly a JS project with added TypeScript typings (as opposed to being designed from the ground up to leverage a type system).

However, Fastify also provides official documentation for usage in TypeScript, which in particular shows its really great capability (via an added library) of building TypeScript types out of request validation schemas. What this means is that when using TypeScript, the request.params, request.query and request.body fields will have types matching their validated content, ensuring that the validation code and the application code are in sync.

...and more

  • Built-in JSON support such that route handlers don't need to encode/decode JSON manually, which is a nice convenience
  • Built-in logging via pino
  • Speed, with Fastify performancing much better than Express in benchmarks

There are many other quality JavaScript Web frameworks to consider, but without going into a full comparison, I don't think they fill the role of Express quite so well as Fastify does:

  • NestJS is great in its own right as a heavyweight, opinionated framework (which can optionally be based on Fastify), but it's not a good fit for small projects or ones which need a lot of flexibility.
  • Koa is great for being very similar to Express while still adding some welcome features, but it doesn't have nearly the feature set of Fastify.
  • AdonisJS is pretty comparable to Fastify and it's TypeScript-first, but it doesn't have quite the same plugin ecosystem.

Pros and Cons of Migrating from Express to Fastify

For a greenfield Node.js project, I would readily choose Fastify over Express; it just gives developers access to so many added features, all in an opt-in fashion so they don't get in the way, in a well-maintained project that feels like it will still be around in five years' time.

Whether or not to migrate an existing Express project to Fastify, however, is a more difficult question.

Such a migration doesn't need to happen in a single step; there's a @fastify/express plugin which allows an entire Express router to be loaded inside of a Fastify application, allowing both to exist simultaneously for a time. The plugin documentation advises against using it as a long-term solution, so it is intended to be part of a full migration and not for maintaining a set of legacy Express routes long-term. Still, Fastify is different enough from Express that a migration is time-consuming, and differences in its request and response objects means that application code in the route handlers needs to be touched as well.

While Fastify provides a ton of added functionality, almost all that functionality can be tacked on to Express via third-party libraries, so there's little reason why a project would flat-out need to migrate. Express gets the job done; unless the project has a long life ahead, good test coverage to cover a migration, and tens of hours to spare to implement it, it's probably best to stick with Express.

...That said, in order to come to that conlusion I needed to see just how easy (or hard) it was to migrate a project, so that's just what I did. In Part 2 we'll look at the process of converting an Express implementation of the RealWorld example app, to get an idea of what it looks like to migrate a non-trivial application to Fastify.

Previous

Software Dependency Management: Best Practices
Leveraging third-party libraries and frameworks is essential in most modern software projects, and the projects we build at Art+Logic are no exception. The pressure on developers to rapidly deliver features is high, and there are so many commonalities in the details of each project (particularly in Web development) that a lot of development time can be saved by using well-designed libraries that handle the details.