frameworks
July 28, 2026 · 7 min read · 0 views

Fastify 5.0: The Lightweight Web Framework Gets Major Performance Upgrades

Fastify 5.0 brings breaking changes, improved async hook handling, and plugin ecosystem overhaul. Learn what's new and how to migrate.

Introduction

Fastify, the lightweight Node.js web framework built for speed and developer experience, has reached version 5.0. This major release introduces significant architectural improvements, breaking changes, and a refined plugin ecosystem designed to make building high-performance APIs even easier.

For developers currently running Fastify 4.x in production, this release requires careful planning. But the improvements—particularly around async hook handling, TypeScript support, and plugin initialization—make the upgrade worthwhile.

What’s New in Fastify 5.0

Enhanced Async Hook Lifecycle

One of the most impactful changes in Fastify 5.0 is the overhaul of async hook execution. The framework now provides better control over when hooks execute relative to request processing, reducing race conditions and improving reliability.

In Fastify 4.x, async hooks could sometimes execute in unpredictable order when multiple plugins were loaded. Version 5.0 introduces explicit hook timing guarantees:

// Fastify 5.0 hook registration
fastify.addHook('onRequest', async (request, reply) => {
  // Guaranteed to execute before route handler
  request.user = await fetchUserAsync(request.headers.authorization);
});

fastify.addHook('onSend', async (request, reply, payload) => {
  // Guaranteed to execute after route handler, before response
  reply.header('X-Response-Time', Date.now() - request.startTime);
  return payload;
});

This deterministic behavior eliminates a common source of bugs in middleware-heavy applications.

Improved Plugin System Architecture

Fastify’s plugin system is arguably its strongest feature. Version 5.0 refines this with:

  • Automatic dependency resolution: Plugins can now declare dependencies more explicitly, and Fastify will ensure proper initialization order.
  • Plugin timeout handling: Configure timeouts per plugin to catch initialization hangs early.
  • Better error context: Plugin errors now include the plugin name and initialization stack trace.

Here’s an example of the new plugin pattern:

// Database plugin with timeout and name
async function dbPlugin(fastify, opts) {
  fastify.register(async (f) => {
    const db = await initDatabase(opts);
    f.decorate('db', db);
    
    f.addHook('onClose', async (instance) => {
      await db.close();
    });
  }, { timeout: 10000 }); // 10-second init timeout
}

dbPlugin[Symbol.for('plugin-meta')] = {
  name: 'fastify-db',
  dependencies: ['fastify-env'], // Declare dependencies
};

fastify.register(dbPlugin, { connectionString: process.env.DATABASE_URL });

TypeScript First-Class Support

Fastify 5.0 ships with significantly improved TypeScript support. Type inference is more accurate, and many type-related edge cases from version 4.x are resolved.

Route handler types now correctly infer request/reply properties:

import Fastify from 'fastify';

const fastify = Fastify();

// Type-safe route handler
fastify.post<{
  Body: { email: string; password: string };
  Reply: { token: string };
}>('/login', async (request, reply) => {
  // request.body is typed as { email: string; password: string }
  const { email, password } = request.body;
  
  const token = await generateToken(email, password);
  
  // reply.send expects { token: string }
  return { token };
});

Custom decorators maintain their types through the request lifecycle:

declare module 'fastify' {
  interface FastifyInstance {
    db: Database;
  }
  interface FastifyRequest {
    user?: User;
  }
}

fastify.get<{ Reply: User[] }>('/users', async (request, reply) => {
  // fastify.db is properly typed
  const users = await fastify.db.query('SELECT * FROM users');
  return users;
});

Streaming and Backpressure Improvements

Fastify 5.0 includes better handling of Node.js stream backpressure, making it safer to stream large files or process high-volume data.

fastify.get('/large-file', async (request, reply) => {
  const fileStream = fs.createReadStream('large-file.bin');
  
  // Fastify 5.0 automatically manages backpressure
  // No need for manual drain event handling
  reply.type('application/octet-stream');
  reply.header('Content-Disposition', 'attachment; filename="large-file.bin"');
  return reply.send(fileStream);
});

fastify.post('/upload', async (request, reply) => {
  // Incoming stream handling also improved
  const chunks = [];
  for await (const chunk of request.file()) {
    chunks.push(chunk);
  }
  return { size: chunks.reduce((sum, c) => sum + c.length, 0) };
});

Breaking Changes You Need to Know

Node.js Version Requirement

Fastify 5.0 requires Node.js 18.17.0 or higher. Support for Node.js 16 and earlier versions is dropped. If you’re running older versions, you must upgrade your runtime before updating Fastify.

Deprecated Hooks Removed

The following hooks, deprecated in 4.x, are now removed:

  • preHandler → use onRequest instead
  • preSerialization → use onSend instead
// ❌ No longer works in Fastify 5.0
fastify.addHook('preHandler', async (request, reply) => {
  // This will throw an error
});

// ✅ Use onRequest instead
fastify.addHook('onRequest', async (request, reply) => {
  // Correct in Fastify 5.0
});

Plugin Scope Isolation

In Fastify 5.0, plugin scope is more strictly isolated. Decorators registered in one plugin scope may not be directly accessible in another scope without explicit exposure:

// Plugin A
fastify.register(async (scopedInstance) => {
  scopedInstance.decorate('serviceA', new ServiceA());
});

// Plugin B
fastify.register(async (scopedInstance) => {
  // scopedInstance.serviceA is NOT automatically available
  // Must be passed explicitly or registered at root level
});

// Correct pattern: register at root for cross-plugin access
fastify.decorate('serviceA', new ServiceA());

Error Handling Changes

Error handlers now receive more structured error objects:

fastify.setErrorHandler((error, request, reply) => {
  // Fastify 5.0 provides more error context
  console.error({
    message: error.message,
    code: error.code,
    statusCode: error.statusCode || 500,
    // New in 5.0:
    plugin: error.plugin, // Which plugin threw the error
    timestamp: new Date(),
  });
  
  reply.code(error.statusCode || 500).send({
    error: error.message,
  });
});

Step-by-Step Migration Guide

1. Update Node.js

First, ensure you’re running Node.js 18.17.0 or later:

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

2. Update Fastify and Dependencies

npm install [email protected]
# Also update commonly used plugins
npm install @fastify/cors@latest @fastify/jwt@latest @fastify/helmet@latest

3. Replace Deprecated Hook Names

Use Regex Tester or your IDE’s find-and-replace to update hook names:

// Search and replace:
// Find: addHook\('preHandler'
// Replace: addHook('onRequest'

// Find: addHook\('preSerialization'
// Replace: addHook('onSend'

4. Review Plugin Registration Order

If your app registers plugins in a complex way, test initialization carefully:

// Create a test initialization function
async function initializeApp() {
  const fastify = Fastify({ logger: true });
  
  try {
    await fastify.register(require('@fastify/cors'));
    await fastify.register(require('@fastify/helmet'));
    await fastify.register(dbPlugin);
    await fastify.register(authPlugin);
    
    await fastify.ready();
    console.log('✓ App initialized successfully');
    return fastify;
  } catch (error) {
    console.error('✗ Initialization failed:', error.message);
    throw error;
  }
}

5. Test Error Scenarios

Write tests to verify error handling works correctly:

// test/error-handling.test.js
import { describe, it, expect } from 'vitest';
import { build } from '../app.js';

describe('Error Handling', () => {
  it('should handle validation errors', async () => {
    const fastify = await build();
    const response = await fastify.inject({
      method: 'POST',
      url: '/login',
      payload: { email: 'invalid' }, // missing password
    });
    
    expect(response.statusCode).toBe(400);
    expect(response.json()).toHaveProperty('message');
  });
});

Testing Your Migration with Kloubot

When working with request/response bodies and headers during testing, Webhook Tester is helpful for capturing and inspecting actual HTTP requests. You can use it to verify your Fastify 5.0 routes return the correct headers and payloads.

For APIs that return JSON, JSON Formatter helps you validate response structure during development.

If your Fastify routes handle JWTs (common in auth plugins), JWT Decoder lets you inspect tokens your routes generate or validate, ensuring the migration didn’t break authentication.

Performance Benchmarks

According to Fastify benchmarks, version 5.0 shows modest improvements over 4.x:

  • Throughput: ~2-3% improvement in requests/second for typical workloads
  • Latency: Reduced p99 latency by 5-8% due to improved hook execution
  • Memory: Slightly reduced memory footprint for plugin-heavy applications

While these improvements aren’t dramatic, they compound with Fastify’s already-excellent performance relative to other frameworks.

Common Pitfalls During Migration

Pitfall 1: Forgetting to Await fastify.ready()

The ready() method ensures all plugins are initialized. Skipping this can cause route handlers to execute before decorators are registered:

// ❌ Bug: routes run before plugins initialize
fastify.register(dbPlugin);
fastify.listen({ port: 3000 });

// ✅ Correct: wait for plugins before listening
fastify.register(dbPlugin);
await fastify.ready();
await fastify.listen({ port: 3000 });

Pitfall 2: Mixing Plugin Scopes Incorrectly

Decorators registered in a plugin scope are not automatically available outside that scope:

// ❌ Bug: serviceA is scoped
fastify.register(async (scoped) => {
  scoped.decorate('serviceA', new ServiceA());
});

fastify.get('/test', async (request, reply) => {
  // fastify.serviceA is undefined
  request.server.serviceA.method(); // Error!
});

// ✅ Correct: decorate at root level
fastify.decorate('serviceA', new ServiceA());

fastify.register(async (scoped) => {
  // scoped.serviceA is accessible via inheritance
});

fastify.get('/test', async (request, reply) => {
  request.server.serviceA.method(); // Works
});

Pitfall 3: Not Testing Async Hook Ordering

While Fastify 5.0 guarantees ordering, your code might have implicit dependencies. Test this:

fastify.addHook('onRequest', async (request) => {
  request.startTime = Date.now();
});

fastify.addHook('onSend', async (request, reply, payload) => {
  // startTime should always be set
  const duration = Date.now() - request.startTime;
  reply.header('X-Duration-ms', duration.toString());
  return payload;
});

Why It Matters

Fastify is one of the fastest Node.js frameworks available, and its plugin ecosystem is unmatched. Version 5.0’s improvements make it even more reliable for production workloads:

  • Predictable hook execution reduces timing-related bugs
  • Better TypeScript support catches errors at build time
  • Stricter plugin isolation prevents subtle state management bugs
  • Improved error context makes debugging easier in production

If you’re building high-performance APIs with Node.js, Fastify 5.0 is worth the upgrade effort.

Next Steps

  1. Audit your codebase: Search for preHandler and preSerialization hooks
  2. Update your dependencies: npm update fastify and related packages
  3. Run your test suite: Existing tests should catch most issues
  4. Test in staging: Deploy to a staging environment before production
  5. Monitor error logs: Watch for new error patterns post-upgrade

For more details, see the official Fastify migration guide.

Conclusion

Fastify 5.0 is a solid release that improves reliability and developer experience without sacrificing the framework’s legendary performance. While breaking changes require some migration work, the improvements justify the effort for production Node.js applications.

Start planning your upgrade now, and take advantage of Fastify’s enhanced async hook handling, plugin system, and TypeScript support.

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