frameworks
October 23, 2026 · 8 min read · 0 views

Fastify 5.0: Performance Upgrades and Async Hooks Deep Dive

Fastify 5.0 brings major performance improvements, enhanced async hook support, and breaking changes. Learn what's new, how to migrate, and how to leverage the updates.

Introduction

Fastify 5.0 has arrived, and it represents a significant milestone for the high-performance Node.js web framework. With a focus on async hook improvements, performance optimizations, and modernization, this release sets a new standard for building fast, reliable APIs.

Whether you’re running a greenfield project or maintaining legacy services, understanding the changes in Fastify 5.0 is critical for staying current and maximizing performance.

What’s New in Fastify 5.0

Major Performance Gains

Fastify 5.0 delivers measurable throughput improvements across the board:

  • 20–30% faster request handling in typical REST API scenarios
  • Reduced memory footprint through internal optimization and garbage collection improvements
  • Faster plugin loading and initialization via streamlined dependency resolution
  • Better handling of high-concurrency workloads with improved async resource tracking

These gains come from:

  1. Optimized route matching — The router has been refactored to reduce lookup times, especially for routes with many parameters.
  2. Improved async context tracking — Better integration with Node.js’s AsyncLocalStorage and AsyncResource.
  3. Reduced JSON serialization overhead — Smarter JSON handling for large payloads.
  4. Native support for HTTP/2 Server Push — Previously an extension; now built-in and optimized.

Enhanced Async Hooks

Async hooks are central to Fastify’s plugin ecosystem. Fastify 5.0 refines these significantly:

onRequest → onRequestHookHandler

The hook signature is now more explicit and type-safe:

fastify.addHook('onRequest', async (request, reply) => {
  console.log(`[${request.method}] ${request.url}`);
  // Hook logic here
});

New onSend Hook Timing

The onSend hook now fires before reply object serialization, allowing you to intercept and modify the response object directly:

fastify.addHook('onSend', async (request, reply, payload) => {
  // Payload is the serialized response
  // Modify headers, logging, metrics — all happen before send
  reply.header('X-Response-Time', Date.now() - request.startTime);
  return payload;
});

New onError Hook

Better error handling with the enhanced onError hook:

fastify.addHook('onError', async (request, reply, error) => {
  console.error(`Error on ${request.method} ${request.url}:`, error.message);
  // Custom error logging, metrics, alerting
  reply.status(error.statusCode || 500).send({ error: error.message });
});

Lifecycle Guarantees

Fastify 5.0 now guarantees hook execution order, even in error scenarios:

const lifecycleExample = async () => {
  // 1. onRequest
  fastify.addHook('onRequest', async (request, reply) => {
    console.log('1. onRequest fired');
  });

  // 2. preHandler (route-specific)
  fastify.addHook('preHandler', async (request, reply) => {
    console.log('2. preHandler fired');
  });

  // 3. Route handler
  fastify.get('/example', async (request, reply) => {
    console.log('3. Route handler running');
    return { status: 'ok' };
  });

  // 4. onSend
  fastify.addHook('onSend', async (request, reply, payload) => {
    console.log('4. onSend fired');
    return payload;
  });

  // 5. onResponse
  fastify.addHook('onResponse', async (request, reply) => {
    console.log('5. onResponse fired');
  });
};

This predictable flow is essential for metrics, logging, and middleware-like behavior.

Breaking Changes You Need to Know

1. Node.js Version Requirement

Fastify 5.0 requires Node.js 18+. If you’re on Node.js 16 or earlier, you must upgrade.

2. Deprecated Plugins Removed

Several long-deprecated plugins have been removed:

  • fastify-jwt → Use @fastify/jwt
  • fastify-cors → Use @fastify/cors
  • fastify-helmet → Use @fastify/helmet

Update your imports:

// Old (no longer works)
import fastifyJwt from 'fastify-jwt';

// New
import fastifyJwt from '@fastify/jwt';

fastify.register(fastifyJwt, { secret: 'your-secret' });

3. Changed Reply.send() Behavior

The reply.send() method now throws if called multiple times in the same request:

fastify.get('/bad', async (request, reply) => {
  reply.send({ data: 1 });
  reply.send({ data: 2 }); // Throws FasifyError
});

4. Stricter Schema Validation

JSON Schema validation is now strict by default. Invalid schemas will throw during route registration:

// This will now throw during app startup
fastify.post('/data', {
  schema: {
    body: {
      type: 'object',
      properties: {
        name: { type: 'invalid-type' } // Error!
      }
    }
  },
  handler: async (request, reply) => {
    return { ok: true };
  }
});

5. Plugin Load Order

Plugin dependency order is now enforced. Plugins that declare dependencies on other plugins will throw if those plugins aren’t registered first:

// plugin-b.js
export default async function pluginB(fastify, opts) {
  // Declares dependency on plugin-a
  fastify.register(pluginA);
}

pluginB.dependencies = ['plugin-a']; // Explicit dependency

Step-by-Step Migration Guide

Step 1: Audit Your Dependencies

Check your package.json for deprecated plugin usage:

npm ls | grep fastify-

Replace old packages with their scoped equivalents:

npm uninstall fastify-cors fastify-jwt fastify-helmet
npm install @fastify/cors @fastify/jwt @fastify/helmet

Step 2: Update Node.js

Ensure you’re on Node.js 18+:

node --version
# Should output v18.0.0 or higher

Step 3: Update Fastify

npm install [email protected]

Step 4: Test Schema Validation

Run your test suite to catch schema validation errors early:

npm test

Step 5: Update Hook Usage

Review your hooks and update them to use the new signatures if needed. For most cases, existing code will work without changes, but test thoroughly.

Example Migration: JWT Plugin

Before (Fastify 4.x):

import Fastify from 'fastify';
import fastifyJwt from 'fastify-jwt';

const fastify = Fastify();

fastify.register(fastifyJwt, {
  secret: process.env.JWT_SECRET,
  sign: { expiresIn: '24h' }
});

fastify.get('/protected', async (request, reply) => {
  await request.jwtVerify();
  return { data: 'protected' };
});

After (Fastify 5.0):

import Fastify from 'fastify';
import fastifyJwt from '@fastify/jwt';

const fastify = Fastify();

await fastify.register(fastifyJwt, {
  secret: process.env.JWT_SECRET,
  sign: { expiresIn: '24h' }
});

fastify.get('/protected', async (request, reply) => {
  await request.jwtVerify();
  return { data: 'protected' };
});

The main change: use the scoped package name.

Performance Testing

Benchmarking Your Migration

Use a tool like autocannon to compare performance before and after:

npm install -D autocannon
// benchmark.js
import autocannon from 'autocannon';

const result = await autocannon({
  url: 'http://localhost:3000/api/endpoint',
  connections: 100,
  duration: 10,
  requests: [
    {
      path: '/api/data',
      method: 'GET'
    }
  ]
});

console.log('Throughput:', result.requests.average, 'req/sec');
console.log('Latency (p99):', result.latency.p99, 'ms');

Run this before and after upgrading to quantify gains.

Common Pitfalls

Pitfall 1: Multiple reply.send() Calls

Fastify 5.0 enforces single-send semantics. If you have code like:

fastify.get('/data', async (request, reply) => {
  if (!request.query.id) {
    reply.status(400).send({ error: 'Missing id' });
  }
  const data = await fetchData(request.query.id);
  reply.send(data); // This will throw if id was missing
});

Fix with early returns:

fastify.get('/data', async (request, reply) => {
  if (!request.query.id) {
    return reply.status(400).send({ error: 'Missing id' });
  }
  const data = await fetchData(request.query.id);
  return reply.send(data);
});

Pitfall 2: Forgetting await on Plugin Registration

In Fastify 5.0, plugin registration should be awaited in async contexts:

// ❌ Bad
const app = Fastify();
app.register(myPlugin);
app.listen({ port: 3000 });

// ✅ Good
const app = Fastify();
await app.register(myPlugin);
await app.listen({ port: 3000 });

Pitfall 3: Schema Validation Errors

Invalid JSON schemas will now throw immediately. Use a JSON schema validator like JSON Formatter to validate your schemas before deploying:

// Validate schema structure
const isValidSchema = (schema) => {
  try {
    // Compile the schema
    const ajv = new Ajv();
    ajv.compile(schema);
    return true;
  } catch (e) {
    console.error('Invalid schema:', e.message);
    return false;
  }
};

Why It Matters

Fastify 5.0 is more than a version bump—it’s a consolidation of best practices:

  1. Performance — Every millisecond matters in production. The 20–30% improvements translate to reduced infrastructure costs and better user experience.
  2. Reliability — Stricter validation and hook guarantees make debugging easier and production issues rarer.
  3. Modern JavaScript — Requiring Node.js 18+ lets Fastify leverage modern async/await patterns and native APIs.
  4. Plugin Ecosystem — Unified scoped package naming makes it easier to discover and manage official plugins.

Getting Started

Quick Start Example

Here’s a complete Fastify 5.0 server:

import Fastify from 'fastify';

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

// Add a hook
fastify.addHook('onRequest', async (request, reply) => {
  request.startTime = Date.now();
});

fastify.addHook('onResponse', async (request, reply) => {
  const duration = Date.now() - request.startTime;
  fastify.log.info({ duration, method: request.method, url: request.url });
});

// Simple route
fastify.get('/', async (request, reply) => {
  return { hello: 'world' };
});

// Route with validation
fastify.post('/data', {
  schema: {
    body: {
      type: 'object',
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' }
      },
      required: ['name', 'email']
    },
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'string' },
          name: { type: 'string' }
        }
      }
    }
  },
  handler: async (request, reply) => {
    // request.body is already validated
    return { id: '123', name: request.body.name };
  }
});

// Start server
await fastify.listen({ port: 3000 });
console.log('Server running on http://localhost:3000');

Testing Your Routes

Use the API Request Builder to test your routes during development:

curl -X POST http://localhost:3000/data \
  -H "Content-Type: application/json" \
  -d '{"name": "John", "email": "[email protected]"}'

Handling JWT Authentication

If your API uses JWT tokens, Fastify 5.0’s improved async hooks make authentication cleaner:

import Fastify from 'fastify';
import fastifyJwt from '@fastify/jwt';

const fastify = Fastify();

await fastify.register(fastifyJwt, {
  secret: process.env.JWT_SECRET || 'super-secret'
});

// Protect routes with a preHandler hook
fastify.addHook('preHandler', async (request, reply) => {
  if (request.url.startsWith('/protected')) {
    try {
      await request.jwtVerify();
    } catch (error) {
      reply.status(401).send({ error: 'Unauthorized' });
    }
  }
});

fastify.post('/login', async (request, reply) => {
  const token = fastify.jwt.sign({ userId: 'user123' });
  return { token };
});

fastify.get('/protected', async (request, reply) => {
  // Only reached if JWT is valid
  return { data: 'secret', userId: request.user.userId };
});

You can verify your JWT tokens using the JWT Decoder tool during development.

Debugging with Kloubot Tools

When building Fastify 5.0 applications, several Kloubot tools are invaluable:

Conclusion

Fastify 5.0 represents a significant step forward for Node.js developers building high-performance APIs. With improved async hooks, guaranteed performance gains, and a more predictable plugin ecosystem, migrating from earlier versions is worth the effort.

The breaking changes are minimal and well-documented. By following the migration guide and testing thoroughly, you can be up and running in hours, not days.

Start with the examples in this post, run your test suite, and measure the performance improvements. Your users will thank you.

This post was generated with AI assistance and reviewed for accuracy.