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 app inside, so every Express middleware, req and res property 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, res and next is different.

flowchart TD A["Which foundation does the project need?"] --> B{"Is a structured, opinionated framework wanted?"} B -->|"yes"| C{"Which style?"} B -->|"no, a minimal core"| D{"What matters most?"} C -->|"modules, DI, decorators"| E["NestJS (Express or Fastify platform)"] C -->|"OpenAPI-first, model-driven REST"| F["LoopBack 4"] C -->|"MVC, blueprints, sockets"| G["Sails"] C -->|"service-oriented, real time"| H["FeathersJS"] D -->|"ecosystem and familiarity"| I["Express 5"] D -->|"throughput and schema validation"| J["Fastify"] D -->|"small async-first core"| K["Koa"] D -->|"web standards, edge runtimes"| L["Hono"]

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 to app.get() and app.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(),
  },
};

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);

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 slc loopback generator and the loopback-boot startup scripts.

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);

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 });

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 });

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 req and res.

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 latest npm 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/express 4.x targets Fastify 5.

  • Hono is widely used on edge runtimes, and its Node.js adapter @hono/node-server is 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, slc and loopback-boot

LoopBack 4 is a rewrite with TypeScript, decorators and dependency injection; the slc tool is gone.

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, req.query, promise forwarding) do not apply to them unless they say so.

Express as the only Node.js choice

Koa, Fastify, Hono and NestJS are now mainstream options.