For the last 10 years, Express.js has been the most popular web framework for node.js. Everyone who has worked with it knows that complex applications on Express.js can be difficult to structure. But, as they say, habit is second nature. It can be hard to give up Express.js. Like, for example, it’s hard to quit smoking. It seems that we need this endless chain of middleware, and if we take away the ability to create them for any reason, the project will stop.

It is encouraging that now, finally, there is a worthy contender for the place of the main web framework of all and everything – I mean not Fastify.js, but, of course, Nest.js. Although by the quantitative indicators of popularity, it is very, very far away from Express.js.

Express.js still runs in more than 2/3 of web applications for node.js. Moreover, 2/3 of the most popular web frameworks for node.js use Express.js approaches. (It would be more accurate to say the approaches of the Connect.js library, on which Express.js was based until version 4.)

The function that handles the root does not return a value. Instead, one of the methods of the response (res) object must be called. If this method is not called explicitly, even after returning from the function, the client and server will remain in a state of waiting for the server’s response until they each timeout. This is only “direct loss”, but there is also “lost profit”. The fact that this function does not return a value makes it impossible to simply implement the functionality you need, such as validation or logging of responses returned to the client.

In Express.js, built-in error handling is always synchronous. However, it’s rare for a Root to do without calling asynchronous operations. Since Express.js was created in the pre-Promise era, a standard synchronous error handler for asynchronous errors will not work.

The complexity of asynchronous service initialization. For example, an application works with a database and accesses the database as a service by storing a reference in a variable. Root initialization in Express.js is always synchronous. This means that when the first client requests start coming to the roots, the asynchronous initialization of the service will most likely not have time to work yet, so you will have to “drag” asynchronous code into the roots to get a reference to this service. All this, of course, is realizable.

When you develop your part of the application, you can be sure that 10-20 middleware have already been worked on before your code, which hang all sorts of properties on the req object and may even modify the original request, just as you can be sure that as much if not more middleware can be added after you develop your part of the application.
Historical attempts to overcome the shortcomings of Express.js

Fastify.js supports both the style of server response generation familiar to Express.js developers and the more forward-looking style of function return value, while still allowing flexibility in manipulating other response parameters.