Frameworks Built on Express and Alternatives
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Express 5.3 (npm express 5.3.0, published 2026-10-09) on Node.js 18 or later, and it states
where Express 4 (latest 4.22.3) behaves differently. The package versions quoted for the other frameworks were read from
the npm registry on 2026-10-29 and move quickly, so treat them as a snapshot. Express is deliberately small: it routes
requests, chains middleware and offers a few helpers. Most teams that outgrow that surface do not abandon Express, they
pick a framework that adds structure on top of it, or they move to a different foundation. This page maps both groups
for the Bookshelf API that grows across this section: what each option adds, what it costs, and how it relates to the
Express application you already know. The examples use ESM and the node: prefix for built-in modules, and contain
placeholders only, never real secrets.
The map: build on Express, or replace it
There are two different relationships with Express, and mixing them up is the usual source of confusion:
-
A framework built on Express keeps an Express
appinside, so every Express middleware,reqandresproperty and error-handling rule in this section still applies underneath. NestJS (through@nestjs/platform-express), LoopBack 4, Sails and FeathersJS belong here. -
An alternative replaces Express with its own request pipeline. Koa, Fastify and Hono belong here. Express middleware does not run on them unchanged, and the mental model of
req,resandnextis different.
NestJS on @nestjs/platform-express
NestJS is a TypeScript framework with modules, dependency injection, decorators, guards, pipes and interceptors. It is
not tied to Express: it talks to an HTTP server through an adapter, and @nestjs/platform-express is the default one. The
adapter (ExpressAdapter) creates an Express application, registers Nest’s routes on it and exposes it, so a Nest
application is an Express application with a framework wrapped around it. At the time of writing
@nestjs/platform-express 12.3.2 depends on Express 5.3, while the 11.x line already moved Nest to Express 5; Nest 12
requires Node.js 20 or later, which is stricter than Express 5 itself.
What Nest adds on top of the Express layer:
-
A module system and constructor-based dependency injection, which replaces the hand-written "services" folder of a plain Express project.
-
Decorators for routing (
@Controller(),@Get(),@Post()) that compile down toapp.get()andapp.post()calls on the Express application. -
Guards, pipes, interceptors and exception filters, which have no Express counterpart and run in a fixed order around the handler. Express middleware still runs first, in registration order.
-
Built-in integration with validation, OpenAPI, configuration, testing, GraphQL and microservice transports.
What stays Express: @Req() and @Res() hand over the real Express Request and Response, app.use() accepts any
Express middleware (Helmet, cors, compression, cookie-parser), and app.set() settings such as trust proxy are
reached through the underlying instance:
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import helmet from 'helmet';
import { AppModule } from './app.module.js';
async function bootstrap(): Promise<void> {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// The generic argument types the Express-only API.
app.set('trust proxy', 1);
app.disable('x-powered-by');
app.use(helmet());
// The raw Express application, for anything Nest does not wrap.
const expressApp = app.getHttpAdapter().getInstance();
expressApp.get('/healthz', (_req, res) => {
res.json({ status: 'ok' });
});
await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();
Choose the Express platform when the project depends on Express middleware or on Express-only packages. Choose the Fastify platform when raw throughput matters more; the trade-offs, including the routing and CORS differences, are in Fastify and HTTP Platforms and the architecture behind the adapter in NestJS Introduction and Architecture. The order in which middleware, guards, pipes and interceptors run is covered in Request Lifecycle, and running Nest without an HTTP server at all in Platform Agnosticism and Standalone Apps.
LoopBack 4
LoopBack 4 is a TypeScript framework for building API-first services from models, repositories, controllers and
datasources, with generated OpenAPI documents. It is a complete rewrite of the earlier LoopBack 3 and is not
backwards compatible with it. @loopback/rest runs on an Express application inside @loopback/express, and at the time
of writing both pin Express 4.22 (^4.22.3), so a LoopBack 4 service does not use the Express 5 behaviour described in
this section. Its current packages require Node.js 22 or later.
The Express layer is reachable in two ways. A server-wide Express middleware is registered through the REST server’s middleware chain, so it takes part in LoopBack’s own sequence of actions (find route, parse parameters, invoke, send). An Express router can also be mounted next to the generated routes, which is the pragmatic way to keep a few legacy Express endpoints in a LoopBack service:
import { RestApplication } from '@loopback/rest';
import express from 'express';
const app = new RestApplication({ rest: { port: Number(process.env.PORT ?? 3000) } });
// A plain Express router mounted beside the generated controllers.
const legacy = express.Router();
legacy.get('/books/ping', (_req, res) => {
res.json({ pong: true });
});
app.mountExpressRouter('/legacy', legacy);
await app.start();
The decision is about style. LoopBack favours an explicit, model-driven architecture and generated clients; NestJS favours modules and decorators with broader community momentum. If the requirement is "documented REST API over existing databases with generated OpenAPI", LoopBack remains a valid choice, but check the release cadence and the Express 4 pin before committing.
Sails
Sails is a full-stack MVC framework on Express and Socket.IO. It generates controllers, actions, policies (its
authorisation middleware), views, and "blueprint" REST routes straight from models, and it ships Waterline, an ORM with
adapters for several databases. A Sails application is configured by files under config/ rather than by code, which is
convenient for a conventional CRUD application and awkward for anything unusual. The current 1.5.x line
(1.5.18) still pins Express 4.22.2, and its manifest declares only node >= 0.10.0, a range that says nothing about
which modern Node.js releases are tested, so treat it as an Express 4 framework and check it on your Node.js line.
Express middleware fits into Sails through config/http.js, where middleware.order lists the layers in sequence and
custom entries are plain Express functions:
// config/http.js
const compression = require('compression');
module.exports.http = {
middleware: {
order: ['cookieParser', 'session', 'compress', 'bodyParser', 'router', 'www', 'favicon'],
compress: compression(),
},
};
Source: Sails documentation, middleware.
Sails is a reasonable fit for server-rendered applications with real-time features and a small team that wants conventions. For a new JSON API without views or Waterline, it brings more than is needed and is usually a worse choice than plain Express or NestJS.
FeathersJS
FeathersJS 5 is a service-oriented framework: the application is a set of services with the same methods (find,
get, create, update, patch, remove), and Feathers exposes each one over REST and, optionally, WebSockets
through Socket.IO, with hooks for validation, authentication and logging around them. It is transport-agnostic by design,
and @feathersjs/express and @feathersjs/koa are two interchangeable transports. The Express package wraps an Express
application (currently Express 4.21) so express(feathers()) gives Feathers' service API plus ordinary
Express routes and middleware:
import { feathers } from '@feathersjs/feathers';
import express, { rest, json, errorHandler } from '@feathersjs/express';
const app = express(feathers());
app.use(json());
app.configure(rest());
app.use('books', {
async find() {
return [{ id: 1, title: 'Placeholder title' }];
},
});
app.use(errorHandler());
await app.listen(3030);
Source: Feathers documentation, Express.
The service code does not change when the transport is swapped for @feathersjs/koa, which is Feathers' main value. Choose it when the same business logic must be served over REST and real time and the
service-per-resource model suits the domain.
Historical frameworks: LoopBack 2/3, KeystoneJS 0.x and MEAN.JS
Older tutorials and books build on frameworks that have since been superseded. They matter only when maintaining legacy code:
| Framework | What it was | Today |
|---|---|---|
LoopBack 2 and 3 |
Express 4 with a JSON model definition, the |
Replaced by LoopBack 4, which is a different architecture. Migration is a rewrite of models and controllers, not an upgrade. |
KeystoneJS 0.x and 4 |
A CMS and application framework on Express and MongoDB with Jade/Pug templates and an admin UI. |
The project moved to Keystone 5 and 6, a separate GraphQL and Prisma based CMS. The 0.x/4 code is unmaintained. |
MEAN.JS |
A MongoDB, Express, AngularJS and Node.js boilerplate with Yeoman generators, Bower and Grunt. |
Unmaintained; AngularJS reached end of life and Bower is deprecated. Modern stacks use Angular, React or Vue with a separate Express API. |
None of these runs on Express 5, and none of their generators should be used for a new project.
Koa
Koa, from the Express authors, drops the bundled router and helpers and keeps a small core: an async-first middleware
chain where each middleware calls await next() and the response is written when the chain unwinds. Everything beyond that is
a separate package, for example @koa/router. The difference from Express that matters most is the handler shape: one
ctx object replaces req/res, and there is no error-first next(err); errors are thrown and caught with
try/catch around await next().
import Koa from 'koa';
import Router from '@koa/router';
const app = new Koa();
const router = new Router();
router.get('/books/:id', (ctx) => {
ctx.body = { id: ctx.params.id, title: 'Placeholder title' };
});
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status ?? 500;
ctx.body = { error: err.expose ? err.message : 'Internal Server Error' };
}
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);
Source: Koa documentation.
Koa 3 requires a recent Node.js and is a fair choice for a very small service; since Express 5 also forwards rejected promises, the main argument that Koa was "the async one" has largely disappeared.
Fastify
Fastify is the main performance- and schema-oriented alternative: JSON Schema validation and serialisation are built in, plugins are encapsulated, and logging is Pino by default. Handlers return values or promises, and request and reply objects are Fastify’s own, wrapping the Node.js ones. For the full side-by-side with code, the version map and a comparison table, see Web Frameworks: Express and Fastify and the popularity and ecosystem charts in Backend Frameworks Comparison.
Running Express middleware inside Fastify
Fastify can host Express middleware, which makes a gradual migration possible. @fastify/express (currently 4.x)
provides full Express compatibility, including app.use() and routers, while @fastify/middie supplies only the
connect-style middleware engine. Mounting an existing Express application as a sub-application is the typical use:
import Fastify from 'fastify';
import expressPlugin from '@fastify/express';
import express from 'express';
const legacy = express();
legacy.get('/legacy/books', (_req, res) => {
res.json([{ id: 1, title: 'Placeholder title' }]);
});
const fastify = Fastify({ logger: true });
await fastify.register(expressPlugin);
fastify.use(legacy);
fastify.get('/books', async () => [{ id: 1, title: 'Placeholder title' }]);
await fastify.listen({ port: 3000 });
Source: @fastify/express repository.
The compatibility has a cost: the Express layers run through a bridge, so the performance advantage applies only to the
native Fastify routes, and some Express features that depend on prototype changes to req and res need care. Use it as
a migration step, not as a permanent architecture.
Hono
Hono is a small framework built on the Web Standards Request and Response objects, which means the same application
runs on Node.js, Deno, Bun, Cloudflare Workers and other edge runtimes. On Node.js it is served through the
@hono/node-server adapter. Its router is fast, TypeScript support is first class, and a typed RPC client can be
generated from the route definitions.
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
const app = new Hono();
app.get('/books/:id', (c) => c.json({ id: c.req.param('id'), title: 'Placeholder title' }));
serve({ fetch: app.fetch, port: 3000 });
Source: Hono documentation, Node.js.
Because Hono handlers receive a context rather than Node’s req and res, Express middleware cannot be dropped in;
equivalents exist as Hono middleware (CORS, secure headers, compression, JWT). Choose Hono when portability across
runtimes or the edge is a goal, not as a replacement for an existing Express middleware stack.
Choosing between them
| Option | Pick it when | Watch out for |
|---|---|---|
Express 5 |
Maximum ecosystem, familiarity, a thin layer you structure yourself. |
No built-in validation, DI or structure; you assemble it. |
NestJS |
A large team or codebase needs enforced structure, DI, testability and TypeScript. |
More concepts and boilerplate; Node.js 20 or later for Nest 12. |
LoopBack 4 |
Model-driven REST with generated OpenAPI over existing data sources. |
Express 4 underneath and a smaller community. |
Sails |
Conventional MVC with views, blueprints and sockets. |
Express 4 underneath; convention-heavy configuration. |
FeathersJS |
Services that must be reachable over REST and real time with the same code. |
Express 4 underneath for the Express transport. |
Koa |
A minimal async-first core with your own choice of packages. |
Everything beyond the core is a separate package; smaller ecosystem than Express. |
Fastify |
High throughput, schema validation, structured logging. |
Different plugin model; Express middleware needs a compatibility layer. |
Hono |
Portable code across Node.js, edge and serverless runtimes. |
Web-standard APIs, not Express’s |
A practical rule: start with Express 5 when the service is small or the team already knows it; move to NestJS when the number of modules and developers makes convention more valuable than flexibility; move to Fastify when measured throughput or built-in validation justifies it. A framework on top of Express inherits the Express version it pins, so before adopting one, check which major it runs on (the table above shows the snapshot) and whether the fixes in Versions, Support and LTS reach it.
What changed recently
-
Express 5 is the
latestnpm tag (5.3.0, 2026-10-09). NestJS moved to Express 5 with Nest 11 and its current 12.x line uses Express 5.3, while LoopBack 4, Sails and the FeathersJS Express transport still pin Express 4. -
Koa 3 and Fastify 5 are the current majors;
@fastify/express4.x targets Fastify 5. -
Hono is widely used on edge runtimes, and its Node.js adapter
@hono/node-serveris on major 2. -
The books' frameworks (LoopBack 2/3, KeystoneJS 0.x, MEAN.JS) are obsolete, as described above.
Where the books are outdated
| Topic in the books | Current state |
|---|---|
LoopBack 2/3 models, |
LoopBack 4 is a rewrite with TypeScript, decorators and dependency injection; the |
KeystoneJS 0.x/4 with Jade |
Superseded by Keystone 5/6; Jade was renamed Pug in 2016. |
MEAN.JS with AngularJS, Bower and Grunt |
AngularJS is end of life and Bower is deprecated; use a modern front end with a separate API. |
Frameworks presented as "Express plus extras" for Express 4.13 |
The frameworks still pin Express 4 in several cases, so Express 5 changes (path syntax, |
Express as the only Node.js choice |
Koa, Fastify, Hono and NestJS are now mainstream options. |