Skip to content

Latest commit

 

History

History
507 lines (406 loc) · 12.1 KB

File metadata and controls

507 lines (406 loc) · 12.1 KB

Removing @fastify/express from Gasket Applications

This guide helps you migrate away from @fastify/express in your Gasket application. The @fastify/express package provides Express middleware compatibility for Fastify, but is meant only as an intermediary step for migration from Express to Fastify. Express specific code should be migrated to Fastify over time.

Why Remove @fastify/express?

  1. Reduced Security Surface: There is a major vulnerability in versions of the plugin <= 4.0.2.
  2. Better Performance: Native Fastify hooks are optimized for Fastify's architecture.
  3. Cleaner Architecture: Using framework-native patterns leads to more maintainable code.
  4. Simplified Dependencies: Removes the Express compatibility layer and its transitive dependencies.

Migration Guide for Fastify Users

Step 1: Audit Your Middleware Usage

First, identify where you're using Express-style middleware. Check for:

  1. The middleware lifecycle hook in your plugins:
// This Express-style middleware requires @fastify/express
export default {
  name: 'my-plugin',
  hooks: {
    middleware: function (gasket, app) {
      return (req, res, next) => {
        // Express-style middleware
        req.customData = 'value';
        next();
      };
    }
  }
};
  1. The errorMiddleware lifecycle hook:
// This also requires @fastify/express
export default {
  name: 'my-error-plugin',
  hooks: {
    errorMiddleware: function (gasket) {
      return (err, req, res, next) => {
        res.status(500).send({ error: err.message });
      };
    }
  }
};

Step 2: Convert to Native Fastify Patterns

Converting Request Middleware

Before (Express-style via middleware hook):

export default {
  name: 'my-plugin',
  hooks: {
    middleware: function (gasket, app) {
      return (req, res, next) => {
        req.startTime = Date.now();
        req.customHeader = req.headers['x-custom'];
        next();
      };
    }
  }
};

After (Native Fastify via fastify hook):

export default {
  name: 'my-plugin',
  hooks: {
    fastify: async function (gasket, app) {
      app.addHook('preHandler', async (request, reply) => {
        request.startTime = Date.now();
        request.customHeader = request.headers['x-custom'];
      });
    }
  }
};

Converting Cookie Parsing

Before (Express cookie-parser):

import cookieParser from 'cookie-parser';

export default {
  name: 'my-plugin',
  hooks: {
    middleware: function (gasket, app) {
      return cookieParser();
    }
  }
};

After (Native @fastify/cookie):

npm install @fastify/cookie
import cookie from '@fastify/cookie';

export default {
  name: 'my-plugin',
  hooks: {
    fastify: async function (gasket, app) {
      await app.register(cookie, {
        secret: 'my-secret', // for signed cookies
        hook: 'onRequest'
      });
    }
  }
};

Converting Compression

Before (Express compression):

import compression from 'compression';

export default {
  name: 'my-plugin',
  hooks: {
    middleware: function (gasket, app) {
      return compression();
    }
  }
};

After (Native @fastify/compress):

npm install @fastify/compress
import compress from '@fastify/compress';

export default {
  name: 'my-plugin',
  hooks: {
    fastify: async function (gasket, app) {
      await app.register(compress, {
        global: true,
        encodings: ['gzip', 'deflate']
      });
    }
  }
};

Converting CORS Middleware

Before (Express cors):

import cors from 'cors';

export default {
  name: 'my-plugin',
  hooks: {
    middleware: function (gasket, app) {
      return cors({ origin: true });
    }
  }
};

After (Native @fastify/cors):

npm install @fastify/cors
import cors from '@fastify/cors';

export default {
  name: 'my-plugin',
  hooks: {
    fastify: async function (gasket, app) {
      await app.register(cors, {
        origin: true
      });
    }
  }
};

Converting Error Handling

Before (Express-style errorMiddleware hook):

export default {
  name: 'my-error-plugin',
  hooks: {
    errorMiddleware: function (gasket) {
      return (err, req, res, next) => {
        gasket.logger.error('Request failed:', err);
        res.status(err.statusCode || 500).send({
          error: 'Internal Server Error',
          message: err.message
        });
      };
    }
  }
};

After (Native Fastify error handler):

export default {
  name: 'my-error-plugin',
  hooks: {
    fastify: async function (gasket, app) {
      app.setErrorHandler((error, request, reply) => {
        gasket.logger.error('Request failed:', error);
        reply
          .code(error.statusCode || 500)
          .send({
            error: 'Internal Server Error',
            message: error.message
          });
      });
    }
  }
};

Converting Route-Specific Middleware

Before (Express-style path middleware):

export default {
  name: 'my-auth-plugin',
  hooks: {
    middleware: function (gasket, app) {
      return {
        paths: ['/api'],
        handler: (req, res, next) => {
          if (!req.headers.authorization) {
            return res.status(401).send({ error: 'Unauthorized' });
          }
          next();
        }
      };
    }
  }
};

After (Native Fastify route hooks):

export default {
  name: 'my-auth-plugin',
  hooks: {
    fastify: async function (gasket, app) {
      // Option 1: Register a scoped plugin for /api routes
      app.register(async (instance) => {
        instance.addHook('preHandler', async (request, reply) => {
          if (!request.headers.authorization) {
            reply.code(401).send({ error: 'Unauthorized' });
          }
        });
      }, { prefix: '/api' });

      // Option 2: Use route-level preHandler
      app.get('/api/protected', {
        preHandler: async (request, reply) => {
          if (!request.headers.authorization) {
            reply.code(401).send({ error: 'Unauthorized' });
          }
        }
      }, async (request, reply) => {
        return { data: 'protected resource' };
      });
    }
  }
};

Avoiding the res.locals Pattern

The @gasket/plugin-middleware adds res.locals support for Fastify via @fastify/express. However, using res.locals is deprecated in Gasket v7 in favor of GasketActions.

The res.locals pattern has several drawbacks:

  • Middleware runs for every request, regardless of whether the data is used
  • It's not always clear what properties are available and when
  • It prevents full utilization of Next.js 14 features like App Router and streaming

Recommended: Use GasketActions

Instead of attaching data to res.locals, create a GasketAction in your plugin:

Before (using res.locals):

// In middleware - runs on EVERY request
export default {
  name: 'my-plugin',
  hooks: {
    middleware: function (gasket, app) {
      return async (req, res, next) => {
        res.locals.user = await getUser(req);
        next();
      };
    }
  }
};

// In route handler
app.get('/profile', (req, res) => {
  const user = res.locals.user;
  res.json({ user });
});

After (using GasketActions):

// Define a GasketAction - only called when needed
export default {
  name: 'my-plugin',
  actions: {
    async getUser(gasket, req) {
      // Returns the user
      // Use the req argument to access headers, cookies, queries, etc.
    }
  }
};

// In route handler or anywhere you have access to gasket
app.get('/profile', async (request, reply) => {
  const user = await gasket.actions.getUser(request);
  return { user };
});

For more details on migrating to GasketActions, see the Switch to GasketActions section of the v7 upgrade guide.

Step 3: Remove Dependencies

Once you've migrated all middleware to native Fastify patterns:

  1. Remove @gasket/plugin-middleware from your gasket.js (if you no longer need the middleware lifecycle):
// gasket.js
import pluginFastify from '@gasket/plugin-fastify';
- import pluginMiddleware from '@gasket/plugin-middleware';

export default makeGasket({
  plugins: [
    pluginFastify,
-   pluginMiddleware
  ]
});
  1. Uninstall the packages:
npm uninstall @gasket/plugin-middleware @fastify/express
  1. Install native Fastify plugins as needed:
npm install @fastify/cookie @fastify/compress @fastify/cors

Fastify Hook Reference

Express Middleware Timing Fastify Hook Description
Before route handlers preHandler Runs after parsing, before route handler
Early in request onRequest First hook, runs before parsing
After response sent onResponse Runs after response is sent
On errors setErrorHandler Global error handling
After parsing preValidation After body parsing, before validation
Before serialization preSerialization Before response serialization

Migration Guide for Express Users

The @gasket/plugin-middleware works natively with Express using standard Express middleware patterns. Express users should upgrade the version of @gasket/plugin-middleware to >=7.5.2. This will make ensure that the @fastify/express is only a devDependecy of the middleware plugin and only conditionally included for fastify applications.

Continue using the middleware lifecycle hook as-is. No changes are needed:

export default {
  name: 'my-plugin',
  hooks: {
    middleware: function (gasket, app) {
      return (req, res, next) => {
        // Standard Express middleware - no @fastify/express involved
        next();
      };
    }
  }
};

If You Want to Migrate to Fastify (Without @fastify/express)

If you're migrating from Express to Fastify for performance benefits, follow these steps:

  1. Replace @gasket/plugin-express with @gasket/plugin-fastify:
npm uninstall @gasket/plugin-express
npm install @gasket/plugin-fastify
  1. Update your gasket.js:
// gasket.js
- import pluginExpress from '@gasket/plugin-express';
+ import pluginFastify from '@gasket/plugin-fastify';
- import pluginMiddleware from '@gasket/plugin-middleware';

export default makeGasket({
  plugins: [
-   pluginExpress,
-   pluginMiddleware
+   pluginFastify
  ],
- express: {
+ fastify: {
    compression: true,
    trustProxy: true
  }
});
  1. Convert your middleware hooks to fastify hooks using the patterns described in the Fastify section above.

Common Middleware Conversion Cheatsheet

Express Middleware Fastify Equivalent Installation
cookie-parser @fastify/cookie npm i @fastify/cookie
compression @fastify/compress npm i @fastify/compress
cors @fastify/cors npm i @fastify/cors
helmet @fastify/helmet npm i @fastify/helmet
express-session @fastify/session npm i @fastify/session
body-parser Built-in (Fastify parses JSON/form by default)
serve-static @fastify/static npm i @fastify/static
express-rate-limit @fastify/rate-limit npm i @fastify/rate-limit

Troubleshooting

Error: "errorMiddleware requires @fastify/express"

This error means you're still using the errorMiddleware lifecycle hook. Convert to Fastify's native error handler:

// Use setErrorHandler instead of errorMiddleware
app.setErrorHandler((error, request, reply) => {
  // Handle error
});

Error: "app.use is not a function"

This error occurs when code expects Express's app.use() method on a Fastify instance. Migrate to:

  • app.addHook() for middleware
  • app.register() for plugins
  • app.route() or app.get/post/etc. for routes

Additional Resources