├── .gitattributes ├── .github ├── dependabot.yml ├── stale.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── eslint.config.js ├── index.js ├── package.json ├── test ├── 404s.test.js ├── application.test.js ├── basic.test.js ├── enhance-request.test.js ├── form-data.test.js ├── hooks.test.js └── middleware.test.js └── types ├── index.d.ts └── index.test-d.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically convert line endings 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "monthly" 13 | open-pull-requests-limit: 10 14 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 15 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - "discussion" 8 | - "feature request" 9 | - "bug" 10 | - "help wanted" 11 | - "plugin suggestion" 12 | - "good first issue" 13 | # Label to use when marking an issue as stale 14 | staleLabel: stale 15 | # Comment to post when marking an issue as stale. Set to `false` to disable 16 | markComment: > 17 | This issue has been automatically marked as stale because it has not had 18 | recent activity. It will be closed if no further activity occurs. Thank you 19 | for your contributions. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: false 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | - 'v*' 9 | paths-ignore: 10 | - 'docs/**' 11 | - '*.md' 12 | pull_request: 13 | paths-ignore: 14 | - 'docs/**' 15 | - '*.md' 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | test: 22 | permissions: 23 | contents: write 24 | pull-requests: write 25 | uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5 26 | with: 27 | license-check: true 28 | lint: true 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # Vim swap files 133 | *.swp 134 | 135 | # macOS files 136 | .DS_Store 137 | 138 | # Clinic 139 | .clinic 140 | 141 | # lock files 142 | bun.lockb 143 | package-lock.json 144 | pnpm-lock.yaml 145 | yarn.lock 146 | 147 | # editor files 148 | .vscode 149 | .idea 150 | 151 | #tap files 152 | .tap/ 153 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fastify 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @fastify/express 2 | 3 | [![CI](https://github.com/fastify/fastify-express/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify-express/actions/workflows/ci.yml) 4 | [![NPM version](https://img.shields.io/npm/v/@fastify/express.svg?style=flat)](https://www.npmjs.com/package/@fastify/express) 5 | [![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) 6 | 7 | This plugin adds full [Express](http://expressjs.com) compatibility to Fastify, it exposes the same `use` function of Express, and it allows you to use any Express middleware or application.
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
NoteThis plugin should not be used as a long-term solution, it aims to help you have a smooth transition from Express to Fastify, but you should migrate your Express specific code to Fastify over time.
Since Express does not support Node.js core HTTP/2 module, this plugin does not support HTTP/2 either.
20 | 21 | ## Install 22 | ``` 23 | npm i @fastify/express 24 | ``` 25 | 26 | ### Compatibility 27 | | Plugin version | Fastify version | 28 | | ---------------|-----------------| 29 | | `>=4.x` | `^5.x` | 30 | | `>=2.x <4.x` | `^4.x` | 31 | | `^1.x` | `^3.x` | 32 | | `^0.x` | `^2.x` | 33 | 34 | 35 | Please note that if a Fastify version is out of support, then so are the corresponding versions of this plugin 36 | in the table above. 37 | See [Fastify's LTS policy](https://github.com/fastify/fastify/blob/main/docs/Reference/LTS.md) for more details. 38 | 39 | ## Usage 40 | Register the plugin and start using your Express middlewares. 41 | ```js 42 | const Fastify = require('fastify') 43 | 44 | async function build () { 45 | const fastify = Fastify() 46 | await fastify.register(require('@fastify/express')) 47 | // do you know we also have cors support? 48 | // https://github.com/fastify/fastify-cors 49 | fastify.use(require('cors')()) 50 | // express.Application is also accessible 51 | fastify.express.disabled('x-powered-by') // true 52 | return fastify 53 | } 54 | 55 | build() 56 | .then(fastify => fastify.listen({ port: 3000 })) 57 | .catch(console.log) 58 | ``` 59 | 60 | ### Add a complete application 61 | 62 | You can register an entire Express application and make it work with Fastify. Remember, `@fastify/express` is just `express` under the covers and requires the same body parsers as you'd use in `express`. 63 | 64 | ```js 65 | // index.js 66 | const fastify = require('fastify')() 67 | const express = require('express') 68 | const router = express.Router() 69 | 70 | router.use(function (req, res, next) { 71 | res.setHeader('x-custom', true) 72 | next() 73 | }) 74 | 75 | router.get('/hello', (req, res) => { 76 | res.status(201) 77 | res.json({ hello: 'world' }) 78 | }) 79 | 80 | router.get('/foo', (req, res) => { 81 | res.status(400) 82 | res.json({ foo: 'bar' }) 83 | }) 84 | 85 | router.patch('/bar', (req, res) => { 86 | if (!req.body || Object.keys(req.body).length === 0) { 87 | res.status(400) 88 | res.json({ msg: 'no req.body'}) 89 | } else { 90 | res.status(200) 91 | res.json(req.body) 92 | } 93 | }) 94 | 95 | router.use('*', (req, res) => { 96 | res.status(404) 97 | res.json({ msg: 'not found'}) 98 | }) 99 | 100 | fastify.register(require('@fastify/express')) 101 | .after(() => { 102 | fastify.use(express.urlencoded({extended: false})) // for Postman x-www-form-urlencoded 103 | fastify.use(express.json()) 104 | 105 | fastify.use(router) 106 | }) 107 | 108 | fastify.listen({ port: 3000 }, console.log) 109 | ``` 110 | 111 | #### Testing Your App 112 | Run `node index.js` to start your server. Then run the following commands to ensure your server is working. Use the optional `-v` flag in curl for verbose output. 113 | 114 | ```bash 115 | me@computer ~ % curl -X GET http://localhost:3000/hello 116 | {"hello":"world"}% 117 | me@computer ~ % curl -X GET http://localhost:3000/foo 118 | {"foo":"bar"}% 119 | me@computer ~ % curl -X GET http://localhost:3000/bar 120 | {"msg":"not found"}% 121 | me@computer ~ % curl -X PATCH -H 'content-type:application/json' http://localhost:3000/bar 122 | {"msg":"no req.body"}% 123 | me@computer ~ % curl -X PATCH -H 'content-type:application/json' -d '{"foo2":"bar2"}' http://localhost:3000/bar 124 | {"foo2":"bar2"}% 125 | ``` 126 | 127 | ### Encapsulation support 128 | 129 | The encapsulation works as usual with Fastify, you can register the plugin in a subsystem and your express code will work only inside there, or you can declare the express plugin top level and register a middleware in a nested plugin, and the middleware will be executed only for the nested routes of the specific plugin. 130 | 131 | *Register the plugin in its own subsystem:* 132 | ```js 133 | const fastify = require('fastify')() 134 | 135 | fastify.register(subsystem) 136 | 137 | async function subsystem (fastify, opts) { 138 | await fastify.register(require('@fastify/express')) 139 | fastify.use(require('cors')()) 140 | } 141 | ``` 142 | 143 | *Register a middleware in a specific plugin:* 144 | ```js 145 | const fastify = require('fastify')() 146 | 147 | fastify 148 | .register(require('@fastify/express')) 149 | .register(subsystem) 150 | 151 | async function subsystem (fastify, opts) { 152 | fastify.use(require('cors')()) 153 | } 154 | ``` 155 | 156 | ### Hooks and middleware 157 | 158 | Every registered middleware will be run during the `onRequest` hook phase, so the registration order is important. 159 | Take a look at the [Lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle) documentation page to understand better how every request is executed. 160 | 161 | ```js 162 | const fastify = require('fastify')() 163 | 164 | fastify 165 | .register(require('@fastify/express')) 166 | .register(subsystem) 167 | 168 | async function subsystem (fastify, opts) { 169 | fastify.addHook('onRequest', async (req, reply) => { 170 | console.log('first') 171 | }) 172 | 173 | fastify.use((req, res, next) => { 174 | console.log('second') 175 | next() 176 | }) 177 | 178 | fastify.addHook('onRequest', async (req, reply) => { 179 | console.log('third') 180 | }) 181 | } 182 | ``` 183 | 184 | ### Restrict middleware execution to a certain path(s) 185 | 186 | If you need to run a middleware only under certain path(s), just pass the path as the first parameter to use and you are done! 187 | 188 | ```js 189 | const fastify = require('fastify')() 190 | const path = require('node:path') 191 | const serveStatic = require('serve-static') 192 | 193 | fastify 194 | .register(require('@fastify/express')) 195 | .register(subsystem) 196 | 197 | async function subsystem (fastify, opts) { 198 | // Single path 199 | fastify.use('/css', serveStatic(path.join(__dirname, '/assets'))) 200 | 201 | // Wildcard path 202 | fastify.use('/css/*', serveStatic(path.join(__dirname, '/assets'))) 203 | 204 | // Multiple paths 205 | fastify.use(['/css', '/js'], serveStatic(path.join(__dirname, '/assets'))) 206 | } 207 | ``` 208 | 209 | ### Wrap Express req in Proxy 210 | 211 | It is possible to wrap the Express request object in a Proxy by passing `createProxyHandler` function to generate the Proxy handler. The function will receive the Fastify request object as the first parameter. 212 | 213 | For example, using Proxy to expose something from Fastify request into the Express request. 214 | 215 | ```js 216 | fastify.decorateRequest('welcomeMessage', 'Hello World'); 217 | fastify.register(expressPlugin, { 218 | createProxyHandler: fastifyRequest => ({ 219 | get (target, prop) { 220 | if (prop === 'welcomeMessage') { 221 | return fastifyRequest[prop] 222 | } 223 | 224 | return target[prop] 225 | } 226 | }) 227 | }) 228 | ``` 229 | 230 | ## TypeScript support 231 | 232 | To use this module with TypeScript, make sure to install `@types/express`. 233 | 234 | You will need to add `"types": ["@fastify/express"]` to your tsconfig.json file when using `require` to import the plugin. 235 | 236 | ## Middleware alternatives 237 | 238 | Fastify offers some alternatives to the most commonly used middleware: 239 | 240 | | Express Middleware | Fastify Plugin | 241 | | ------------- |---------------| 242 | | [`helmet`](https://github.com/helmetjs/helmet) | [`@fastify/helmet`](https://github.com/fastify/fastify-helmet) | 243 | | [`cors`](https://github.com/expressjs/cors) | [`@fastify/cors`](https://github.com/fastify/fastify-cors) | 244 | | [`serve-static`](https://github.com/expressjs/serve-static) | [`@fastify/static`](https://github.com/fastify/fastify-static) | 245 | 246 | ## Troubleshooting 247 | 248 | ### POST request with body hangs up 249 | 250 | [body-parser](https://github.com/expressjs/body-parser) library incompatible with `fastify-express`, when you have `fastify` routes and any `express` middlewares. 251 | Any POST requests with **body**, which `body-parser` will try to parse, will hang up. 252 | 253 | Example application: 254 | 255 | ```js 256 | const Fastify = require('fastify') 257 | const Express = require('express') 258 | const expressPlugin = require('@fastify/express') 259 | const bodyParser = require('body-parser') 260 | 261 | const fastify = Fastify() 262 | const express = Express() 263 | 264 | express.use(bodyParser.urlencoded({ extended: false })) 265 | 266 | await fastify.register(expressPlugin) 267 | 268 | fastify.use(express) 269 | 270 | // this route will never reply 271 | fastify.post('/hello', (req, reply) => { 272 | return { hello: 'world' } 273 | }) 274 | ``` 275 | 276 | For this case, you need to remove `body-parser`, install `@fastify/formbody` and change `@fastify/express` options: 277 | 278 | 279 | ```js 280 | const Fastify = require('fastify') 281 | const Express = require('express') 282 | const expressPlugin = require('@fastify/express') 283 | const fastifyFormBody = require('@fastify/formbody') 284 | 285 | const fastify = Fastify() 286 | const express = Express() 287 | 288 | await fastify.register(fastifyFormBody) 289 | await fastify.register(expressPlugin, { 290 | // run express after `@fastify/formbody` logic 291 | expressHook: 'preHandler' 292 | }) 293 | 294 | fastify.use(express) 295 | 296 | // it works! 297 | fastify.post('/hello', (req, reply) => { 298 | return { hello: 'world' } 299 | }) 300 | ``` 301 | 302 | ## License 303 | 304 | Licensed under [MIT](./LICENSE).
305 | [`express` license](https://github.com/expressjs/express/blob/master/LICENSE) 306 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = require('neostandard')({ 4 | ignores: require('neostandard').resolveIgnoresFromGitignore(), 5 | ts: true 6 | }) 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const Express = require('express') 5 | const kMiddlewares = Symbol('fastify-express-middlewares') 6 | 7 | function fastifyExpress (fastify, options, next) { 8 | const { 9 | expressHook = 'onRequest', 10 | createProxyHandler 11 | } = options 12 | 13 | fastify.decorate('use', use) 14 | fastify[kMiddlewares] = [] 15 | fastify.decorate('express', Express()) 16 | fastify.express.disable('x-powered-by') 17 | 18 | fastify 19 | .addHook(expressHook, enhanceRequest) 20 | .addHook(expressHook, runConnect) 21 | .addHook('onRegister', onRegister) 22 | 23 | function use (path, fn) { 24 | if (typeof path === 'string') { 25 | const prefix = this.prefix 26 | path = prefix + (path === '/' && prefix.length > 0 ? '' : path) 27 | } 28 | this[kMiddlewares].push([path, fn]) 29 | if (fn == null) { 30 | this.express.use(path) 31 | } else { 32 | this.express.use(path, fn) 33 | } 34 | return this 35 | } 36 | 37 | function enhanceRequest (req, reply, next) { 38 | // Allow attaching custom Proxy handlers to Express req 39 | if (typeof createProxyHandler === 'function') { 40 | req.raw = new Proxy(req.raw, createProxyHandler(req)) 41 | } 42 | 43 | const { url } = req.raw 44 | req.raw.originalUrl = url 45 | req.raw.id = req.id 46 | req.raw.hostname = req.hostname 47 | req.raw.ip = req.ip 48 | req.raw.ips = req.ips 49 | req.raw.log = req.log 50 | reply.raw.log = req.log 51 | reply.raw.send = function send (...args) { 52 | // Restore req.raw.url to its original value https://github.com/fastify/fastify-express/issues/11 53 | req.raw.url = url 54 | return reply.send.apply(reply, args) 55 | } 56 | 57 | // backward compatibility for body-parser 58 | if (req.body) { 59 | req.raw.body = req.body 60 | } 61 | // backward compatibility for cookie-parser 62 | /* c8 ignore next 3 */ 63 | if (req.cookies) { 64 | req.raw.cookies = req.cookies 65 | } 66 | 67 | // Make it lazy as it does a bit of work 68 | Object.defineProperty(req.raw, 'protocol', { 69 | get () { 70 | return req.protocol 71 | } 72 | }) 73 | 74 | next() 75 | } 76 | 77 | function runConnect (req, reply, next) { 78 | if (this[kMiddlewares].length > 0) { 79 | for (const [headerName, headerValue] of Object.entries(reply.getHeaders())) { 80 | reply.raw.setHeader(headerName, headerValue) 81 | } 82 | 83 | this.express(req.raw, reply.raw, next) 84 | } else { 85 | next() 86 | } 87 | } 88 | 89 | function onRegister (instance) { 90 | const middlewares = instance[kMiddlewares].slice() 91 | instance[kMiddlewares] = [] 92 | instance.decorate('express', Express()) 93 | instance.express.disable('x-powered-by') 94 | instance.decorate('use', use) 95 | for (const middleware of middlewares) { 96 | instance.use(...middleware) 97 | } 98 | } 99 | 100 | next() 101 | } 102 | 103 | module.exports = fp(fastifyExpress, { 104 | fastify: '5.x', 105 | name: '@fastify/express' 106 | }) 107 | module.exports.default = fastifyExpress 108 | module.exports.fastifyExpress = fastifyExpress 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fastify/express", 3 | "version": "4.0.2", 4 | "description": "Express compatibility layer for Fastify", 5 | "main": "index.js", 6 | "type": "commonjs", 7 | "types": "types/index.d.ts", 8 | "scripts": { 9 | "lint": "eslint", 10 | "lint:fix": "eslint --fix", 11 | "test": "npm run test:unit && npm run test:typescript", 12 | "test:unit": "c8 --100 --reporter html node --test", 13 | "test:typescript": "tsd" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/fastify/fastify-express.git" 18 | }, 19 | "keywords": [ 20 | "fastify", 21 | "express", 22 | "middlewares", 23 | "use", 24 | "compatibility" 25 | ], 26 | "author": "Tomas Della Vedova - @delvedor (http://delved.org)", 27 | "contributors": [ 28 | { 29 | "name": "Matteo Collina", 30 | "email": "hello@matteocollina.com" 31 | }, 32 | { 33 | "name": "Manuel Spigolon", 34 | "email": "behemoth89@gmail.com" 35 | }, 36 | { 37 | "name": "James Sumners", 38 | "url": "https://james.sumners.info" 39 | }, 40 | { 41 | "name": "Frazer Smith", 42 | "email": "frazer.dev@icloud.com", 43 | "url": "https://github.com/fdawgs" 44 | } 45 | ], 46 | "license": "MIT", 47 | "bugs": { 48 | "url": "https://github.com/fastify/fastify-express/issues" 49 | }, 50 | "homepage": "https://github.com/fastify/fastify-express#readme", 51 | "funding": [ 52 | { 53 | "type": "github", 54 | "url": "https://github.com/sponsors/fastify" 55 | }, 56 | { 57 | "type": "opencollective", 58 | "url": "https://opencollective.com/fastify" 59 | } 60 | ], 61 | "devDependencies": { 62 | "@fastify/formbody": "^8.0.0", 63 | "@types/express": "^5.0.0", 64 | "@types/node": "*", 65 | "body-parser": "^1.20.2", 66 | "c8": "^10.1.3", 67 | "cors": "^2.8.5", 68 | "eslint": "^9.17.0", 69 | "fastify": "^5.0.0", 70 | "helmet": "^8.0.0", 71 | "neostandard": "^0.12.0", 72 | "passport": "^0.7.0", 73 | "passport-http-bearer": "^1.0.1", 74 | "serve-static": "^2.2.0", 75 | "tsd": "^0.32.0" 76 | }, 77 | "dependencies": { 78 | "express": "^5.1.0", 79 | "fastify-plugin": "^5.0.0" 80 | }, 81 | "tsd": { 82 | "directory": "test/types" 83 | }, 84 | "publishConfig": { 85 | "access": "public" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /test/404s.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const fp = require('fastify-plugin') 5 | const Fastify = require('fastify') 6 | const expressPlugin = require('../index') 7 | 8 | test('run hooks and middleware on default 404', async t => { 9 | t.plan(6) 10 | 11 | const fastify = Fastify() 12 | 13 | fastify 14 | .register(expressPlugin) 15 | .after(() => { 16 | fastify.use(function (_req, _res, next) { 17 | t.assert.ok('middleware called') 18 | next() 19 | }) 20 | }) 21 | 22 | fastify.addHook('onRequest', function (_req, _res, next) { 23 | t.assert.ok('onRequest called') 24 | next() 25 | }) 26 | 27 | fastify.addHook('preHandler', function (_request, _reply, next) { 28 | t.assert.ok('preHandler called') 29 | next() 30 | }) 31 | 32 | fastify.addHook('onSend', function (_request, _reply, _payload, next) { 33 | t.assert.ok('onSend called') 34 | next() 35 | }) 36 | 37 | fastify.addHook('onResponse', function (_request, _reply, next) { 38 | t.assert.ok('onResponse called') 39 | next() 40 | }) 41 | 42 | fastify.get('/', function (_req, reply) { 43 | reply.send({ hello: 'world' }) 44 | }) 45 | 46 | t.after(() => fastify.close()) 47 | 48 | const address = await fastify.listen({ port: 0 }) 49 | 50 | const result = await fetch(address, { 51 | method: 'PUT', 52 | body: JSON.stringify({ hello: 'world' }), 53 | }) 54 | 55 | t.assert.deepStrictEqual(result.status, 404) 56 | }) 57 | 58 | test('run non-encapsulated plugin hooks and middleware on default 404', async t => { 59 | t.plan(6) 60 | 61 | const fastify = Fastify() 62 | t.after(() => fastify.close()) 63 | fastify.register(expressPlugin) 64 | 65 | fastify.register(fp(function (instance, _options, next) { 66 | instance.addHook('onRequest', function (_req, _res, next) { 67 | t.assert.ok('onRequest called') 68 | next() 69 | }) 70 | 71 | instance.use(function (_req, _res, next) { 72 | t.assert.ok('middleware called') 73 | next() 74 | }) 75 | 76 | instance.addHook('preHandler', function (_request, _reply, next) { 77 | t.assert.ok('preHandler called') 78 | next() 79 | }) 80 | 81 | instance.addHook('onSend', function (_request, _reply, _payload, next) { 82 | t.assert.ok('onSend called') 83 | next() 84 | }) 85 | 86 | instance.addHook('onResponse', function (_request, _reply, next) { 87 | t.assert.ok('onResponse called') 88 | next() 89 | }) 90 | 91 | next() 92 | })) 93 | 94 | fastify.get('/', function (_req, reply) { 95 | reply.send({ hello: 'world' }) 96 | }) 97 | 98 | const address = await fastify.listen({ port: 0 }) 99 | 100 | const result = await fetch(address, { 101 | method: 'POST', 102 | body: JSON.stringify({ hello: 'world' }), 103 | }) 104 | t.assert.deepStrictEqual(result.status, 404) 105 | }) 106 | 107 | test('run non-encapsulated plugin hooks and middleware on custom 404', async t => { 108 | t.plan(12) 109 | 110 | const fastify = Fastify() 111 | t.after(() => fastify.close()) 112 | fastify.register(expressPlugin) 113 | 114 | const plugin = fp((instance, _opts, next) => { 115 | instance.addHook('onRequest', function (_req, _res, next) { 116 | t.assert.ok('onRequest called') 117 | next() 118 | }) 119 | 120 | instance.use(function (_req, _res, next) { 121 | t.assert.ok('middleware called') 122 | next() 123 | }) 124 | 125 | instance.addHook('preHandler', function (_request, _reply, next) { 126 | t.assert.ok('preHandler called') 127 | next() 128 | }) 129 | 130 | instance.addHook('onSend', function (_request, _reply, _payload, next) { 131 | t.assert.ok('onSend called') 132 | next() 133 | }) 134 | 135 | instance.addHook('onResponse', function (_request, _reply, next) { 136 | t.assert.ok('onResponse called') 137 | next() 138 | }) 139 | 140 | next() 141 | }) 142 | 143 | fastify.register(plugin) 144 | 145 | fastify.get('/', function (_req, reply) { 146 | reply.send({ hello: 'world' }) 147 | }) 148 | 149 | fastify.setNotFoundHandler(function (_req, reply) { 150 | reply.code(404).send('this was not found') 151 | }) 152 | 153 | fastify.register(plugin) // Registering plugin after handler also works 154 | 155 | const address = await fastify.listen({ port: 0 }) 156 | 157 | const result = await fetch(address + '/not-found') 158 | 159 | t.assert.deepStrictEqual(await result.text(), 'this was not found') 160 | t.assert.deepStrictEqual(result.status, 404) 161 | }) 162 | 163 | test('run hooks and middleware with encapsulated 404', async t => { 164 | t.plan(11) 165 | 166 | const fastify = Fastify() 167 | 168 | fastify 169 | .register(expressPlugin) 170 | .after(() => { 171 | fastify.use(function (_req, _res, next) { 172 | t.assert.ok('middleware called') 173 | next() 174 | }) 175 | }) 176 | 177 | fastify.addHook('onRequest', function (_req, _res, next) { 178 | t.assert.ok('onRequest called') 179 | next() 180 | }) 181 | 182 | fastify.addHook('preHandler', function (_request, _reply, next) { 183 | t.assert.ok('preHandler called') 184 | next() 185 | }) 186 | 187 | fastify.addHook('onSend', function (_request, _reply, _payload, next) { 188 | t.assert.ok('onSend called') 189 | next() 190 | }) 191 | 192 | fastify.addHook('onResponse', function (_request, _reply, next) { 193 | t.assert.ok('onResponse called') 194 | next() 195 | }) 196 | 197 | fastify.register(function (f, _opts, next) { 198 | f.setNotFoundHandler(function (_req, reply) { 199 | reply.code(404).send('this was not found 2') 200 | }) 201 | 202 | f.addHook('onRequest', function (_req, _res, next) { 203 | t.assert.ok('onRequest 2 called') 204 | next() 205 | }) 206 | 207 | f.use(function (_req, _res, next) { 208 | t.assert.ok('middleware 2 called') 209 | next() 210 | }) 211 | 212 | f.addHook('preHandler', function (_request, _reply, next) { 213 | t.assert.ok('preHandler 2 called') 214 | next() 215 | }) 216 | 217 | f.addHook('onSend', function (_request, _reply, _payload, next) { 218 | t.assert.ok('onSend 2 called') 219 | next() 220 | }) 221 | 222 | f.addHook('onResponse', function (_request, _reply, next) { 223 | t.assert.ok('onResponse 2 called') 224 | next() 225 | }) 226 | 227 | next() 228 | }, { prefix: '/test' }) 229 | 230 | t.after(() => fastify.close()) 231 | 232 | const address = await fastify.listen({ port: 0 }) 233 | 234 | const result = await fetch(address + '/test', { 235 | method: 'PUT', 236 | body: JSON.stringify({ hello: 'world' }), 237 | }) 238 | 239 | t.assert.deepStrictEqual(result.status, 404) 240 | }) 241 | 242 | test('run middlewares on default 404', async t => { 243 | t.plan(2) 244 | 245 | const fastify = Fastify() 246 | fastify 247 | .register(expressPlugin) 248 | .after(() => { 249 | fastify.use(function (_req, _res, next) { 250 | t.assert.ok('middleware called') 251 | next() 252 | }) 253 | }) 254 | 255 | fastify.get('/', function (_req, reply) { 256 | reply.send({ hello: 'world' }) 257 | }) 258 | 259 | t.after(() => fastify.close()) 260 | 261 | const address = await fastify.listen({ port: 0 }) 262 | 263 | const result = await fetch(address, { 264 | method: 'PUT', 265 | body: JSON.stringify({ hello: 'world' }), 266 | }) 267 | 268 | t.assert.deepStrictEqual(result.status, 404) 269 | }) 270 | 271 | test('run middlewares with encapsulated 404', async t => { 272 | t.plan(3) 273 | 274 | const fastify = Fastify() 275 | fastify 276 | .register(expressPlugin) 277 | .after(() => { 278 | fastify.use(function (_req, _res, next) { 279 | t.assert.ok('middleware called') 280 | next() 281 | }) 282 | }) 283 | 284 | fastify.register(function (f, _opts, next) { 285 | f.setNotFoundHandler(function (_req, reply) { 286 | reply.code(404).send('this was not found 2') 287 | }) 288 | 289 | f.use(function (_req, _res, next) { 290 | t.assert.ok('middleware 2 called') 291 | next() 292 | }) 293 | 294 | next() 295 | }, { prefix: '/test' }) 296 | 297 | t.after(() => fastify.close()) 298 | 299 | const address = await fastify.listen({ port: 0 }) 300 | 301 | const result = await fetch(address + '/test', { 302 | method: 'PUT', 303 | body: JSON.stringify({ hello: 'world' }), 304 | }) 305 | 306 | t.assert.deepStrictEqual(result.status, 404) 307 | }) 308 | -------------------------------------------------------------------------------- /test/application.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const Fastify = require('fastify') 5 | const Express = require('express') 6 | 7 | const expressPlugin = require('../index') 8 | 9 | test('Register express application', async t => { 10 | t.plan(3) 11 | const fastify = Fastify() 12 | const express = Express() 13 | t.after(() => fastify.close()) 14 | 15 | express.use(function (_req, res, next) { 16 | res.setHeader('x-custom', true) 17 | next() 18 | }) 19 | 20 | express.get('/hello', (_req, res) => { 21 | res.status(201) 22 | res.json({ hello: 'world' }) 23 | }) 24 | 25 | fastify.register(expressPlugin) 26 | .after(() => { fastify.use(express) }) 27 | 28 | const address = await fastify.listen({ port: 0 }) 29 | 30 | const result = await fetch(address + '/hello') 31 | 32 | t.assert.deepStrictEqual(result.status, 201) 33 | t.assert.deepStrictEqual(result.headers.get('x-custom'), 'true') 34 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 35 | }) 36 | 37 | test('Register express application that uses Router', async t => { 38 | t.plan(6) 39 | const fastify = Fastify() 40 | t.after(() => fastify.close()) 41 | 42 | const router = Express.Router() 43 | 44 | router.use(function (_req, res, next) { 45 | res.setHeader('x-custom', true) 46 | next() 47 | }) 48 | 49 | router.get('/hello', (_req, res) => { 50 | res.status(201) 51 | res.json({ hello: 'world' }) 52 | }) 53 | 54 | router.get('/foo', (_req, res) => { 55 | res.status(400) 56 | res.json({ foo: 'bar' }) 57 | }) 58 | 59 | fastify.register(expressPlugin) 60 | .after(() => { fastify.use(router) }) 61 | 62 | const address = await fastify.listen({ port: 0 }) 63 | 64 | const result = await fetch(address + '/hello') 65 | 66 | t.assert.deepStrictEqual(result.status, 201) 67 | t.assert.deepStrictEqual(result.headers.get('x-custom'), 'true') 68 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 69 | 70 | const result2 = await fetch(address + '/foo') 71 | 72 | t.assert.deepStrictEqual(result2.status, 400) 73 | t.assert.deepStrictEqual(result2.headers.get('x-custom'), 'true') 74 | t.assert.deepStrictEqual(await result2.json(), { foo: 'bar' }) 75 | }) 76 | 77 | test('Should remove x-powered-by header', async t => { 78 | t.plan(1) 79 | const fastify = Fastify() 80 | t.after(() => fastify.close()) 81 | 82 | const router = Express.Router() 83 | 84 | router.get('/', (_req, res) => { 85 | res.status(201) 86 | res.json({ hello: 'world' }) 87 | }) 88 | 89 | fastify 90 | .register(expressPlugin) 91 | .after(() => { fastify.use(router) }) 92 | 93 | const address = await fastify.listen({ port: 0 }) 94 | 95 | const result = await fetch(address) 96 | t.assert.deepStrictEqual(result.headers.get('x-powered-by'), null) 97 | }) 98 | 99 | test('Should expose the express app on the fastify instance', async t => { 100 | t.plan(1) 101 | const fastify = Fastify() 102 | t.after(() => fastify.close()) 103 | 104 | const router = Express.Router() 105 | 106 | router.get('/', (_req, res) => { 107 | res.status(201) 108 | res.json({ hello: 'world' }) 109 | }) 110 | 111 | fastify 112 | .register(expressPlugin) 113 | .after(() => { fastify.use(router) }) 114 | 115 | const address = await fastify.listen({ port: 0 }) 116 | 117 | await fetch(address) 118 | t.assert.deepStrictEqual(fastify.express.disabled('x-powered-by'), true) 119 | }) 120 | 121 | test('Should flush headers if express handles request', async t => { 122 | t.plan(1) 123 | const fastify = Fastify() 124 | t.after(() => fastify.close()) 125 | 126 | fastify.addHook('onRequest', (_, reply, done) => { 127 | reply.header('foo', 'bar') 128 | 129 | done() 130 | }) 131 | 132 | const router = Express.Router() 133 | 134 | router.get('/', (_req, res) => { 135 | res.status(201) 136 | res.json({ hello: 'world' }) 137 | }) 138 | 139 | fastify 140 | .register(expressPlugin) 141 | .after(() => { fastify.use(router) }) 142 | 143 | const address = await fastify.listen({ port: 0 }) 144 | 145 | const result = await fetch(address) 146 | t.assert.deepStrictEqual(result.headers.get('foo'), 'bar') 147 | }) 148 | -------------------------------------------------------------------------------- /test/basic.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const Fastify = require('fastify') 5 | const cors = require('cors') 6 | const passport = require('passport') 7 | const Strategy = require('passport-http-bearer').Strategy 8 | 9 | const expressPlugin = require('../index') 10 | 11 | test('Should support connect style middlewares', async t => { 12 | t.plan(2) 13 | const fastify = Fastify() 14 | t.after(() => fastify.close()) 15 | 16 | fastify 17 | .register(expressPlugin) 18 | .after(() => { fastify.use(cors()) }) 19 | 20 | fastify.get('/', async () => { 21 | return { hello: 'world' } 22 | }) 23 | 24 | const address = await fastify.listen({ port: 0 }) 25 | 26 | const result = await fetch(address) 27 | t.assert.deepStrictEqual(result.headers.get('access-control-allow-origin'), '*') 28 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 29 | }) 30 | 31 | test('Should support connect style middlewares (async await)', async t => { 32 | t.plan(2) 33 | const fastify = Fastify() 34 | t.after(() => fastify.close()) 35 | 36 | await fastify.register(expressPlugin) 37 | fastify.use(cors()) 38 | 39 | fastify.get('/', async () => { 40 | return { hello: 'world' } 41 | }) 42 | 43 | const address = await fastify.listen({ port: 0 }) 44 | 45 | const result = await fetch(address) 46 | t.assert.deepStrictEqual(result.headers.get('access-control-allow-origin'), '*') 47 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 48 | }) 49 | 50 | test('Should support connect style middlewares (async await after)', async t => { 51 | t.plan(2) 52 | const fastify = Fastify() 53 | t.after(() => fastify.close()) 54 | 55 | fastify.register(expressPlugin) 56 | await fastify.after() 57 | fastify.use(cors()) 58 | 59 | fastify.get('/', async () => { 60 | return { hello: 'world' } 61 | }) 62 | 63 | const address = await fastify.listen({ port: 0 }) 64 | const result = await fetch(address) 65 | t.assert.deepStrictEqual(result.headers.get('access-control-allow-origin'), '*') 66 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 67 | }) 68 | 69 | test('Should support per path middlewares', async t => { 70 | t.plan(2) 71 | const fastify = Fastify() 72 | t.after(() => fastify.close()) 73 | 74 | fastify 75 | .register(expressPlugin) 76 | .after(() => { fastify.use('/cors', cors()) }) 77 | 78 | fastify.get('/cors/hello', async () => { 79 | return { hello: 'world' } 80 | }) 81 | 82 | fastify.get('/', async () => { 83 | return { hello: 'world' } 84 | }) 85 | 86 | const address = await fastify.listen({ port: 0 }) 87 | 88 | const result = await fetch(address + '/cors/hello') 89 | t.assert.deepStrictEqual(result.headers.get('access-control-allow-origin'), '*') 90 | 91 | const result2 = await fetch(address) 92 | t.assert.deepStrictEqual(result2.headers.get('access-control-allow-origin'), null) 93 | }) 94 | 95 | test('Should support complex middlewares', async t => { 96 | t.plan(3) 97 | 98 | const fastify = Fastify() 99 | 100 | passport.use(new Strategy((token, cb) => { 101 | t.assert.deepStrictEqual(token, '123456789') 102 | cb(null, { token }) 103 | })) 104 | 105 | t.after(() => fastify.close()) 106 | 107 | fastify 108 | .register(expressPlugin) 109 | .after(() => { fastify.use(passport.authenticate('bearer', { session: false })) }) 110 | 111 | fastify 112 | .get('/', (req, reply) => { 113 | t.assert.deepStrictEqual(req.raw.user, { token: '123456789' }) 114 | reply.send('ok') 115 | }) 116 | 117 | const address = await fastify.listen({ port: 0 }) 118 | 119 | const result = await fetch(address, { 120 | headers: { 121 | authorization: 'Bearer 123456789' 122 | } 123 | }) 124 | 125 | t.assert.ok(result.ok) 126 | }) 127 | 128 | test('Encapsulation support / 1', async t => { 129 | t.plan(1) 130 | 131 | const fastify = Fastify() 132 | 133 | t.after(() => fastify.close()) 134 | 135 | fastify.register((instance, _opts, next) => { 136 | instance.register(expressPlugin) 137 | .after(() => { instance.use(middleware) }) 138 | 139 | instance.get('/plugin', (_req, reply) => { 140 | reply.send('ok') 141 | }) 142 | 143 | next() 144 | }) 145 | 146 | fastify.get('/', (_req, reply) => { 147 | reply.send('ok') 148 | }) 149 | 150 | const address = await fastify.listen({ port: 0 }) 151 | 152 | const result = await fetch(address) 153 | t.assert.ok(result.ok) 154 | 155 | function middleware () { 156 | t.assert.fail('Shuld not be called') 157 | } 158 | }) 159 | 160 | test('Encapsulation support / 2', async t => { 161 | t.plan(1) 162 | 163 | const fastify = Fastify() 164 | 165 | t.after(() => fastify.close()) 166 | 167 | fastify.register(expressPlugin) 168 | 169 | fastify.register((instance, _opts, next) => { 170 | instance.use(middleware) 171 | instance.get('/plugin', (_req, reply) => { 172 | reply.send('ok') 173 | }) 174 | 175 | next() 176 | }) 177 | 178 | fastify.get('/', (_req, reply) => { 179 | reply.send('ok') 180 | }) 181 | 182 | const address = await fastify.listen({ port: 0 }) 183 | 184 | const result = await fetch(address) 185 | t.assert.ok(result.ok) 186 | 187 | function middleware () { 188 | t.assert.fail('Shuld not be called') 189 | } 190 | }) 191 | 192 | test('Encapsulation support / 3', async t => { 193 | t.plan(2) 194 | 195 | const fastify = Fastify() 196 | 197 | t.after(() => fastify.close()) 198 | 199 | fastify.register(expressPlugin) 200 | 201 | fastify.register((instance, _opts, next) => { 202 | instance.use(cors()) 203 | instance.get('/plugin', (_req, reply) => { 204 | reply.send('ok') 205 | }) 206 | 207 | next() 208 | }) 209 | 210 | fastify.get('/', (_req, reply) => { 211 | reply.send('ok') 212 | }) 213 | 214 | const address = await fastify.listen({ port: 0 }) 215 | 216 | const result = await fetch(address + '/plugin') 217 | t.assert.deepStrictEqual(result.headers.get('access-control-allow-origin'), '*') 218 | 219 | const result2 = await fetch(address) 220 | t.assert.deepStrictEqual(result2.headers.get('access-control-allow-origin'), null) 221 | }) 222 | 223 | test('Encapsulation support / 4', async t => { 224 | t.plan(3) 225 | 226 | const fastify = Fastify() 227 | 228 | t.after(() => fastify.close()) 229 | 230 | fastify.register(expressPlugin) 231 | fastify.after(() => { 232 | fastify.use(middleware1) 233 | }) 234 | 235 | fastify.register((instance, _opts, next) => { 236 | instance.use(middleware2) 237 | instance.get('/plugin', (_req, reply) => { 238 | reply.send('ok') 239 | }) 240 | 241 | next() 242 | }) 243 | 244 | fastify.get('/', (_req, reply) => { 245 | reply.send('ok') 246 | }) 247 | 248 | const address = await fastify.listen({ port: 0 }) 249 | 250 | const result = await fetch(address + '/plugin') 251 | t.assert.deepStrictEqual(result.headers.get('x-middleware-1'), 'true') 252 | t.assert.deepStrictEqual(result.headers.get('x-middleware-2'), 'true') 253 | 254 | const result2 = await fetch(address) 255 | t.assert.deepStrictEqual(result2.headers.get('x-middleware-1'), 'true') 256 | 257 | function middleware1 (_req, res, next) { 258 | res.setHeader('x-middleware-1', true) 259 | next() 260 | } 261 | 262 | function middleware2 (_req, res, next) { 263 | res.setHeader('x-middleware-2', true) 264 | next() 265 | } 266 | }) 267 | 268 | test('Encapsulation support / 5', async t => { 269 | t.plan(6) 270 | 271 | const fastify = Fastify() 272 | 273 | t.after(() => fastify.close()) 274 | 275 | fastify.register(expressPlugin) 276 | fastify.after(() => { 277 | fastify.use(middleware1) 278 | }) 279 | 280 | fastify.register((instance, _opts, next) => { 281 | instance.use(middleware2) 282 | instance.get('/', (_req, reply) => { 283 | reply.send('ok') 284 | }) 285 | 286 | instance.register((i, _opts, next) => { 287 | i.use(middleware3) 288 | i.get('/nested', (_req, reply) => { 289 | reply.send('ok') 290 | }) 291 | 292 | next() 293 | }) 294 | 295 | next() 296 | }, { prefix: '/plugin' }) 297 | 298 | fastify.get('/', (_req, reply) => { 299 | reply.send('ok') 300 | }) 301 | 302 | const address = await fastify.listen({ port: 0 }) 303 | 304 | const result = await fetch(address + '/plugin/nested') 305 | t.assert.deepStrictEqual(result.headers.get('x-middleware-1'), 'true') 306 | t.assert.deepStrictEqual(result.headers.get('x-middleware-2'), 'true') 307 | t.assert.deepStrictEqual(result.headers.get('x-middleware-3'), 'true') 308 | 309 | const result2 = await fetch(address + '/plugin') 310 | t.assert.deepStrictEqual(result2.headers.get('x-middleware-1'), 'true') 311 | t.assert.deepStrictEqual(result2.headers.get('x-middleware-2'), 'true') 312 | 313 | const result3 = await fetch(address) 314 | t.assert.deepStrictEqual(result3.headers.get('x-middleware-1'), 'true') 315 | 316 | function middleware1 (_req, res, next) { 317 | res.setHeader('x-middleware-1', true) 318 | next() 319 | } 320 | 321 | function middleware2 (_req, res, next) { 322 | res.setHeader('x-middleware-2', true) 323 | next() 324 | } 325 | 326 | function middleware3 (_req, res, next) { 327 | res.setHeader('x-middleware-3', true) 328 | next() 329 | } 330 | }) 331 | 332 | test('Middleware chain', async t => { 333 | t.plan(4) 334 | 335 | const order = [1, 2, 3] 336 | const fastify = Fastify() 337 | 338 | t.after(() => fastify.close()) 339 | 340 | fastify 341 | .register(expressPlugin) 342 | .after(() => { 343 | fastify 344 | .use(middleware1) 345 | .use(middleware2) 346 | .use(middleware3) 347 | }) 348 | 349 | fastify.get('/', async () => { 350 | return { hello: 'world' } 351 | }) 352 | 353 | const address = await fastify.listen({ port: 0 }) 354 | 355 | const result = await fetch(address) 356 | t.assert.ok(result.ok) 357 | 358 | function middleware1 (_req, _res, next) { 359 | t.assert.deepStrictEqual(order.shift(), 1) 360 | next() 361 | } 362 | 363 | function middleware2 (_req, _res, next) { 364 | t.assert.deepStrictEqual(order.shift(), 2) 365 | next() 366 | } 367 | 368 | function middleware3 (_req, _res, next) { 369 | t.assert.deepStrictEqual(order.shift(), 3) 370 | next() 371 | } 372 | }) 373 | 374 | test('Middleware chain (with errors) / 1', async t => { 375 | t.plan(6) 376 | 377 | const order = [1, 2, 3] 378 | const fastify = Fastify() 379 | 380 | t.after(() => fastify.close()) 381 | 382 | fastify 383 | .register(expressPlugin) 384 | .after(() => { 385 | fastify 386 | .use(middleware1) 387 | .use(middleware2) 388 | .use(middleware3) 389 | }) 390 | 391 | fastify.get('/', async () => { 392 | return { hello: 'world' } 393 | }) 394 | 395 | const address = await fastify.listen({ port: 0 }) 396 | 397 | const result = await fetch(address) 398 | t.assert.deepStrictEqual(result.status, 500) 399 | 400 | function middleware1 (_req, _res, next) { 401 | t.assert.deepStrictEqual(order.shift(), 1) 402 | next(new Error('middleware1')) 403 | } 404 | 405 | function middleware2 (err, _req, _res, next) { 406 | t.assert.deepStrictEqual(err.message, 'middleware1') 407 | t.assert.deepStrictEqual(order.shift(), 2) 408 | next(new Error('middleware2')) 409 | } 410 | 411 | function middleware3 (err, _req, _res, next) { 412 | t.assert.deepStrictEqual(err.message, 'middleware2') 413 | t.assert.deepStrictEqual(order.shift(), 3) 414 | next(new Error('kaboom')) 415 | } 416 | }) 417 | 418 | test('Middleware chain (with errors) / 2', async t => { 419 | t.plan(5) 420 | 421 | const order = [1, 2] 422 | const fastify = Fastify() 423 | 424 | t.after(() => fastify.close()) 425 | 426 | fastify.setErrorHandler((err, _req, reply) => { 427 | t.assert.deepStrictEqual(err.message, 'middleware2') 428 | reply.send(err) 429 | }) 430 | 431 | fastify 432 | .register(expressPlugin) 433 | .after(() => { 434 | fastify 435 | .use(middleware1) 436 | .use(middleware2) 437 | .use(middleware3) 438 | }) 439 | 440 | fastify.get('/', async () => { 441 | return { hello: 'world' } 442 | }) 443 | 444 | const address = await fastify.listen({ port: 0 }) 445 | 446 | const result = await fetch(address) 447 | t.assert.deepStrictEqual(result.status, 500) 448 | 449 | function middleware1 (_req, _res, next) { 450 | t.assert.deepStrictEqual(order.shift(), 1) 451 | next(new Error('middleware1')) 452 | } 453 | 454 | function middleware2 (err, _req, _res, next) { 455 | t.assert.deepStrictEqual(err.message, 'middleware1') 456 | t.assert.deepStrictEqual(order.shift(), 2) 457 | next(new Error('middleware2')) 458 | } 459 | 460 | function middleware3 () { 461 | t.assert.fail('We should not be here') 462 | } 463 | }) 464 | 465 | test('Send a response from a middleware', async t => { 466 | t.plan(3) 467 | 468 | const fastify = Fastify() 469 | 470 | t.after(() => fastify.close()) 471 | 472 | fastify 473 | .register(expressPlugin) 474 | .after(() => { 475 | fastify 476 | .use(middleware1) 477 | .use(middleware2) 478 | }) 479 | 480 | fastify.addHook('preValidation', () => { 481 | t.assert.fail('We should not be here') 482 | }) 483 | 484 | fastify.addHook('preParsing', () => { 485 | t.assert.fail('We should not be here') 486 | }) 487 | 488 | fastify.addHook('preHandler', () => { 489 | t.assert.fail('We should not be here') 490 | }) 491 | 492 | fastify.addHook('onSend', (_req, _reply, payload, next) => { 493 | t.assert.ok('called') 494 | next(null, payload) 495 | }) 496 | 497 | fastify.addHook('onResponse', (_req, _reply, next) => { 498 | t.assert.ok('called') 499 | next() 500 | }) 501 | 502 | fastify.get('/', () => { 503 | t.assert.fail('We should not be here') 504 | }) 505 | 506 | const address = await fastify.listen({ port: 0 }) 507 | 508 | const result = await fetch(address) 509 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 510 | 511 | function middleware1 (_req, res) { 512 | res.send({ hello: 'world' }) 513 | } 514 | 515 | function middleware2 () { 516 | t.assert.fail('We should not be here') 517 | } 518 | }) 519 | 520 | test('Should support plugin level prefix', async t => { 521 | t.plan(2) 522 | const fastify = Fastify() 523 | t.after(() => fastify.close()) 524 | 525 | fastify.register(expressPlugin) 526 | 527 | fastify.register((instance, _opts, next) => { 528 | instance.use('/world', (_req, res, next) => { 529 | res.setHeader('x-foo', 'bar') 530 | next() 531 | }) 532 | 533 | instance.get('/world', (_req, reply) => { 534 | reply.send({ hello: 'world' }) 535 | }) 536 | 537 | next() 538 | }, { prefix: '/hello' }) 539 | 540 | const address = await fastify.listen({ port: 0 }) 541 | 542 | const result = await fetch(address + '/hello/world') 543 | t.assert.deepStrictEqual(result.headers.get('x-foo'), 'bar') 544 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 545 | }) 546 | -------------------------------------------------------------------------------- /test/enhance-request.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const Fastify = require('fastify') 5 | 6 | const expressPlugin = require('../index') 7 | 8 | test('Should enhance the Node.js core request/response objects', async t => { 9 | t.plan(8) 10 | const fastify = Fastify() 11 | t.after(() => fastify.close()) 12 | 13 | fastify.register(expressPlugin) 14 | 15 | fastify.get('/', async (req, reply) => { 16 | t.assert.deepStrictEqual(req.raw.originalUrl, req.raw.url) 17 | t.assert.deepStrictEqual(req.raw.id, req.id) 18 | t.assert.deepStrictEqual(req.raw.hostname, req.hostname) 19 | t.assert.deepStrictEqual(req.raw.protocol, req.protocol) 20 | t.assert.deepStrictEqual(req.raw.ip, req.ip) 21 | t.assert.deepStrictEqual(req.raw.ips, req.ips) 22 | t.assert.ok(req.raw.log) 23 | t.assert.ok(reply.raw.log) 24 | return { hello: 'world' } 25 | }) 26 | 27 | const address = await fastify.listen({ port: 0 }) 28 | 29 | await fetch(address) 30 | }) 31 | 32 | test('trust proxy protocol', async (t) => { 33 | t.plan(3) 34 | const fastify = Fastify({ trustProxy: true }) 35 | 36 | t.after(() => fastify.close()) 37 | 38 | fastify.register(expressPlugin).after(() => { 39 | fastify.use('/', function (req, res) { 40 | t.assert.deepStrictEqual(req.ip, '1.1.1.1', 'gets ip from x-forwarded-for') 41 | t.assert.deepStrictEqual(req.hostname, 'example.com', 'gets hostname from x-forwarded-host') 42 | t.assert.deepStrictEqual(req.protocol, 'lorem', 'gets protocol from x-forwarded-proto') 43 | 44 | res.sendStatus(200) 45 | }) 46 | }) 47 | 48 | const address = await fastify.listen({ port: 0 }) 49 | 50 | await fetch(address, { 51 | headers: { 52 | 'X-Forwarded-For': '1.1.1.1', 53 | 'X-Forwarded-Host': 'example.com', 54 | 'X-Forwarded-Proto': 'lorem' 55 | } 56 | }) 57 | }) 58 | 59 | test('passing createProxyHandler sets up a Proxy with Express req', async t => { 60 | t.plan(6) 61 | const testString = 'test proxy' 62 | 63 | const fastify = Fastify() 64 | t.after(() => fastify.close()) 65 | 66 | fastify.register(expressPlugin, { 67 | createProxyHandler: () => ({ 68 | set (target, prop, value) { 69 | if (prop === 'customField') { 70 | t.assert.deepStrictEqual(value, testString) 71 | } 72 | 73 | return Reflect.set(target, prop, value) 74 | }, 75 | get (target, prop) { 76 | if (prop === 'customField') { 77 | t.assert.ok('get customField called') 78 | } 79 | 80 | return target[prop] 81 | } 82 | }) 83 | }) 84 | .after(() => { 85 | fastify.use(function (req, _res, next) { 86 | req.customField = testString 87 | t.assert.deepStrictEqual(req.customField, testString) 88 | next() 89 | }) 90 | }) 91 | 92 | fastify.get('/', function (_request, reply) { 93 | reply.send({ hello: 'world' }) 94 | }) 95 | 96 | const address = await fastify.listen({ port: 0 }) 97 | 98 | const response = await fetch(address) 99 | 100 | const responseText = await response.text() 101 | t.assert.deepStrictEqual(response.status, 200) 102 | t.assert.deepStrictEqual(response.headers.get('content-length'), '' + responseText.length) 103 | t.assert.deepStrictEqual(JSON.parse(responseText), { hello: 'world' }) 104 | }) 105 | 106 | test('createProxyHandler has access to Fastify request object', async t => { 107 | t.plan(10) 108 | const startTestString = 'original' 109 | 110 | const fastify = Fastify() 111 | t.after(() => fastify.close()) 112 | fastify.decorateRequest('getAndSetFastify', startTestString) 113 | fastify.decorateRequest('getOnlyFastify', startTestString) 114 | 115 | fastify.register(expressPlugin, { 116 | createProxyHandler: fastifyReq => ({ 117 | set (target, prop, value) { 118 | if (prop === 'getAndSetFastify') { 119 | t.assert.ok('set to Fastify called') 120 | return Reflect.set(fastifyReq, prop, value) 121 | } else if (prop === 'getOnlyFastify') { 122 | return true 123 | } else { 124 | return Reflect.set(target, prop, value) 125 | } 126 | }, 127 | get (target, prop) { 128 | if (prop === 'getAndSetFastify' || prop === 'getOnlyFastify') { 129 | // Return something from Fastify req 130 | t.assert.ok('get from Fastify called') 131 | return fastifyReq[prop] 132 | } 133 | 134 | return target[prop] 135 | } 136 | }) 137 | }) 138 | .after(() => { 139 | fastify.use(function (req, _res, next) { 140 | t.assert.deepStrictEqual(req.getAndSetFastify, startTestString) 141 | t.assert.deepStrictEqual(req.getOnlyFastify, startTestString) 142 | req.getAndSetFastify = 'updated' 143 | req.getOnlyFastify = 'updated' 144 | next() 145 | }) 146 | }) 147 | 148 | fastify.get('/', function (request, reply) { 149 | // getOnlyFastify should change and getOnlyFastify should not 150 | t.assert.deepStrictEqual(request.getAndSetFastify, 'updated') 151 | t.assert.deepStrictEqual(request.getOnlyFastify, startTestString) 152 | 153 | reply.send({ hello: 'world' }) 154 | }) 155 | 156 | const address = await fastify.listen({ port: 0 }) 157 | 158 | const response = await fetch(address) 159 | 160 | const responseText = await response.text() 161 | t.assert.deepStrictEqual(response.status, 200) 162 | t.assert.deepStrictEqual(response.headers.get('content-length'), '' + responseText.length) 163 | t.assert.deepStrictEqual(JSON.parse(responseText), { hello: 'world' }) 164 | }) 165 | -------------------------------------------------------------------------------- /test/form-data.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const Fastify = require('fastify') 5 | const fastifyFormBody = require('@fastify/formbody') 6 | const Express = require('express') 7 | const bodyParser = require('body-parser') 8 | 9 | const expressPlugin = require('../index') 10 | 11 | test('POST request without form body works', async t => { 12 | t.plan(3) 13 | const fastify = Fastify() 14 | const express = Express() 15 | t.after(() => fastify.close()) 16 | 17 | fastify.register(fastifyFormBody) 18 | fastify.register(expressPlugin) 19 | .after(() => { 20 | express.use(bodyParser.urlencoded({ extended: false })) 21 | fastify.use(express) 22 | fastify.use((req, _res, next) => { 23 | // body-parser default value 24 | t.assert.deepStrictEqual(req.body, {}) 25 | next() 26 | }) 27 | }) 28 | 29 | fastify.post('/hello', () => { 30 | return { hello: 'world' } 31 | }) 32 | 33 | const address = await fastify.listen({ port: 0 }) 34 | 35 | const result = await fetch(address + '/hello', { 36 | method: 'post', 37 | signal: AbortSignal.timeout(100) 38 | }) 39 | 40 | t.assert.deepStrictEqual(result.status, 200) 41 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 42 | }) 43 | 44 | test('POST request with form body and without body-parser works', async t => { 45 | t.plan(3) 46 | const fastify = Fastify() 47 | const express = Express() 48 | t.after(() => fastify.close()) 49 | 50 | fastify.register(fastifyFormBody) 51 | fastify.register(expressPlugin) 52 | .after(() => { 53 | fastify.use(express) 54 | fastify.use((req, _res, next) => { 55 | // req.body default value 56 | t.assert.deepStrictEqual(req.body, undefined) 57 | next() 58 | }) 59 | }) 60 | 61 | fastify.post('/hello', () => { 62 | return { hello: 'world' } 63 | }) 64 | 65 | const address = await fastify.listen({ port: 0 }) 66 | 67 | const result = await fetch(address + '/hello', { 68 | method: 'post', 69 | body: new URLSearchParams({ input: 'test' }), 70 | signal: AbortSignal.timeout(100) 71 | }) 72 | 73 | t.assert.deepStrictEqual(result.status, 200) 74 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 75 | }) 76 | 77 | test('POST request with form body and body-parser hangs up', async t => { 78 | t.plan(2) 79 | const fastify = Fastify() 80 | const express = Express() 81 | t.after(() => fastify.close()) 82 | 83 | fastify.register(fastifyFormBody) 84 | fastify.register(expressPlugin) 85 | .after(() => { 86 | express.use(bodyParser.urlencoded({ extended: false })) 87 | fastify.use(express) 88 | fastify.use((req, _res, next) => { 89 | // body-parser result 90 | t.assert.deepStrictEqual(req.body, { input: 'test' }) 91 | next() 92 | }) 93 | }) 94 | 95 | fastify.post('/hello', () => { 96 | return { hello: 'world' } 97 | }) 98 | 99 | const address = await fastify.listen({ port: 0 }) 100 | 101 | await t.assert.rejects(() => fetch(address + '/hello', { 102 | method: 'post', 103 | body: new URLSearchParams({ input: 'test' }), 104 | signal: AbortSignal.timeout(5) 105 | }), 'Request timed out') 106 | }) 107 | 108 | test('POST request with form body and body-parser hangs up, compatibility case', async t => { 109 | t.plan(3) 110 | const fastify = Fastify() 111 | const express = Express() 112 | t.after(() => fastify.close()) 113 | 114 | fastify.register(fastifyFormBody) 115 | fastify.register(expressPlugin, { expressHook: 'preHandler' }) 116 | .after(() => { 117 | fastify.use(express) 118 | fastify.use((req, _res, next) => { 119 | // fastify-formbody with backward compatibility result 120 | t.assert.deepStrictEqual(req.body.input, 'test') 121 | next() 122 | }) 123 | }) 124 | 125 | fastify.post('/hello', () => { 126 | return { hello: 'world' } 127 | }) 128 | 129 | const address = await fastify.listen({ port: 0 }) 130 | 131 | const result = await fetch(address + '/hello', { 132 | method: 'post', 133 | body: new URLSearchParams({ input: 'test' }), 134 | signal: AbortSignal.timeout(100) 135 | }) 136 | 137 | t.assert.deepStrictEqual(result.status, 200) 138 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 139 | }) 140 | -------------------------------------------------------------------------------- /test/hooks.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('node:test') 4 | const Fastify = require('fastify') 5 | const express = require('express') 6 | const expressPlugin = require('../index') 7 | 8 | test('onSend hook should receive valid request and reply objects if middleware fails', async t => { 9 | t.plan(3) 10 | const fastify = Fastify() 11 | fastify.register(expressPlugin) 12 | .after(() => { 13 | fastify.use(function (_req, _res, next) { 14 | next(new Error('middlware failed')) 15 | }) 16 | }) 17 | 18 | fastify.decorateRequest('testDecorator', 'testDecoratorVal') 19 | fastify.decorateReply('testDecorator', 'testDecoratorVal') 20 | 21 | fastify.addHook('onSend', function (request, reply, _payload, next) { 22 | t.assert.deepStrictEqual(request.testDecorator, 'testDecoratorVal') 23 | t.assert.deepStrictEqual(reply.testDecorator, 'testDecoratorVal') 24 | next() 25 | }) 26 | 27 | fastify.get('/', (_req, reply) => { 28 | reply.send('hello') 29 | }) 30 | 31 | const result = await fastify.inject({ 32 | method: 'GET', 33 | url: '/' 34 | }) 35 | 36 | t.assert.deepStrictEqual(result.statusCode, 500) 37 | }) 38 | 39 | test('request.url is not mutated between onRequest and onResponse', async t => { 40 | t.plan(3) 41 | const fastify = Fastify() 42 | const targetUrl = '/hubba/bubba' 43 | 44 | fastify.addHook('onRequest', (request, _, next) => { 45 | t.assert.deepStrictEqual(request.url, targetUrl) 46 | next() 47 | }) 48 | 49 | fastify.addHook('onResponse', (request, _, next) => { 50 | t.assert.deepStrictEqual(request.url, targetUrl) 51 | next() 52 | }) 53 | 54 | fastify.register(expressPlugin).after(() => { 55 | const mainRouter = express.Router() 56 | const innerRouter = express.Router() 57 | mainRouter.use('/hubba', innerRouter) 58 | innerRouter.get('/bubba', (_req, res) => { 59 | res.sendStatus(200) 60 | }) 61 | fastify.use(mainRouter) 62 | }) 63 | 64 | const result = await fastify.inject({ 65 | method: 'GET', 66 | url: targetUrl 67 | }) 68 | 69 | t.assert.deepStrictEqual(result.statusCode, 200) 70 | }) 71 | -------------------------------------------------------------------------------- /test/middleware.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Original Fastify test/middlewares.test.js file 4 | 5 | const { test } = require('node:test') 6 | const fastify = require('fastify') 7 | const fp = require('fastify-plugin') 8 | const cors = require('cors') 9 | const helmet = require('helmet') 10 | 11 | const expressPlugin = require('../index') 12 | 13 | test('use a middleware', async t => { 14 | t.plan(5) 15 | 16 | const instance = fastify() 17 | t.after(() => instance.close()) 18 | instance.register(expressPlugin) 19 | .after(() => { 20 | const useRes = instance.use(function (_req, _res, next) { 21 | t.assert.ok('middleware called') 22 | next() 23 | }) 24 | 25 | t.assert.deepStrictEqual(useRes, instance) 26 | }) 27 | 28 | instance.get('/', function (_request, reply) { 29 | reply.send({ hello: 'world' }) 30 | }) 31 | 32 | const address = await instance.listen({ port: 0 }) 33 | 34 | const result = await fetch(address) 35 | 36 | const responseText = await result.text() 37 | t.assert.deepStrictEqual(result.status, 200) 38 | t.assert.deepStrictEqual(result.headers.get('content-length'), '' + responseText.length) 39 | t.assert.deepStrictEqual(JSON.parse(responseText), { hello: 'world' }) 40 | }) 41 | 42 | test('use cors', async t => { 43 | t.plan(1) 44 | 45 | const instance = fastify() 46 | t.after(() => instance.close()) 47 | instance.register(expressPlugin) 48 | .after(() => { 49 | instance.use(cors()) 50 | }) 51 | 52 | instance.get('/', function (_request, reply) { 53 | reply.send({ hello: 'world' }) 54 | }) 55 | 56 | const address = await instance.listen({ port: 0 }) 57 | 58 | const result = await fetch(address) 59 | t.assert.deepStrictEqual(result.headers.get('access-control-allow-origin'), '*') 60 | }) 61 | 62 | test('use helmet', async t => { 63 | t.plan(1) 64 | 65 | const instance = fastify() 66 | t.after(() => instance.close()) 67 | instance.register(expressPlugin) 68 | .after(() => { 69 | instance.use(helmet()) 70 | }) 71 | 72 | instance.get('/', function (_request, reply) { 73 | reply.send({ hello: 'world' }) 74 | }) 75 | 76 | const address = await instance.listen({ port: 0 }) 77 | 78 | const result = await fetch(address) 79 | t.assert.ok(result.headers.get('x-xss-protection')) 80 | }) 81 | 82 | test('use helmet and cors', async t => { 83 | t.plan(2) 84 | 85 | const instance = fastify() 86 | t.after(() => instance.close()) 87 | instance.register(expressPlugin) 88 | .after(() => { 89 | instance.use(cors()) 90 | instance.use(helmet()) 91 | }) 92 | 93 | instance.get('/', function (_request, reply) { 94 | reply.send({ hello: 'world' }) 95 | }) 96 | 97 | const address = await instance.listen({ port: 0 }) 98 | 99 | const result = await fetch(address) 100 | 101 | t.assert.ok(result.headers.get('x-xss-protection')) 102 | t.assert.deepStrictEqual(result.headers.get('access-control-allow-origin'), '*') 103 | }) 104 | 105 | test('use cors only on prefix', async t => { 106 | t.plan(1) 107 | 108 | const instance = fastify() 109 | t.after(() => instance.close()) 110 | instance.register((innerInstance) => { 111 | innerInstance.register(expressPlugin).after(() => { 112 | innerInstance.use('/', cors()) 113 | }) 114 | innerInstance.get('/', function (_request, reply) { 115 | reply.send({ hello: 'world' }) 116 | }) 117 | }, { prefix: '/prefix' }) 118 | 119 | const address = await instance.listen({ port: 0 }) 120 | 121 | const result = await fetch(address + '/prefix') 122 | 123 | t.assert.deepStrictEqual(result.headers.get('access-control-allow-origin'), '*') 124 | }) 125 | 126 | test('middlewares with prefix', async t => { 127 | t.plan(4) 128 | 129 | const instance = fastify() 130 | t.after(() => instance.close()) 131 | instance.register(expressPlugin) 132 | .after(() => { 133 | instance.use(function (req, _res, next) { 134 | req.global = true 135 | next() 136 | }) 137 | instance.use('', function (req, _res, next) { 138 | req.global2 = true 139 | next() 140 | }) 141 | instance.use('/', function (req, _res, next) { 142 | req.root = true 143 | next() 144 | }) 145 | instance.use('/prefix', function (req, _res, next) { 146 | req.prefixed = true 147 | next() 148 | }) 149 | instance.use('/prefix/', function (req, _res, next) { 150 | req.slashed = true 151 | next() 152 | }) 153 | }) 154 | 155 | function handler (request, reply) { 156 | reply.send({ 157 | prefixed: request.raw.prefixed, 158 | slashed: request.raw.slashed, 159 | global: request.raw.global, 160 | global2: request.raw.global2, 161 | root: request.raw.root 162 | }) 163 | } 164 | 165 | instance.get('/', handler) 166 | instance.get('/prefix', handler) 167 | instance.get('/prefix/', handler) 168 | instance.get('/prefix/inner', handler) 169 | 170 | const address = await instance.listen({ port: 0 }) 171 | 172 | await t.test('/', async t => { 173 | t.plan(1) 174 | 175 | const result = await fetch(address + '/') 176 | t.assert.deepStrictEqual(await result.json(), { 177 | global: true, 178 | global2: true, 179 | root: true 180 | }) 181 | }) 182 | 183 | await t.test('/prefix', async t => { 184 | t.plan(1) 185 | 186 | const result = await fetch(address + '/prefix') 187 | t.assert.deepStrictEqual(await result.json(), { 188 | prefixed: true, 189 | global: true, 190 | global2: true, 191 | root: true, 192 | slashed: true 193 | }) 194 | }) 195 | 196 | await t.test('/prefix/', async t => { 197 | t.plan(1) 198 | 199 | const result = await fetch(address + '/prefix/') 200 | t.assert.deepStrictEqual(await result.json(), { 201 | prefixed: true, 202 | slashed: true, 203 | global: true, 204 | global2: true, 205 | root: true 206 | }) 207 | }) 208 | 209 | await t.test('/prefix/inner', async t => { 210 | t.plan(1) 211 | 212 | const result = await fetch(address + '/prefix/inner') 213 | t.assert.deepStrictEqual(await result.json(), { 214 | prefixed: true, 215 | slashed: true, 216 | global: true, 217 | global2: true, 218 | root: true 219 | }) 220 | }) 221 | }) 222 | 223 | test('res.end should block middleware execution', async t => { 224 | t.plan(4) 225 | 226 | const instance = fastify() 227 | t.after(() => instance.close()) 228 | instance.register(expressPlugin) 229 | .after(() => { 230 | instance.use(function (_req, res) { 231 | res.send('hello') 232 | }) 233 | 234 | instance.use(function () { 235 | t.assert.fail('we should not be here') 236 | }) 237 | }) 238 | 239 | instance.addHook('onRequest', (_req, _res, next) => { 240 | t.assert.ok('called') 241 | next() 242 | }) 243 | 244 | instance.addHook('preHandler', () => { 245 | t.assert.fail('this should not be called') 246 | }) 247 | 248 | instance.addHook('onSend', (_req, _reply, payload, next) => { 249 | t.assert.ok('called') 250 | next(null, payload) 251 | }) 252 | 253 | instance.addHook('onResponse', (_request, _reply, next) => { 254 | t.assert.ok('called') 255 | next() 256 | }) 257 | 258 | instance.get('/', function () { 259 | t.assert.fail('we should no be here') 260 | }) 261 | 262 | const address = await instance.listen({ port: 0 }) 263 | 264 | const result = await fetch(address) 265 | 266 | t.assert.deepStrictEqual(result.status, 200) 267 | 268 | t.assert.deepStrictEqual(await result.text(), 'hello') 269 | }) 270 | 271 | test('Use a middleware inside a plugin after an encapsulated plugin', async t => { 272 | t.plan(3) 273 | const f = fastify() 274 | t.after(() => f.close()) 275 | f.register(expressPlugin) 276 | 277 | f.register(function (instance, _opts, next) { 278 | instance.use(function (_req, _res, next) { 279 | t.assert.ok('first middleware called') 280 | next() 281 | }) 282 | 283 | instance.get('/', function (_request, reply) { 284 | reply.send({ hello: 'world' }) 285 | }) 286 | 287 | next() 288 | }) 289 | 290 | f.register(fp(function (instance, _opts, next) { 291 | instance.use(function (_req, _res, next) { 292 | t.assert.ok('second middleware called') 293 | next() 294 | }) 295 | 296 | next() 297 | })) 298 | 299 | const address = await f.listen({ port: 0 }) 300 | 301 | const result = await fetch(address) 302 | t.assert.deepStrictEqual(result.status, 200) 303 | 304 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 305 | }) 306 | 307 | test('middlewares should run in the order in which they are defined', async t => { 308 | t.plan(8) 309 | const f = fastify() 310 | t.after(() => f.close()) 311 | f.register(expressPlugin) 312 | 313 | f.register(fp(function (instance, _opts, next) { 314 | instance.use(function (req, _res, next) { 315 | t.assert.deepStrictEqual(req.previous, undefined) 316 | req.previous = 1 317 | next() 318 | }) 319 | 320 | instance.register(fp(function (i, _opts, next) { 321 | i.use(function (req, _res, next) { 322 | t.assert.deepStrictEqual(req.previous, 2) 323 | req.previous = 3 324 | next() 325 | }) 326 | next() 327 | })) 328 | 329 | instance.use(function (req, _res, next) { 330 | t.assert.deepStrictEqual(req.previous, 1) 331 | req.previous = 2 332 | next() 333 | }) 334 | 335 | next() 336 | })) 337 | 338 | f.register(function (instance, _opts, next) { 339 | instance.use(function (req, _res, next) { 340 | t.assert.deepStrictEqual(req.previous, 3) 341 | req.previous = 4 342 | next() 343 | }) 344 | 345 | instance.get('/', function (request, reply) { 346 | t.assert.deepStrictEqual(request.raw.previous, 5) 347 | reply.send({ hello: 'world' }) 348 | }) 349 | 350 | instance.register(fp(function (i, _opts, next) { 351 | i.use(function (req, _res, next) { 352 | t.assert.deepStrictEqual(req.previous, 4) 353 | req.previous = 5 354 | next() 355 | }) 356 | next() 357 | })) 358 | 359 | next() 360 | }) 361 | 362 | const address = await f.listen({ port: 0 }) 363 | 364 | const result = await fetch(address) 365 | t.assert.deepStrictEqual(result.status, 200) 366 | t.assert.deepStrictEqual(await result.json(), { hello: 'world' }) 367 | }) 368 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Application, Request } from 'express' 2 | import { FastifyPluginCallback, FastifyRequest } from 'fastify' 3 | 4 | declare module 'fastify' { 5 | interface FastifyInstance { 6 | /** 7 | * Express middleware function 8 | */ 9 | use: Application['use'] 10 | 11 | /** 12 | * Express application instance 13 | */ 14 | express: Application 15 | } 16 | } 17 | 18 | type FastifyExpress = FastifyPluginCallback 19 | 20 | declare namespace fastifyExpress { 21 | 22 | export interface FastifyExpressOptions { 23 | expressHook?: string; 24 | createProxyHandler?: (fastifyReq: FastifyRequest) => ProxyHandler 25 | } 26 | 27 | export const fastifyExpress: FastifyExpress 28 | export { fastifyExpress as default } 29 | } 30 | 31 | declare function fastifyExpress (...params: Parameters): ReturnType 32 | export = fastifyExpress 33 | -------------------------------------------------------------------------------- /types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import Fastify, { FastifyRequest } from 'fastify' 2 | import fastifyExpress from '..' 3 | import { expectType } from 'tsd' 4 | import { Application } from 'express' 5 | 6 | const app = Fastify() 7 | 8 | app.register(fastifyExpress) 9 | app.register(fastifyExpress, { 10 | expressHook: 'onRequest', 11 | createProxyHandler: (fastifyReq) => ({ 12 | set (target, prop, value) { 13 | expectType(fastifyReq) 14 | return Reflect.set(target, prop, value) 15 | } 16 | }) 17 | }) 18 | 19 | expectType(app.express) 20 | 21 | app.express.disable('x-powered-by') 22 | 23 | app.use('/world', (_req, res) => { 24 | res.sendStatus(200) 25 | }) 26 | --------------------------------------------------------------------------------