October 22, 2025
The State of JavaScript Server Frameworks
An overview of modern backend JS frameworks and their features.
By Ross Robinojavascript / router / web server
Here’s an overview of some modern options for backend JavaScript server frameworks. I’ve tried to include frameworks that use code-based (not file based) routing that do not require a bundler like Vite or Webpack. So options such as Next, Nuxt, SvelteKit are not included.
In this article, I’ll outline the basic components of these frameworks and differentiators. I hope this helps people decide which modern backend framework best suits their needs and programming style.
If you have a correction, or see a missing framework you want added to this list, please submit an issue or PR!
This chart shows the total number of npm downloads between 2024-10-20 and 2025-10-20 and the approximate GitHub stars on 2025-10-20. Downloads is not the best popularity metric particularly for the Oak framework as it is Deno first and Deno does not default to npm. Remix’s fetch-router is also a brand new package for Remix v3, separate from Remix v2.
There are two popular matching strategies used in most modern frameworks, regular expression and trie based. In addition, each router has specific implementation details. For example in Elysia, a new trie is created for each HTTP method. Routers also can have multiple matching strategies users can choose from, or automatically switch between based on the application’s added routes.
Regular expression based routers first convert the route’s pattern into a regular expression that a URL can be matched to. Perhaps the most well known package for this conversion is path-to-regexp, there’s also the lighter weight regexparam.
Most framework implementations take each pattern entered by the user, and compile them into their corresponding regular expressions. Then when a request is received, they loop over the compiled regular expressions and test the request’s URL against each one until a match is found.
Looping over these expressions is generally very fast up front, but performance can degrade as you add more routes. More patterns will be iterated over before hitting the match as users add more into routes to their application.
Hono’s RegExpRouter addresses this shortcoming by compiling all of the routes into a single massive regular expression, this is a great differentiator for Hono’s router.
Regular expression routers are great for fast startup performance and dynamically routing capabilities.
Trie (pronounced try) based matchers construct a tree of nodes containing the segments of the route pattern. For example, the pattern /users/* could be broken into four nodes: /, users, /, and *. A popular trie based routing package is find-my-way that is used within Fastify.
For each request, the router takes the URL and breaks it into segments and traverses the tree for the match. The links between nodes are contained in a map—so each lookup is constant. For example, /users/rossrobino could be broken into /, users, /, and rossrobino. In this case, each segment would match the corresponding node since * matches anything. But /nope would not be found, / would match the first node, but nope would not be contained in the map.
Commonly, the radix trie is used for web servers, it further optimizes the data structure to only create new nodes when required. For example, /dashboard/home and /dashboard/insights might be added. Instead of splitting the pattern into segments, only three nodes need to be created to account for these patterns: /dashboard/, home, and insights, with the latter nodes holding the store for each match.
Trie data structures are ideal for larger applications since the lookup time does not increase as you add more patterns. It’s also a more flexible approach since the matching is done in JavaScript code instead of a regular expression, so more pattern types are often supported in trie routers. Remix’s new fetch-router supports matching the entire URL!
The disadvantages of using a trie structure are startup performance and the ability to dynamically add routes. It takes time to construct the trie based on all the user’s route patterns, and the trie must be modified if a new pattern is dynamically added after the initialization since a new node might break up existing ones.
Middleware and handler are common terms used for the code that users write to handle requests within the framework. Middleware can be composed together in various ways including in a chain, using a composition function, or using hooks.
The simplest form of middleware is a linear chain. Popularized by connect (used in Express), it allows users to create a stack of middleware and call the next one in the stack.
This method works well, it’s easy to understand what code is running when during each request. The primary limitation of this API is that users cannot access the response from middleware after the next function has ran since each middleware is synchronous. Instead, events like finish can be listened for.
Most of the modern routers listed above have middleware based on the @koa/compose API. Composing middleware with async/await marked the third major iteration of middleware design by the Express/Koa team, succeeding connect and generator based middleware previously used in Koa. This was made possible with the async and await language features added in ES2015.
This flexible design allows you to easily compose middleware together, and coordinate the flow of each function without having to listen for events and use callbacks.
Another alternative to coordinate what actions are taken on each request is event based middleware or hooks. Elysia is a great example of using lifecycle events instead of composing middleware. It has events like onRequest, beforeHandle, and afterResponse that users can hook into and execute code at the right time within the lifecycle.
Here’s an example of Elysia’s beforeHandle and afterHandle events:
When it comes to servers, NodeJS is the most popular and considered the standard JavaScript server runtime to use. Because of this, other newer server runtimes like Bun, Deno, and Cloudflare Workers try to be compatible with most of the Node built-in server side APIs like node:http.…