@fastify/express

repository·main·Indexed 42 days ago

https://github.com/fastify/fastify-express

An Express compatibility layer for Fastify (version 4.0.7) that allows developers to use Express middleware and applications within a Fastify environment. It serves as a migration tool to facilitate moving from Express to Fastify by providing the `fastify.use()` method and bridging the Fastify request/reply lifecycle with the Express middleware pattern.

Tokens
3.7K
Snippets
12
Records
16
Agent score
5%

What's inside @fastify/express

  1. How encapsulation works with @fastify/express

    main

    The plugin respects Fastify's encapsulation model. You can:

    1. Register the plugin in a subsystem: Express middleware will only be available within that specific plugin scope.
    2. Register middleware in a nested plugin: If @fastify/express is registered at the top level, middleware registered inside a nested plugin will only execute for routes within that plugin's scope.
    // Example: Registering middleware in a specific plugin scope
    const fastify = require('fastify')()
    
    fastify
      .register(require('@fastify/express'))
      .register(subsystem)
    
    async function subsystem (fastify, opts) {
      // This middleware only applies to routes inside 'subsystem'
      fastify.use(require('cors')())
    }
  2. Middleware execution order and Hooks

    main

    Every registered Express middleware runs during the Fastify onRequest hook phase. When mixing Fastify hooks and Express middleware, the order of registration determines the execution order. In the example below, the Fastify onRequest hook runs before the Express middleware.

    const fastify = require('fastify')()
    
    fastify
      .register(require('@fastify/express'))
      .register(subsystem)
    
    async function subsystem (fastify, opts) {
      fastify.addHook('onRequest', async (req, reply) => {
        console.log('first')
      })
    
      fastify.use((req, res, next) => {
        console.log('second')
        next()
      })
    
      fastify.addHook('onRequest', async (req, reply) => {
        console.log('third')
      })
    }
  3. Add a complete Express application to Fastify

    main

    You can register an entire Express Router or application instance. Because @fastify/express is Express under the hood, you must manually register Express body parsers (like express.json() or express.urlencoded()) if your Express routes depend on them.

    // index.js
    const fastify = require('fastify')()
    const express = require('express')
    const router = express.Router()
    
    router.get('/hello', (req, res) => {
      res.status(201)
      res.json({ hello: 'world' })
    })
    
    fastify.register(require('@fastify/express'))
      .after(() => {
        // Register body parsers required by Express
        fastify.use(express.urlencoded({extended: false}))
        fastify.use(express.json())
    
        fastify.use(router)
      })
    
    fastify.listen({ port: 3000 }, console.log)
  4. How @fastify/express maps Fastify to Express

    main

    The plugin works by bridging the Fastify request/reply lifecycle with the Express middleware pattern.

    1. Request Enhancement: During the configured expressHook (default onRequest), the plugin enhances req.raw (the Node.js incoming message) and reply.raw (the Fastify response wrapper). It maps Fastify properties like req.id, req.ip, req.hostname, and req.log onto the Express req.raw object so that middleware can access them.
    2. URL Normalization: The plugin normalizes the URL to ensure compatibility with Express routing, handling duplicate slashes and semicolon delimiters based on your configuration.
    3. Middleware Execution: The plugin maintains an internal list of middlewares. When the hook triggers, it executes the Express middleware stack using the this.express(req.raw, reply.raw, next) call.
    4. Response Bridging: It wraps reply.raw.send to ensure that when Express middleware calls res.send(), it correctly invokes the Fastify reply.send() method, maintaining the Fastify lifecycle.
  5. Register the @fastify/express plugin

    main

    To enable Express middleware compatibility in your Fastify application, register the @fastify/express plugin. This plugin adds the .use() method to your Fastify instance, allowing you to use standard Express middleware.

    const fastify = require('fastify')
    const fastifyExpress = require('@fastify/express')
    
    const server = fastify({ logger: true })
    
    server.register(fastifyExpress)
    
    server.listen({ port: 3000 })
  6. Troubleshoot: POST requests with body hanging

    main

    If you use both Fastify routes and Express middleware that uses body-parser, POST requests with a body may hang. This happens because of incompatibilities between body-parser and the Fastify request lifecycle.

    Solution:

    1. Remove body-parser.
    2. Install @fastify/formbody.
    3. Register @fastify/formbody before @fastify/express.
    4. Set the expressHook option to 'preHandler' in @fastify/express to ensure Express runs after the Fastify body parsing logic.
    const Fastify = require('fastify')
    const Express = require('express')
    const expressPlugin = require('@fastify/express')
    const fastifyFormBody = require('@fastify/formbody')
    
    const fastify = Fastify()
    const express = Express()
    
    await fastify.register(fastifyFormBody)
    await fastify.register(expressPlugin, {
      // run express after `@fastify/formbody` logic
      expressHook: 'preHandler'
    })
    
    fastify.use(express)
    
    // This route will now work correctly
    fastify.post('/hello', (req, reply) => {
      return { hello: 'world' }
    })
  7. Use Express middleware in Fastify

    main

    Register the @fastify/express plugin to gain access to the fastify.use() method, which allows you to use any Express middleware. You can also access fastify.express to interact with the underlying Express application (e.g., disabling features like x-powered-by).

    const Fastify = require('fastify')
    
    async function build () {
      const fastify = Fastify()
      await fastify.register(require('@fastify/express'))
      // use express middleware
      fastify.use(require('cors')())
      // access express application settings
      fastify.express.disabled('x-powered-by') // true
      return fastify
    }
    
    build()
      .then(fastify => fastify.listen({ port: 3000 }))
      .catch(console.log)
  8. Wrap Express req in a Proxy

    main

    You can wrap the Express req object in a Proxy to expose properties from the Fastify request object to the Express middleware. This is done by providing a createProxyHandler function in the plugin options. The function receives the Fastify request object as its argument.

    fastify.decorateRequest('welcomeMessage', 'Hello World');
    fastify.register(require('@fastify/express'), {
      createProxyHandler: fastifyRequest => ({
        get (target, prop) {
          if (prop === 'welcomeMessage') {
            return fastifyRequest[prop]
          }
          return target[prop]
        }
      })
    })
  9. Restrict middleware execution to specific paths

    main

    You can restrict Express middleware to specific paths by passing the path as the first argument to fastify.use(). Supported formats include single strings, arrays of strings, and wildcards.

    Note on Prefixed Plugins: In prefixed plugins, @fastify/express rewrites paths to include the prefix. However, RegExp paths with ^ anchors inside groups (e.g., /(?:^\/a|^\/b)/) are not rewritten correctly. It is recommended to use arrays for multiple paths instead.

    const fastify = require('fastify')()
    const path = require('node:path')
    const serveStatic = require('serve-static')
    
    fastify
      .register(require('@fastify/express'))
      .register(subsystem)
    
    async function subsystem (fastify, opts) {
      // Single path
      fastify.use('/css', serveStatic(path.join(__dirname, '/assets')))
    
      // Wildcard path
      fastify.use('/css/*', serveStatic(path.join(__dirname, '/assets')))
    
      // Multiple paths (Recommended over RegExp for prefixed plugins)
      fastify.use(['/css', '/js'], serveStatic(path.join(__dirname, '/assets')))
    }
  10. Configure @fastify/express options

    main

    When registering the @fastify/express plugin, you can pass an options object to customize its behavior:

    • expressHook (string): The Fastify hook used to trigger the middleware execution. Defaults to 'onRequest'.
    • createProxyHandler (function): A function that returns a Proxy handler for the Express req.raw object. This allows you to intercept or modify how the raw request is accessed.

    Additionally, the plugin respects routerOptions from the main Fastify configuration to control URL normalization:

    • ignoreDuplicateSlashes (boolean): If true, removes duplicate slashes from the URL.
    • useSemicolonDelimiter (boolean): If true, treats semicolons in the URL as delimiters for path parameters (Express-style).
    server.register(fastifyExpress, {
      expressHook: 'onRequest',
      createProxyHandler: (req) => {
        return (target, prop) => {
          // Custom proxy logic here
          return target[prop]
        }
      }
    })