Skip to content
C Codeloom
Node.js

Node Express vs Fastify Comparison

Compare Express and Fastify on performance, ergonomics, plugin model, and TypeScript support to pick the right HTTP framework for your project.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • Routing and middleware differences
  • Built-in schema validation in Fastify
  • Async error handling models
  • TypeScript and plugin ergonomics
  • Performance characteristics and benchmarks

Prerequisites

  • Comfortable with HTML and JavaScript

What and Why

Express and Fastify are the two most common HTTP frameworks for Node.js. Express has been the default since 2010 and has the largest ecosystem. Fastify is newer, faster, and built around schemas. Both are excellent, but they assume different things about how you structure an application.

Mental Model

Express is a thin layer over http. You compose request handling with middleware functions that share a req and res object. Errors flow through a separate four-argument middleware. Fastify is built around a request lifecycle, hooks, and JSON schemas. The schemas drive validation, serialization, and even OpenAPI documentation. Choosing between them often comes down to whether you prefer minimalism or structure.

Express                       Fastify
request                       request
 -> middleware                 -> onRequest hook
 -> middleware                 -> preValidation
 -> handler                    -> validation (schema)
 -> error middleware           -> preHandler
response                        -> handler
                               -> onSend hook
                              response
Two request lifecycles

Hands-on Example

A small JSON endpoint in Express.

import express from 'express';

const app = express();
app.use(express.json());

app.post('/echo', (req, res) => {
  res.json({ received: req.body });
});

app.use((err, _req, res, _next) => {
  res.status(500).json({ error: err.message });
});

app.listen(3000);

The same endpoint in Fastify with a schema.

import Fastify from 'fastify';

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

app.post('/echo', {
  schema: {
    body: {
      type: 'object',
      required: ['name'],
      properties: { name: { type: 'string', minLength: 1 } },
    },
    response: {
      200: {
        type: 'object',
        properties: { received: { type: 'object' } },
      },
    },
  },
  handler: async (req) => ({ received: req.body }),
});

await app.listen({ port: 3000 });

Fastify rejects requests that fail validation with a clear 400 response and uses the response schema to compile a fast serializer. The performance comes from skipping JSON.stringify in favor of generated code.

Error handling in Fastify is built into the lifecycle. Throwing inside an async handler is enough.

app.get('/users/:id', async (req) => {
  const user = await db.users.find(req.params.id);
  if (!user) throw app.httpErrors.notFound('User missing');
  return user;
});

Express requires either calling next(err) from a callback or wrapping async handlers in a helper like express-async-errors.

Common Pitfalls

In Express, forgetting to register the JSON body parser is a classic beginner trap. Fastify parses bodies automatically based on Content-Type, which removes a class of bugs.

Fastify schemas are strict. Adding an extra property to the response without updating the schema results in that property being stripped. Newcomers see fields disappear and assume the framework is broken. The fix is to keep the schema in sync or set response to null during prototyping.

Middleware ordering matters in Express. Body parsers, cookie parsers, and authentication middleware must be registered before any route that depends on them. Fastify replaces ordering with named lifecycle hooks, which is more explicit but takes longer to learn.

Practical Tips

For TypeScript projects, Fastify shines. Its type providers infer request and reply types from your schemas, so handlers know the exact shape of request.body. Express needs separate @types packages and offers far less inference.

Benchmarks generally place Fastify at two to three times the throughput of Express on JSON workloads, mostly thanks to fast-json-stringify. For most apps the bottleneck is the database, so this advantage matters less than ergonomics. Measure your own workload before deciding.

The plugin model in Fastify encourages encapsulation. Each plugin is isolated, has its own decorators, and cannot leak into siblings unless you call fastify-plugin to opt in. Large codebases benefit from this structure. Express plugins are just middleware that mutates the app globally.

If you have a mature Express codebase, migrating piecemeal is hard because the request and reply APIs differ. A common path is to keep Express in front of legacy routes and build new routes in a separate Fastify service.

Wrap-up

Pick Express if you want the smallest possible learning curve and a giant ecosystem of middleware. Pick Fastify if you want first-class schemas, better TypeScript, and a more structured plugin system. Both are battle-tested in production, so the decision is mostly about team taste and the kind of API you are building.