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.
- Reduced Security Surface: There is a major vulnerability in versions of the plugin
<= 4.0.2. - Better Performance: Native Fastify hooks are optimized for Fastify's architecture.
- Cleaner Architecture: Using framework-native patterns leads to more maintainable code.
- Simplified Dependencies: Removes the Express compatibility layer and its transitive dependencies.
First, identify where you're using Express-style middleware. Check for:
- The
middlewarelifecycle 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();
};
}
}
};- The
errorMiddlewarelifecycle 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 });
};
}
}
};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'];
});
}
}
};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/cookieimport 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'
});
}
}
};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/compressimport compress from '@fastify/compress';
export default {
name: 'my-plugin',
hooks: {
fastify: async function (gasket, app) {
await app.register(compress, {
global: true,
encodings: ['gzip', 'deflate']
});
}
}
};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/corsimport cors from '@fastify/cors';
export default {
name: 'my-plugin',
hooks: {
fastify: async function (gasket, app) {
await app.register(cors, {
origin: true
});
}
}
};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
});
});
}
}
};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' };
});
}
}
};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.
Once you've migrated all middleware to native Fastify patterns:
- Remove
@gasket/plugin-middlewarefrom yourgasket.js(if you no longer need themiddlewarelifecycle):
// gasket.js
import pluginFastify from '@gasket/plugin-fastify';
- import pluginMiddleware from '@gasket/plugin-middleware';
export default makeGasket({
plugins: [
pluginFastify,
- pluginMiddleware
]
});- Uninstall the packages:
npm uninstall @gasket/plugin-middleware @fastify/express- Install native Fastify plugins as needed:
npm install @fastify/cookie @fastify/compress @fastify/cors| 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 |
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're migrating from Express to Fastify for performance benefits, follow these steps:
- Replace
@gasket/plugin-expresswith@gasket/plugin-fastify:
npm uninstall @gasket/plugin-express
npm install @gasket/plugin-fastify- 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
}
});- Convert your middleware hooks to fastify hooks using the patterns described in the Fastify section above.
| 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 |
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
});This error occurs when code expects Express's app.use() method on a Fastify instance. Migrate to:
app.addHook()for middlewareapp.register()for pluginsapp.route()orapp.get/post/etc.for routes