All posts
PerformanceJun 15, 2026·7 min read

Fastify vs Express: Performance Benchmarks

We ran 50,000 requests per second through both frameworks on identical hardware. The results might surprise you — and they're not as simple as 'Fastify is faster.'

The test setup

We deployed both apps to identical 2 vCPU / 4GB dedicated containers with Node 20 LTS. Load generation used autocannon with 50 concurrent connections and 30-second measurement windows. Each measurement was repeated 5 times and averaged to reduce variance. Tests covered five scenarios: plain JSON response, schema-validated JSON, a Postgres SELECT query, a middleware-heavy authenticated route, and large payload serialization.

# Load test command used for each benchmark
npx autocannon   -c 50            # 50 concurrent connections
  -d 30            # 30 second duration
  -p 10            # 10 pipelined requests
  http://localhost:3000/endpoint

Benchmark 1: Plain JSON response

Fastify 4.x:  92,400 req/s  |  avg: 1.1ms  |  p99: 3.2ms
Express 4.x:  14,800 req/s  |  avg: 6.8ms  |  p99: 18.4ms
Fastify 5.x:  96,100 req/s  |  avg: 1.0ms  |  p99: 3.0ms

For a route that does nothing but return res.json({ ok: true }), Fastify is ~6x faster. This headline number represents pure framework overhead — the cost of routing, middleware execution, and response serialization when there's no I/O.

The difference comes down to three things: Fastify uses a radix tree router (O(log n) lookup) vs Express's linear array of regex patterns (O(n)), Fastify's plugin system has less function call overhead than Express middleware chains, and Fastify uses fast-json-stringify for serialization vs dynamic JSON.stringify.

Benchmark 2: Schema-validated JSON response

Fastify (with JSON Schema):  88,200 req/s
Express (with Zod validation):  11,100 req/s
Express (no validation):  14,800 req/s

Fastify's schema system compiles JSON Schema definitions into optimized serialization functions at startup. At request time, there's no validation overhead — just a pre-compiled serializer that's much faster than JSON.stringify. Express using Zod validation adds per-request parsing overhead, hence the drop below even its baseline.

// Fastify with compiled schema — validation is free at runtime
fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['email', 'name'],
      properties: {
        email: { type: 'string', format: 'email' },
        name: { type: 'string', minLength: 1 },
      },
    },
    response: {
      201: {
        type: 'object',
        properties: {
          id: { type: 'string' },
          email: { type: 'string' },
        },
      },
    },
  },
  handler: async (request, reply) => {
    return reply.status(201).send({ id: '123', email: request.body.email });
  },
});

Benchmark 3: Single Postgres query

Fastify:  8,400 req/s  |  avg: 12ms
Express:  8,100 req/s  |  avg: 13ms
Difference: ~4%

When you add a real I/O operation, the framework overhead becomes statistically insignificant. The database query dominates the response time. Both frameworks are CPU-idle for the 11ms it takes Postgres to respond — the event loop handles other requests during this wait regardless of the framework.

The takeaway: If your API's bottleneck is database latency (and for most SaaS APIs, it is), the 6x headline difference is almost entirely irrelevant to your users' experience. The performance argument for Fastify is stronger for CPU-bound operations and high-frequency lightweight endpoints.

Benchmark 4: Middleware-heavy authenticated route

Fastify (auth hook + logger + rate limit):  19,200 req/s  |  p99: 8ms
Express (auth middleware + morgan + rate limit):  8,600 req/s   |  p99: 19ms

With multiple middleware layers, Fastify's hook system maintains a ~2x advantage over Express. Fastify hooks are pre-compiled into a direct function call sequence at route registration time. Express middleware is a linked list traversed at request time.

// Express: middleware chain traversal per request
app.use(authMiddleware);
app.use(loggerMiddleware);
app.use(rateLimitMiddleware);
app.get('/data', handler);

// Fastify: hooks compiled at registration, direct invocation at request time
fastify.addHook('preHandler', authHook);
fastify.addHook('preHandler', rateLimitHook);
fastify.get('/data', { onRequest: logHook }, handler);

Benchmark 5: Large payload serialization (1MB JSON)

Fastify (fast-json-stringify):  1,240 req/s  |  avg: 40ms
Express (JSON.stringify):         680 req/s  |  avg: 73ms

For large payloads, the serialization gap widens. fast-json-stringify builds a specialized serializer from your schema that avoids the type-checking overhead of generic JSON.stringify. For APIs that return large datasets frequently, this 2x improvement translates directly to reduced CPU usage and lower container costs.

Memory footprint comparison

                Idle RSS   Under load (50k req/s)
Fastify 4:       ~32MB          ~88MB
Fastify 5:       ~28MB          ~82MB
Express 4:       ~44MB         ~135MB

Fastify's lower memory usage means you can run more instances on the same hardware, or use smaller containers at the same concurrency level.

Developer experience: the real tradeoffs

TypeScript support

Fastify was designed with TypeScript as a first-class citizen. Route handlers have full type inference from schema definitions. Express's TypeScript support is through @types/express — functional, but bolted on. You frequently need manual type assertions for request bodies.

// Fastify: request body is automatically typed from schema
fastify.post<{ Body: { email: string; name: string } }>('/users', {
  schema: { body: UserSchema },
}, async (request) => {
  // request.body.email is typed as string — no casting needed
  return createUser(request.body.email, request.body.name);
});

// Express: manual typing required
app.post('/users', async (req: Request<{}, {}, CreateUserBody>, res) => {
  // Works, but TypeScript doesn't verify the type matches runtime reality
  const { email, name } = req.body;
});

Plugin ecosystem

Express has 10+ years of middleware packages — authentication, rate limiting, compression, sessions — all well-documented with thousands of Stack Overflow answers. Fastify's ecosystem is smaller but growing rapidly, and first-party plugins cover the 95% case. If you rely on a specific Express middleware (passport.js, express-session, etc.), check for a Fastify equivalent before migrating.

Learning curve

Fastify's plugin system and decorators require a mental model shift from Express. Expect 1–2 days to feel comfortable, 1 week to feel productive. The schema-first approach is actually better API design discipline — you'll document your API as you build it.

Real-world performance: when does it matter?

For a typical SaaS API handling 100–1,000 req/s with database-backed routes, the framework overhead is not your bottleneck. Your bottleneck is query latency, N+1 queries, or missing indexes.

Fastify's performance advantage becomes meaningful when:

  • You're building an API gateway or proxy layer with no database (pure compute/routing)
  • You're handling 10,000+ req/s and framework CPU overhead is measurable
  • You need to serialize large JSON payloads frequently
  • You're running on resource-constrained hardware where memory matters

Migration path from Express to Fastify

If you decide to migrate, the path is incremental:

  1. Add Fastify alongside Express, not replacing it
  2. Start with one new route in Fastify
  3. Move routes one by one, testing each
  4. Replace middleware with equivalent Fastify hooks/plugins
  5. Remove Express when all routes are migrated
// Most Express middleware has a Fastify equivalent
// express-rate-limit   →  @fastify/rate-limit
// helmet               →  @fastify/helmet
// cors                 →  @fastify/cors
// express-session      →  @fastify/session
// passport             →  @fastify/passport
// multer (file upload) →  @fastify/multipart

Our verdict

New projects: Start with Fastify. The TypeScript DX is better, the performance is better, and the schema-first approach leads to more disciplined API design. The learning curve is worth it.

Existing Express apps: Don't migrate unless you have a measured performance problem that you've confirmed is framework-related (not database, not N+1, not missing indexes). Migration takes time and introduces risk. The ROI only exists if framework overhead is genuinely your bottleneck — which for most apps, it isn't.

High-throughput new services: Fastify, no question. The numbers don't lie for pure compute workloads.

Ready to put this into practice?

Deploy your Node.js app to production in minutes — zero YAML, automatic CI/CD, and HTTPS included.