Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
Node.jsComparison

Express 5 vs Fastify 5: which Node.js framework to pick in 2026

Express 5 and Fastify 5 compared on support status, validation, error handling, upgrades and performance, with the same API built and tested in both.

Cloudsoft Zone

7 min read

A silver Amtrak Acela Express high-speed train approaching under footbridges and overhead wires at Readville station in Boston
Photograph: 4300streetcar / CC BY 4.0, via Wikimedia Commons
In this article

Pick Express 5 if your team already knows Express, you depend on its huge middleware ecosystem, or you are upgrading an existing Express 4 app. Pick Fastify 5 for a new JSON API where you want request validation, response serialisation, structured logging and in-process testing built in rather than assembled from packages. Both are actively maintained, both run well on Node.js 24, and for most database-backed APIs the framework is not your bottleneck.

This comparison uses the official migration guides and support pages, npm release data checked on 9 October 2026, and the same small API built in both frameworks and run on Node.js 24.19.0 with Express 5.3.0 and Fastify 5.12.5.

Where each project stands in October 2026

Express 5Fastify 5
First 5.x release5.0.0, 9 September 20245.0.0, 17 September 2024
Latest on npm (9 Oct 2026)5.3.05.12.5
Minimum Node.js1820
Previous major4.x in maintenance since 1 April 2025; still receiving releases (4.22.3 on 14 September 2026)4.x support ended 30 June 2025
Next majorNone announced6.0.0 in alpha on the next tag
ValidationBring your own (zod, Joi, express-validator and others)Built in: JSON Schema with Ajv v8
SerialisationJSON.stringifyfast-json-stringify when a response schema is set
LoggingBring your ownBuilt in (Pino)
Async errorsRejected promises go to error middlewareAsync handlers are native; thrown errors go to the error handler

Express's published support policy says a major version stays ACTIVE for at least 12 months after being tagged latest, and the previous major then gets 12 months of MAINTENANCE with security and high-priority fixes. Version 5.1.0 became latest on 31 March 2025, which started that clock for 4.x; the Express team gave 4.x an end-of-life date of no sooner than 1 October 2026 and said it may extend maintenance. If you still run Express 4, plan the upgrade now.

A sprinter crouched in starting blocks on a red running track, one hand resting beside a black relay baton
Both frameworks are actively maintained in 2026; Fastify 5 needs Node.js 20 or later, Express 5 needs Node.js 18 or later.

The same API in both frameworks

Each version exposes GET /users/:id and POST /users, returns 404 for unknown users, validates the request body and handles a database error.

Express 5

JavaScript
// express-app.mjs
import express from 'express';

const app = express();
app.use(express.json({ limit: '100kb' }));

app.get('/users/:id', async (req, res) => {
  const user = await findUser(req.params.id); // a rejection goes to the error handler
  if (!user) return res.status(404).json({ error: 'not found' });
  res.json(user);
});

app.post('/users', (req, res) => {
  const { name } = req.body ?? {};
  if (typeof name !== 'string' || name.length === 0) {
    return res.status(400).json({ error: 'name is required' });
  }
  res.status(201).json({ id: '2', name });
});

app.use((err, req, res, next) => {
  console.error(err.message);
  res.status(500).json({ error: 'internal error' });
});

async function findUser(id) {
  if (id === 'boom') throw new Error('database unavailable');
  return id === '1' ? { id: '1', name: 'Asha' } : null;
}

app.listen(3002, () => console.log('express on 3002'));

Fastify 5

JavaScript
// fastify-app.mjs
import Fastify from 'fastify';

const app = Fastify({ logger: false });

app.get('/users/:id', {
  schema: {
    params: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
  },
}, async (request, reply) => {
  const user = await findUser(request.params.id);
  if (!user) return reply.code(404).send({ error: 'not found' });
  return user;
});

app.post('/users', {
  schema: {
    body: {
      type: 'object',
      properties: { name: { type: 'string', minLength: 1 } },
      required: ['name'],
      additionalProperties: false,
    },
    response: {
      201: {
        type: 'object',
        properties: { id: { type: 'string' }, name: { type: 'string' } },
      },
    },
  },
}, async (request, reply) => {
  reply.code(201);
  return { id: '2', name: request.body.name, internalNote: 'not serialised' };
});

async function findUser(id) {
  if (id === 'boom') throw new Error('database unavailable');
  return id === '1' ? { id: '1', name: 'Asha' } : null;
}

await app.listen({ port: 3003 });
console.log('fastify on 3003');

What came back

Shell
# Express 5 (port 3002)
GET  /users/1     {"id":"1","name":"Asha"}
GET  /users/9     {"error":"not found"}
GET  /users/boom  {"error":"internal error"}
POST /users {}    {"error":"name is required"}
POST /users       {"id":"2","name":"Ravi"} 201

# Fastify 5 (port 3003)
GET  /users/1     {"id":"1","name":"Asha"}
GET  /users/9     {"error":"not found"}
GET  /users/boom  {"statusCode":500,"error":"Internal Server Error","message":"database unavailable"}
POST /users {}    {"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"body must have required property 'name'"}
POST /users       {"id":"2","name":"Ravi"} 201

Three differences stand out:

  • Validation. Express needs hand-written checks or a library. Fastify rejected the empty body from the schema alone, with a structured 400 response.
  • Response filtering. The Fastify handler returned an extra internalNote field, and it never reached the client, because only properties in the response schema were serialised. The Fastify documentation recommends output schemas to prevent accidental disclosure of sensitive data and says they can drastically increase throughput.
  • Error output. Fastify's default error response included the original error message, database unavailable. That can leak internal details. Add your own error handler before going to production:
JavaScript
app.setErrorHandler((error, request, reply) => {
  if (error.validation) return reply.code(400).send({ error: error.message });
  request.log.error(error);
  reply.code(500).send({ error: 'internal error' });
});

With that handler, the same failing route returned 500 {"error":"internal error"} in our test, which we ran with Fastify's built-in app.inject(), so no network port was needed.

What changed in Express 5, if you are upgrading

Express 5 is mostly a clean-up release, but several changes break Express 4 code. From the official migration guide:

  • Async errors are handled. A rejected promise from a handler or middleware is passed to the error middleware, so you no longer need wrapper packages.
  • Route syntax changed (path-to-regexp 8). Wildcards must be named: /*splat instead of /*, and optional parts use braces: /:file{.:ext} instead of ?. Regular expression characters in string paths are no longer supported.
  • req.body is undefined when no body parser ran, rather than {}.
  • The default query parser is now "simple", so ?a[b]=1 no longer becomes a nested object, and req.query is read-only.
  • express.urlencoded() defaults to extended: false, and express.static ignores dotfiles by default.
  • Removed APIs: app.del(), req.param(), res.sendfile(), and signatures like res.json(obj, status); use res.status(status).json(obj).

We checked the route changes directly. An Express 4 style wildcard now throws at registration time, which at least makes the problem loud:

Shell
Express 4 wildcard rejected: Missing parameter name at index 6: /old/*; visit https://git.new/pathToRegexpError for info
/files/a/b/c.txt {"splat":["a","b","c.txt"]}
/docs/readme {"file":"readme"}
/docs/readme.md {"file":"readme","ext":"md"}
{"a[b]":"1"}            # simple query parser: no nested object
{"body":"undefined"}    # no body parser registered

Note that a named splat arrives as an array of path segments, not a single string.

Runners passing relay batons at a changeover zone on a red athletics track while other athletes wait on the grass
Express middleware passes each request along a chain; in Express 5 a rejected promise is handed to the error middleware automatically.

What changed in Fastify 5

Fastify 5 requires Node.js 20 or later and is stricter about schemas: the migration guide says you must provide a full JSON Schema for querystring, params, body and response, including the type property, and the jsonShortHand option is gone. A custom logger instance now goes in loggerInstance rather than logger. Treat schemas as application code: Fastify compiles them with new Function(), and its documentation warns that this is unsafe with user-provided schemas.

Performance: what actually matters

Fastify was designed for low per-request overhead, and its schema-based serialisation is a real advantage for large JSON responses. But in a typical API each request also waits on a database or another service, and that wait usually dwarfs the framework's share. Benchmark your own routes, with your database, before choosing on speed alone. If you do find that framework overhead matters, Fastify's response schemas are the first lever to pull.

How to choose

  • Existing Express 4 app: upgrade to Express 5. It is far less work than a rewrite, and the official migration guide lists every breaking change.
  • New public JSON API: Fastify 5. Validation, serialisation, logging and inject() testing are there from the first line, and the schemas double as documentation.
  • Server-rendered site, or heavy use of Express middleware such as Passport strategies: Express 5.
  • Team of mixed experience: Express's smaller surface is easier to learn; Fastify's plugin encapsulation pays off as the codebase grows.

Gotchas

  • Fastify's default 500 response exposes error messages. Set an error handler.
  • Express 5 still has no request validation. Upgrading does not make an unvalidated API safe; add a schema library.
  • Middleware compatibility. Fastify does not run Express middleware natively; check that a Fastify plugin exists for each piece you rely on before porting.
  • Body size limits. Set them explicitly in both frameworks (express.json({ limit }), Fastify's bodyLimit) to match what your API accepts.

What to do next

If you are on Express 4, create a branch, upgrade to the latest Express 5 release, and search for app.get('*', /*, req.param( and res.json( calls with two arguments; your tests will find the rest. If you are starting fresh, build two or three real endpoints in Fastify with full request and response schemas, test them with app.inject(), and decide after a day of real work rather than from a benchmark chart.

Frequently asked questions

  • Is Express 5 stable and ready for production?

    Yes. Express 5.0.0 was released on 9 September 2024, 5.1.0 became the default latest npm release on 31 March 2025, and 5.3.0 was the latest release on 9 October 2026.

  • Is Express 4 still supported?

    Express 4 has been in maintenance since 1 April 2025, receiving security and high-priority fixes, with end of life planned no sooner than 1 October 2026. Releases were still appearing in September 2026, but you should plan the upgrade to Express 5.

  • Which Node.js versions does Fastify 5 support?

    Fastify 5 requires Node.js 20 or later. Its LTS page lists Node.js 20 and 22; we also ran it on Node.js 24.19.0 without problems.

  • Is Fastify faster than Express?

    Fastify is designed for lower per-request overhead, and response schemas speed up JSON serialisation. In most real APIs, database and network time dominate, so measure your own routes before choosing on speed.

  • Do I still need express-async-errors with Express 5?

    No. Express 5 forwards rejected promises from handlers and middleware to your error-handling middleware.

  • Can I use Express middleware in Fastify?

    Not natively. Use the equivalent Fastify plugin where one exists, and check each dependency before porting an Express app.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. Migrating to Express 5 — Express
  2. Express@5.1.0: Now the Default on npm with LTS Timeline — Express, 2025-03-31
  3. Express version support — Express
  4. Fastify V5 migration guide — Fastify
  5. Fastify Long Term Support — Fastify
  6. Fastify validation and serialization — Fastify

Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.