Skip to content
Mig ration F low
backend node express fastify api

Express Fastify

Migrate an Express HTTP server to Fastify for built-in validation, schema-first design, and higher throughput.

Copy for

Using an AI agent? See /agents for MCP, JSON, and llms.txt access.

Express → Fastify

Philosophy shift

Express is middleware-first: routing, validation, serialization all go through req/res middleware chains. Fastify is schema-first: routes declare JSON Schema for inputs and outputs, enabling automatic validation and fast serialization. Hooks replace middleware.

Rule: Fastify’s reply is not Express’s res. Never assign return res.json() — use return reply.send() and always return it to signal route completion.

Setup

Remove Express (keep it during migration if running in parallel):

npm uninstall express @types/express

Install Fastify:

npm install fastify
npm install --save-dev @types/node

Bootstrap

// Express
import express from 'express';
const app = express();
app.use(express.json());

app.listen(3000, () => console.log('listening'));

// Fastify
import Fastify from 'fastify';
const app = Fastify({ logger: true });

await app.listen({ port: 3000, host: '0.0.0.0' });

Core transformations

Routes

// Express
app.get('/users/:id', (req, res) => {
  const { id } = req.params;
  res.status(200).json({ id });
});

// Fastify
app.get<{ Params: { id: string } }>('/users/:id', async (request, reply) => {
  const { id } = request.params;
  return reply.status(200).send({ id });
});

Middleware → Hooks

// Express middleware
app.use((req, res, next) => {
  req.requestId = uuid();
  next();
});

// Fastify hook
app.addHook('onRequest', async (request, reply) => {
  request.requestId = uuid();
});

Hook lifecycle order: onRequestpreParsingpreValidationpreHandler → handler → preSerializationonSendonResponse.

Error handling

// Express
app.use((err, req, res, next) => {
  res.status(err.status ?? 500).json({ error: err.message });
});

// Fastify
app.setErrorHandler(async (error, request, reply) => {
  return reply.status(error.statusCode ?? 500).send({ error: error.message });
});

Route validation (Fastify-native)

// Fastify — JSON Schema on the route
app.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' },
      },
    },
    response: {
      201: {
        type: 'object',
        properties: {
          id: { type: 'string' },
        },
      },
    },
  },
}, async (request, reply) => {
  const { name, email } = request.body;
  const user = await createUser({ name, email });
  return reply.status(201).send(user);
});

Static files

// Express
app.use(express.static('public'));

// Fastify
import fastifyStatic from '@fastify/static';
import path from 'path';
app.register(fastifyStatic, { root: path.join(__dirname, 'public') });

CORS

// Express
import cors from 'cors';
app.use(cors({ origin: 'https://example.com' }));

// Fastify
import fastifyCors from '@fastify/cors';
app.register(fastifyCors, { origin: 'https://example.com' });

Common Express middleware equivalents

ExpressFastify plugin
express.json()built-in (default)
express.static@fastify/static
cors@fastify/cors
helmet@fastify/helmet
express-rate-limit@fastify/rate-limit
multer@fastify/multipart
cookie-parser@fastify/cookie
express-session@fastify/session
compression@fastify/compress

Plugin system

Fastify uses fastify-plugin for shared scope. Group routes with register:

// Routes plugin
import fp from 'fastify-plugin';

const userRoutes = fp(async (app) => {
  app.get('/users', async (request, reply) => { ... });
  app.post('/users', async (request, reply) => { ... });
});

app.register(userRoutes, { prefix: '/api/v1' });

When NOT to migrate

Pitfalls

Validation checklist

Codemod references

AI Prompt

You are migrating an Express route handler to Fastify.

Rules:
1. Replace `(req, res) =>` with `async (request, reply) =>`.
2. Replace `res.json(data)` with `return reply.send(data)`.
3. Replace `res.status(n).json(data)` with `return reply.status(n).send(data)`.
4. Replace Express middleware (`app.use(fn)`) with Fastify hooks (`app.addHook`).
5. Add JSON Schema `schema` objects for request body and response where inputs are validated.
6. Always `return` the `reply.send()` call.

Migrate the following route file:

References