├── .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 | [](https://github.com/fastify/fastify-express/actions/workflows/ci.yml)
4 | [](https://www.npmjs.com/package/@fastify/express)
5 | [](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 | Note |
13 | This 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. |
14 |
15 |
16 | Since Express does not support Node.js core HTTP/2 module, this plugin does not support HTTP/2 either. |
17 |
18 |
19 |
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 |
--------------------------------------------------------------------------------