├── .all-contributorsrc
├── .editorconfig
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── question.md
└── workflows
│ └── ci.yml
├── .gitignore
├── .husky
└── pre-commit
├── .prettierignore
├── .vscode
└── tasks.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── banner.png
├── docs
├── .nojekyll
├── README.md
├── _sidebar.md
├── googlebe3d4fe76c235fa6.html
└── index.html
├── eslint.json
├── examples
├── simple-js-app
│ ├── index.js
│ └── routes
│ │ ├── .ignored-route.js
│ │ ├── _ignored-route.js
│ │ ├── _igored-subpath
│ │ └── index.js
│ │ ├── index.js
│ │ └── users
│ │ ├── index.js
│ │ └── {userId}
│ │ ├── comments.js
│ │ └── index.js
└── simple-ts-app
│ ├── index.ts
│ └── routes
│ ├── .ignored-route.ts
│ ├── _ignored-route.ts
│ ├── _igored-subpath
│ └── index.ts
│ ├── index.ts
│ └── users
│ ├── index.ts
│ └── {userId}
│ ├── comments.ts
│ └── index.ts
├── jest.config.js
├── package.json
├── src
└── index.ts
├── tests
├── invalid-routes.test.ts
├── options.test.ts
├── routes.test.ts
└── utils
│ └── mock.ts
└── tsconfig.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "projectName": "fastify-autoroutes",
6 | "projectOwner": "GiovanniCardamone",
7 | "repoType": "github",
8 | "repoHost": "https://github.com",
9 | "imageSize": 100,
10 | "commit": true,
11 | "commitConvention": "none",
12 | "contributors": [
13 | {
14 | "login": "GiovanniCardamone",
15 | "name": "Giovanni Cardamone",
16 | "avatar_url": "https://avatars0.githubusercontent.com/u/5117748?v=4",
17 | "profile": "http://giovannicardamone.github.io",
18 | "contributions": [
19 | "code",
20 | "doc",
21 | "example",
22 | "maintenance"
23 | ]
24 | },
25 | {
26 | "login": "genbs",
27 | "name": "Gennaro",
28 | "avatar_url": "https://avatars0.githubusercontent.com/u/6159598?v=4",
29 | "profile": "https://github.com/genbs",
30 | "contributions": [
31 | "design"
32 | ]
33 | }
34 | ],
35 | "contributorsPerLine": 7,
36 | "skipCi": true
37 | }
38 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | [*.{js,ts}]
12 | indent_size = 2
13 | indent_style = space
14 |
15 |
16 | [*.{json,yml}]
17 | indent_size = 4
18 | indent_style = space
19 |
20 | [.prettierrc]
21 | indent_size = 4
22 | indent_style = space
23 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: GiovanniCardamone
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐛 Bug report
3 | about: Create a report to help us improve
4 | ---
5 |
6 |
7 |
8 | ### Versions
9 |
10 |
11 |
12 | - **Logger Version:** v1.0.0
13 | - **Node Version:** v13.0.0
14 | - **Operating System:** Windows 10
15 | - **Terminal:** Windows Powershell
16 |
17 | ### Expected Behavior
18 |
19 |
20 |
21 | ### Actual Behavior
22 |
23 |
25 |
26 | ### Steps to Reproduce
27 |
28 |
30 |
31 | 1. ...
32 | 2. ...
33 | 3. ...
34 |
35 | ### Screenshots (Optional)
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 💫 Feature request
3 | about: Suggest an idea for this project
4 | ---
5 |
6 |
7 |
8 | ### Feature description
9 |
10 |
11 |
12 | ### Feature motivation
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🗨 Question
3 | about: Ask a question
4 | ---
5 |
6 |
7 |
8 | ### Question
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths-ignore:
8 | - 'docs/**'
9 | - '*.md'
10 |
11 | jobs:
12 | # build / test
13 | Build:
14 | name: Build Package
15 | runs-on: ubuntu-latest
16 | steps:
17 | # Checkout Repo
18 | - uses: actions/checkout@v2
19 |
20 | # Setup Node
21 | - uses: actions/setup-node@v2
22 | with:
23 | node-version: 14
24 |
25 | # Install Dependencies
26 | - run: npm ci
27 |
28 | # Build Package
29 | - run: npm run build
30 |
31 | # Run Tests
32 | - run: npm run test:coverage
33 |
34 | # Report Coverage
35 | - name: Coveralls GitHub Action
36 | uses: coverallsapp/github-action@1.1.3
37 | with:
38 | github-token: ${{ secrets.GITHUB_TOKEN }}
39 |
40 | # create / release
41 | Release:
42 | needs: Build
43 | name: Create Release Package
44 | runs-on: ubuntu-latest
45 | steps:
46 | # Checkout Repo
47 | - uses: actions/checkout@v2
48 |
49 | # Setup Node
50 | - uses: actions/setup-node@v2
51 | with:
52 | node-version: 14
53 |
54 | # Show current package
55 | - name: cat package.json
56 | run: cat ./package.json
57 |
58 | # Install Dependencies
59 | - run: npm ci
60 |
61 | # Build package
62 | - run: npm run build
63 |
64 | # Bump version, tag commit and generate a changelog
65 | - name: Conventional Changelog Action
66 | uses: TriPSs/conventional-changelog-action@v3
67 | with:
68 | github-token: ${{ secrets.GITHUB_TOKEN }}
69 | git-message: 'chore(release): {version}'
70 | tag-prefix: 'v'
71 | output-file: 'CHANGELOG.md'
72 | release-count: '10'
73 | skip-on-empty: 'false'
74 | skip-version-file: 'false'
75 | skip-commit: 'false'
76 |
77 | # Publish to NPM
78 | - name: Publish to NPM
79 | uses: JS-DevTools/npm-publish@v1
80 | with:
81 | token: ${{ secrets.NPM_TOKEN }}
82 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | package-lock.json
61 |
62 | dist
63 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm test
5 |
6 | npm run docs
7 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | tsconfig.json
2 | README.md
3 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "f1",
6 | "type": "npm",
7 | "script": "build:watch",
8 | "group": "build",
9 | "problemMatcher": ["$tsc"]
10 | },
11 | {
12 | "label": "f2",
13 | "type": "npm",
14 | "script": "test:watch",
15 | "group": "test",
16 | "problemMatcher": ["$tsc"]
17 | },
18 | {
19 | "label": "f3",
20 | "type": "npm",
21 | "script": "build:watch",
22 | "group": "build",
23 | "problemMatcher": ["$tsc"]
24 | },
25 | {
26 | "label": "f4",
27 | "type": "npm",
28 | "script": "test",
29 | "group": "test",
30 | "problemMatcher": ["$tsc"]
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to fastify-autoroutes
2 |
3 | ## Developer's Certificate of Origin 1.1
4 |
5 | By making a contribution to this project, I certify that:
6 |
7 | - (a) The contribution was created in whole or in part by me and I
8 | have the right to submit it under the open source license
9 | indicated in the file; or
10 |
11 | - (b) The contribution is based upon previous work that, to the best
12 | of my knowledge, is covered under an appropriate open source
13 | license and I have the right under that license to submit that
14 | work with modifications, whether created in whole or in part
15 | by me, under the same open source license (unless I am
16 | permitted to submit under a different license), as indicated
17 | in the file; or
18 |
19 | - (c) The contribution was provided directly to me by some other
20 | person who certified (a), (b) or (c) and I have not modified
21 | it.
22 |
23 | - (d) I understand and agree that this project and the contribution
24 | are public and that a record of the contribution (including all
25 | personal information I submit with it, including my sign-off) is
26 | maintained indefinitely and may be redistributed consistent with
27 | this project or the open source license(s) involved.
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 fastify-autoroutes
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-autoroutes
2 |
3 |
4 |
5 | 
6 |
7 |  
8 |
9 | [](https://www.npmjs.com/package/fastify-autoroutes)
10 | [](https://www.npmjs.com/package/fastify-autoroutes)
11 | [](https://snyk.io/test/github/GiovanniCardamone/fastify-autoroutes)
12 | [](https://github.com/GiovanniCardamone/fastify-autoroutes/blob/master/LICENSE)
13 |
14 | 
15 | [](https://coveralls.io/github/GiovanniCardamone/fastify-autoroutes?branch=master)
16 |
17 |
18 |
19 | > :star: Thanks to everyone who has starred the project, it means a lot!
20 |
21 | Plugin to handle routes in fastify automatically based on directory structure.
22 |
23 | ## :newspaper: **[Full Documentation](https://giovannicardamone.github.io/fastify-autoroutes/)**
24 |
25 | [fastify-autoroutes](https://giovannicardamone.github.io/fastify-autoroutes/)
26 |
27 | ## :rocket: Install
28 |
29 | ```sh
30 | npm install --save fastify-autoroutes
31 | ```
32 |
33 | ## :blue_book: Usage
34 |
35 | ### Register plugin
36 |
37 | ```js
38 | const fastify = require('fastify')
39 | const server = fastify()
40 |
41 | server.register(require('fastify-autoroutes'), {
42 | dir: './', // relative to your cwd
43 | prefix: '/api', // optional, don't use if you do not need prefixes
44 | })
45 | ```
46 |
47 | ### Create file in autoroutes directory
48 |
49 | ```js
50 | //file: `/some/route.js`
51 | //url: `http://your-host/some/route`
52 |
53 | export default (fastifyInstance) => ({
54 | get: {
55 | handler: async (request, reply) => 'Hello, Route'
56 | },
57 | })
58 | ```
59 |
60 | ### Using typescript support for module
61 |
62 | ```typescript
63 | //file: `/some/route.ts`
64 | //url: `http://your-host/some/route`
65 |
66 | import { FastifyInstance } from 'fastify'
67 | import { Resource } from 'fastify-autoroutes'
68 |
69 | export default (fastify: FastifyInstance) => {
70 | get: {
71 | handler: async (request: FastifyRequest, reply: FastifyReply) => 'Hello, Route!'
72 | }
73 | }
74 | ```
75 |
76 | ### Accepts params in autoroutes
77 |
78 | > :information_source: file/directory name must follow syntax `:paramName` or `{paramName}`
79 |
80 | ```js
81 | //file: `/users/{userId}/photos.js`
82 | //mapped to: `/users/:userId/photos`
83 |
84 | export default (fastifyInstance) => ({
85 | get: {
86 | handler: (request, reply) => {
87 | reply.send(`photos of user ${request.params.userId}`)
88 | }
89 | },
90 | })
91 | ```
92 |
93 | ## :arrow_forward: Route module definition
94 |
95 | Method specification for attributes is available here: [Method specification](https://www.fastify.io/docs/latest/Routes/#full-declaration)
96 |
97 | > :information_source: attributes `url` and `method` are dynamically provided
98 |
99 | Allowed attributes mapped to Http methods in module:
100 |
101 | - delete
102 | - get
103 | - head
104 | - patch
105 | - post
106 | - put
107 | - options
108 |
109 | ## :arrow_forward: Skipping files
110 |
111 | to skip file in routes directory, prepend the `.` or `_` character to filename
112 |
113 | examples:
114 |
115 | ```text
116 | routes
117 | ├── .ignored-directory
118 | ├── _ignored-directory
119 | ├── .ignored-js-file.js
120 | ├── _ignored-js-file.js
121 | ├── .ignored-ts-file.ts
122 | ├── _ignored-ts-file.ts
123 | ├── ignored-js-test.test.js
124 | └── ignored-ts-test.test.ts
125 | ```
126 |
127 | > :warning: also any `*.test.js` and `*.test.ts` are skipped!
128 |
129 | this is useful if you want to have a lib file which contains functions that don't have to be a route, so just create the file with `_` prepending character
130 |
131 | ## :page_facing_up: License
132 |
133 | Licensed under [MIT](./LICENSE)
134 |
135 | ## :sparkles: Contributors
136 |
137 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
138 |
139 |
140 |
141 |
142 |
148 |
149 |
150 |
151 |
152 |
153 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.
154 |
155 | Contributions of any kind welcome!
156 |
--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GiovanniCardamone/fastify-autoroutes/559d27ff2400734d09dd4976e2161380f5dfa900/banner.png
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GiovanniCardamone/fastify-autoroutes/559d27ff2400734d09dd4976e2161380f5dfa900/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # fastify-autoroutes
2 |
3 | fastify-autoroutes is an innovative way to declare routes in your fastify app.
4 |
5 | Just specify a directory for routes and fastify-autoroutes will care of import any routes in a same way is defined in your filesystem structures.
6 |
7 | ## Getting started
8 |
9 | ### Install
10 |
11 | Install with npm:
12 |
13 | ```sh
14 | npm i fastify-autoroutes --save
15 | ```
16 |
17 | Install with yarn:
18 |
19 | ```sh
20 | yarn add fastify-autoroutes
21 | ```
22 |
23 | ### Initialize plugin
24 |
25 | create a root directory for your autoroutes and pass it to plugin
26 |
27 | ```javascript
28 | const fastify = require('fastify')
29 | const server = fastify()
30 | server.register(require('fastify-autoroutes'), {
31 | dir: './routes', // routes is my directory in this example
32 | })
33 | ```
34 |
35 | ### Write your first route
36 |
37 | Javascript:
38 |
39 | ```javascript
40 | //file: ./routes/index.js
41 | //url: http://your-host
42 |
43 | export default (fastifyInstance) => ({
44 | get: {
45 | handler: async () => 'Hello, Index!'
46 | }
47 | })
48 |
49 | ```
50 |
51 | Typescript:
52 |
53 | ```typescript
54 | //file: ./routes/index.js
55 | //url: http://your-host
56 |
57 | import { FastifyInstance } from 'fastify'
58 | import { Resource } from 'fastify-autoroutes'
59 |
60 | export default (fastifyInstance: FastifyInstance) => {
61 | get: {
62 | handler: async () => 'Hello, Index!'
63 | }
64 | }
65 | ```
66 |
67 | Directory tree of your file system will look like this:
68 |
69 | ```text
70 | /
71 | ├── index.js
72 | ├── package.json
73 | └── routes
74 | └── index.js
75 | ```
76 |
77 | ## Nested autoroutes
78 |
79 | Autoroutes directory scenario:
80 |
81 | ```text
82 | /
83 | ├── index.js
84 | ├── package.json
85 | └── routes
86 | └── hello => http://your-host/hello
87 | └── world.js => http://your-host/hello/world
88 | ```
89 |
90 | in this case, the plugin will recursively scan any routes in directory and map it urls
91 |
92 | > :warning: those two directory structure are **NOT** equivalent:
93 | >
94 | > ```text
95 | > / | /
96 | > ├── index.js | ├── index.js
97 | > ├── package.json | ├── package.json
98 | > └── routes | └── routes
99 | > └── hello | └── hello
100 | > └── world.js | └── world
101 | > | └── index.js
102 | > |
103 | > mapped to url: | mapped to url:
104 | > http://your-host/hello/world | http://your-host/hello/world/
105 | > ```
106 |
107 | > :information_source: you have to set `ignoreTrailingSlash: true` to make it the same.
108 |
109 | ## Ignore routes
110 |
111 | to ignore routes there are few way:
112 |
113 | - prepend '.' character to your file/directory name
114 | - prepend '_' character to your file/directory name
115 |
116 | > :information_source: files `*.test.js` and `*.test.ts` are automatically ignored
117 |
118 | examples:
119 |
120 | ```text
121 | routes
122 | ├── .ignored-directory
123 | ├── _ignored-directory
124 | ├── .ignored-js-file.js
125 | ├── _ignored-js-file.js
126 | ├── .ignored-ts-file.ts
127 | ├── _ignored-ts-file.ts
128 | ├── ignored-js-test.test.js
129 | └── ignored-ts-test.test.ts
130 | ```
131 |
132 | ## Url parameters in autoroutes
133 |
134 | parameters in URL can be specified using `liquid-variable-syntax` or (Not on windows) prepending `:` to the name of file or directory
135 |
136 | examples:
137 |
138 | using liquid variable syntax
139 |
140 | ```text
141 | .
142 | ├── index.js
143 | ├── package.json
144 | └── routes
145 | └── users
146 | ├── {userId}
147 | │ └── posts.js => http://your-host/users//posts
148 | └── {userId}.js => http://your-host/users/
149 | ```
150 |
151 | using `:` syntax (Not on windows)
152 |
153 | ```text
154 | .
155 | ├── index.js
156 | ├── package.json
157 | └── routes
158 | └── users
159 | ├── :userId
160 | │ └── posts.js => http://your-host/users//posts
161 | └── :userId.js => http://your-host/users/
162 | ```
163 |
164 | ### Retrieve parameters
165 |
166 | Parameters will be injected in route just like normal url matching syntax:
167 |
168 | ```javascript
169 | //file: ./routes/{userId}/posts.js
170 |
171 | export default (fastifyInstance) => ({
172 | get: {
173 | handler: async (request) => `returning posts of user: ${request.params.userId}`
174 | }
175 | })
176 | ```
177 |
178 | ## Accepted methods in module
179 |
180 | each file must export a function that accept fastify as parameter, and return an object with the following properties:
181 |
182 | ```javascript
183 | export default (fastifyInstance) => ({
184 | delete: {
185 | // your handler logic
186 | },
187 | get: {
188 | // your handler logic
189 | },
190 | head: {
191 | // your handler logic
192 | },
193 | patch: {
194 | // your handler logic
195 | },
196 | post: {
197 | // your handler logic
198 | },
199 | put: {
200 | // your handler logic
201 | },
202 | options: {
203 | // your handler logic
204 | },
205 | })
206 |
207 | ```
208 |
209 | ### Route definition
210 |
211 | Each route should be compliant to fastify route: [Method Specification](https://www.fastify.io/docs/latest/Routes/#full-declaration)
212 |
213 | the only exceptions is for `url` and `method` which are automatically mapped by project structure.
214 |
215 | ## Example using Javascript version es3
216 |
217 | ```javascript
218 | module.exports = function (fastifyInstance) {
219 | return {
220 | get: {
221 | handler: function (request, reply) {
222 | reply.send('this is get method')
223 | },
224 | },
225 | }
226 | }
227 | ```
228 |
229 | ## Typescript support for modules
230 |
231 | is useful to have typescript for strict type check of the module.
232 | use definition `Resource` for type check your route
233 |
234 | ```typescript
235 | import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'
236 | import { Resource } from 'fastify-autoroutes'
237 |
238 | export default (fastify: FastifyInstance) => {
239 | return {
240 | post: {
241 | handler: async (request: FastifyRequest, reply: FastifyReply) => {
242 | return 'Hello, World!'
243 | },
244 | },
245 | }
246 | }
247 | ```
248 |
249 | ## Contribute
250 |
251 | That's all, i hope you like my little module and contribution of any kind are welcome!
252 |
253 | I will mention you in my README for any of yours contribution
254 |
255 | Consider to leave a :star: if you like the project :blush:
256 |
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | - [README](README.md)
2 |
--------------------------------------------------------------------------------
/docs/googlebe3d4fe76c235fa6.html:
--------------------------------------------------------------------------------
1 | google-site-verification: googlebe3d4fe76c235fa6.html
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Document
6 |
7 |
8 |
12 |
16 |
17 |
18 |
19 |
27 |
31 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["eslint:recommended", "prettier"],
3 | "env": {
4 | "es6": true,
5 | "node": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/simple-js-app/index.js:
--------------------------------------------------------------------------------
1 | import fastify from 'fastify'
2 | import autoroutes from 'fastify-autoroutes'
3 |
4 | const server = fastify()
5 |
6 | server.register(autoroutes, {
7 | dir: './routes',
8 | })
9 |
10 | server.listen(9999)
11 |
--------------------------------------------------------------------------------
/examples/simple-js-app/routes/.ignored-route.js:
--------------------------------------------------------------------------------
1 | export default (fastify) => {
2 | return {
3 | get: {
4 | handler: (request, reply) => {
5 | reply.send('i am ignored')
6 | },
7 | },
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/simple-js-app/routes/_ignored-route.js:
--------------------------------------------------------------------------------
1 | export default (fastify) => {
2 | return {
3 | get: {
4 | handler: (request, reply) => {
5 | reply.send('i am ignored')
6 | },
7 | },
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/simple-js-app/routes/_igored-subpath/index.js:
--------------------------------------------------------------------------------
1 | export default (fastify) => {
2 | return {
3 | get: {
4 | handler: (request, reply) => {
5 | reply.send('i am ignored')
6 | },
7 | },
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/simple-js-app/routes/index.js:
--------------------------------------------------------------------------------
1 | export default (fastify) => {
2 | return {
3 | get: {
4 | handler: (request, reply) => {
5 | reply.send('hello index')
6 | },
7 | },
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/simple-js-app/routes/users/index.js:
--------------------------------------------------------------------------------
1 | export default (fastify) => {
2 | return {
3 | get: {
4 | handler: (request, reply) => {
5 | reply.send('hello users')
6 | },
7 | },
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/simple-js-app/routes/users/{userId}/comments.js:
--------------------------------------------------------------------------------
1 | export default (fastify) => {
2 | return {
3 | get: {
4 | handler: (request, reply) => {
5 | reply.send(`hello user ${request.params.userId} comments`)
6 | },
7 | },
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/simple-js-app/routes/users/{userId}/index.js:
--------------------------------------------------------------------------------
1 | export default (fastify) => {
2 | return {
3 | get: {
4 | handler: (request, reply) => {
5 | reply.send(`hello user ${request.params.userId}`)
6 | },
7 | },
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/simple-ts-app/index.ts:
--------------------------------------------------------------------------------
1 | import fastify from 'fastify'
2 | import autoroutes from 'fastify-autoroutes'
3 |
4 | const server = fastify()
5 |
6 | server.register(autoroutes, {
7 | dir: './routes',
8 | })
9 |
10 | server.listen(9999)
11 |
--------------------------------------------------------------------------------
/examples/simple-ts-app/routes/.ignored-route.ts:
--------------------------------------------------------------------------------
1 | import fastify, { FastifyInstance } from 'fastify'
2 | import { Resource } from '../../../dist'
3 |
4 | export default (fastify) => {
5 | return {
6 | get: {
7 | handler: (request, reply) => {
8 | reply.send('i am ignored')
9 | },
10 | },
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/simple-ts-app/routes/_ignored-route.ts:
--------------------------------------------------------------------------------
1 | import fastify, { FastifyInstance } from 'fastify'
2 | import { Resource } from '../../../dist'
3 |
4 | export default (fastify) => {
5 | return {
6 | get: {
7 | handler: (request, reply) => {
8 | reply.send('i am ignored')
9 | },
10 | },
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/simple-ts-app/routes/_igored-subpath/index.ts:
--------------------------------------------------------------------------------
1 | import fastify, { FastifyInstance } from 'fastify'
2 | import { Resource } from '../../../../dist'
3 |
4 | export default (fastify: FastifyInstance) => {
5 | return {
6 | get: {
7 | handler: (request, reply) => {
8 | reply.send('i am ignored')
9 | },
10 | },
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/simple-ts-app/routes/index.ts:
--------------------------------------------------------------------------------
1 | import fastify, { FastifyInstance } from 'fastify'
2 | import { Resource } from '../../../dist'
3 |
4 | export default (fastify: FastifyInstance) => {
5 | return {
6 | get: {
7 | handler: (request, reply) => {
8 | reply.send('hello index')
9 | },
10 | },
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/simple-ts-app/routes/users/index.ts:
--------------------------------------------------------------------------------
1 | import fastify, { FastifyInstance } from 'fastify'
2 | import { Resource } from '../../../../dist'
3 |
4 | export default (fastify: FastifyInstance) => {
5 | return {
6 | get: {
7 | handler: (request, reply) => {
8 | reply.send('hello users')
9 | },
10 | },
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/simple-ts-app/routes/users/{userId}/comments.ts:
--------------------------------------------------------------------------------
1 | import fastify, { FastifyInstance } from 'fastify'
2 | import { Resource } from '../../../../../dist'
3 |
4 | interface Params {
5 | userId: string
6 | }
7 |
8 | export default (fastify: FastifyInstance) => {
9 | return {
10 | get: {
11 | handler: (request, reply) => {
12 | reply.send(`hello user ${request.params.userId} comments`)
13 | },
14 | },
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/simple-ts-app/routes/users/{userId}/index.ts:
--------------------------------------------------------------------------------
1 | import fastify, { FastifyInstance } from 'fastify'
2 | import { Resource } from '../../../../../dist'
3 |
4 | interface Params {
5 | userId: string
6 | }
7 |
8 | export default (fastify: FastifyInstance) => {
9 | return {
10 | get: {
11 | handler: (request, reply) => {
12 | reply.send(`hello user ${request.params.userId}`)
13 | },
14 | },
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fastify-autoroutes",
3 | "version": "3.0.2",
4 | "description": "Map directory structure to routes",
5 | "main": "dist/index.js",
6 | "prettier": "@giovannicardamone/prettier-config",
7 | "eslintConfig": {
8 | "extends": "@giovannicardamone/eslint-config"
9 | },
10 | "scripts": {
11 | "lint": "eslint",
12 | "build": "tsc",
13 | "build:watch": "tsc --watch",
14 | "test": "jest",
15 | "test:watch": "jest --watchAll",
16 | "test:coverage": "jest --collectCoverage",
17 | "docs": "docsify generate ./docs",
18 | "docs:watch": "docsify serve ./docs",
19 | "prepare": "husky install",
20 | "prepublishOnly": "npm run build"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/GiovanniCardamone/fastify-autoroutes.git"
25 | },
26 | "keywords": [
27 | "fastify",
28 | "fastify-plugin",
29 | "fastify-autoroutes",
30 | "autoroute",
31 | "autorouting",
32 | "autoroutes"
33 | ],
34 | "author": "Giovanni Cardamone",
35 | "license": "MIT",
36 | "bugs": {
37 | "url": "https://github.com/GiovanniCardamone/fastify-autoroutes/issues"
38 | },
39 | "homepage": "https://github.com/GiovanniCardamone/fastify-autoroutes#readme",
40 | "devDependencies": {
41 | "@giovannicardamone/eslint-config": "^0.6.0",
42 | "@giovannicardamone/prettier-config": "^0.1.3",
43 | "@types/jest": "^29.2.0",
44 | "@types/mock-fs": "^4.13.1",
45 | "@types/node": "^14.14.6",
46 | "coveralls": "^3.1.0",
47 | "docsify": "^4.11.4",
48 | "eslint": "^7.6.0",
49 | "fastify": "^4.9.2",
50 | "fastify-plugin": "4.2.1",
51 | "husky": "^7.0.4",
52 | "jest": "^29.2.1",
53 | "light-my-request": "^5.6.1",
54 | "mock-fs": "^5.1.4",
55 | "ts-jest": "^29.0.3",
56 | "typescript": "^4.8.4"
57 | },
58 | "dependencies": {
59 | "@fastify/error": "^3.0.0",
60 | "fastify-plugin": "^4.0.0",
61 | "glob-promise": "^4.2.2"
62 | },
63 | "peerDependencies": {
64 | "fastify": "^4.0.0"
65 | },
66 | "files": [
67 | "dist"
68 | ],
69 | "contributors": [
70 | {
71 | "name": "Giovanni Cardamone",
72 | "email": "g.cardamone2@gmail.com",
73 | "url": "https://giovannicardamone.github.io"
74 | },
75 | {
76 | "name": "Gennaro Bosone",
77 | "email": "gennaro.bs@gmail.com",
78 | "url": "https://www.desidus.it"
79 | }
80 | ]
81 | }
82 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import createError from '@fastify/error'
2 | import type { FastifyInstance, RouteOptions } from 'fastify'
3 | import fastifyPlugin from 'fastify-plugin'
4 | import fs from 'fs'
5 | import glob from 'glob-promise'
6 | import path from 'path'
7 | import process from 'process'
8 |
9 |
10 | export const ERROR_LABEL = 'fastify-autoroutes'
11 |
12 | export type ValidMethods =
13 | | 'DELETE'
14 | | 'GET'
15 | | 'HEAD'
16 | | 'PATCH'
17 | | 'POST'
18 | | 'PUT'
19 | | 'OPTIONS'
20 |
21 | // const validMethods = [
22 | // 'delete',
23 | // 'get',
24 | // 'head',
25 | // 'patch',
26 | // 'post',
27 | // 'put',
28 | // 'options',
29 | // ]
30 |
31 | export type AnyRoute = Omit
32 |
33 | export type DeleteRoute = AnyRoute
34 | export type GetRoute = Omit
35 | export type HeadRoute = AnyRoute
36 | export type PatchRoute = AnyRoute
37 | export type PostRoute = AnyRoute
38 | export type PutRoute = AnyRoute
39 | export type OptionsRoute = AnyRoute
40 |
41 | interface Security {
42 | [key: string]: string[]
43 | }
44 |
45 | interface StrictAnyRoute extends AnyRoute {
46 | schema: {
47 | summary?: string
48 | description?: string
49 | security?: Security[]
50 | tags?: string[]
51 | consumes?: string[]
52 | produces?: string[]
53 | body?: any
54 | querystring?: any
55 | params?: any
56 | headers?: any
57 | response?: { [key: number]: any }
58 | }
59 | }
60 |
61 | export type StrictDeleteRoute = StrictAnyRoute
62 | export type StrictGetRoute = Omit
63 | export type StrictHeadRoute = StrictAnyRoute
64 | export type StrictPatchRoute = StrictAnyRoute
65 | export type StrictPostRoute = StrictAnyRoute
66 | export type StrictPutRoute = StrictAnyRoute
67 | export type StrictOptionsRoute = StrictAnyRoute
68 |
69 | export interface Resource {
70 | delete?: DeleteRoute
71 | get?: GetRoute
72 | head?: HeadRoute
73 | patch?: PatchRoute
74 | post?: PostRoute
75 | put?: PutRoute
76 | options?: OptionsRoute
77 | }
78 |
79 | export interface StrictResource {
80 | delete?: StrictDeleteRoute
81 | get?: StrictGetRoute
82 | head?: StrictHeadRoute
83 | patch?: StrictPatchRoute
84 | post?: StrictPostRoute
85 | put?: StrictPutRoute
86 | options?: StrictOptionsRoute
87 | }
88 |
89 | interface FastifyAutoroutesOptions {
90 | dir?: string
91 | prefix?: string
92 | }
93 |
94 | export default fastifyPlugin(
95 | async (
96 | fastify: FastifyInstance,
97 | options: FastifyAutoroutesOptions,
98 | next: CallableFunction
99 | ) => {
100 | const { dir, prefix: routePrefix } = {
101 | ...options,
102 | dir: options.dir || './routes',
103 | prefix: options.prefix || '',
104 | }
105 |
106 | let dirPath: string
107 |
108 | if (path.isAbsolute(dir)) {
109 | dirPath = dir
110 | } else if (path.isAbsolute(process.argv[1])) {
111 | dirPath = path.join(process.argv[1], dir)
112 | } else {
113 | dirPath = path.join(process.cwd(), process.argv[1], dir)
114 | }
115 |
116 | if (!fs.existsSync(dirPath)) {
117 | const CustomError = createError(
118 | '1',
119 | `${ERROR_LABEL} dir ${dirPath} must be a directory`
120 | )
121 | return next(new CustomError())
122 | }
123 |
124 | if (!fs.statSync(dirPath).isDirectory()) {
125 | const CustomError = createError(
126 | '2',
127 | `${ERROR_LABEL} dir ${dirPath} must be a directory`
128 | )
129 | return next(new CustomError())
130 | }
131 |
132 | // glob returns ../../, but windows returns ..\..\
133 | dirPath = path.normalize(dirPath).replace(/\\/g, '/')
134 |
135 | const routes = await glob(`${dirPath}/**/[!._]!(*.test).{ts,js}`)
136 | const routesModules: Record = {}
137 |
138 | // console.log({ routes })
139 |
140 | for (const route of routes) {
141 | let routeName = route
142 | .replace(dirPath, '')
143 | .replace('.js', '')
144 | .replace('.ts', '')
145 | .replace('index', '')
146 | .split('/')
147 | .map((part) => part.replace(/{(.+)}/g, ':$1'))
148 | .join('/')
149 |
150 | routeName = !routeName ? '/' : `${routePrefix}${routeName}`
151 |
152 | // console.log({ routeName })
153 |
154 | routesModules[routeName] = loadModule(routeName, route)(fastify)
155 | }
156 |
157 | for (const [url, module] of Object.entries(routesModules)) {
158 | for (const [method, options] of Object.entries(module)) {
159 | fastify.route({
160 | method: method.toUpperCase(),
161 | url: url,
162 | ...options,
163 | })
164 | }
165 | }
166 | },
167 | {
168 | fastify: '>=4.0.0',
169 | name: 'fastify-autoroutes',
170 | }
171 | )
172 |
173 | function loadModule(
174 | name: string,
175 | path: string
176 | ): (instance: FastifyInstance) => StrictResource {
177 | // eslint-disable-next-line @typescript-eslint/no-var-requires
178 | const module = require(path)
179 |
180 | if (typeof module === 'function') {
181 | return module as (instance: any) => StrictResource
182 | }
183 |
184 | if (
185 | typeof module === 'object' &&
186 | 'default' in module &&
187 | typeof module.default === 'function'
188 | ) {
189 | return module.default as (instance: any) => StrictResource
190 | }
191 |
192 | throw new Error(
193 | `${ERROR_LABEL}: invalid route module definition (${name}) ${path}. Must export a function`
194 | )
195 | }
196 |
--------------------------------------------------------------------------------
/tests/invalid-routes.test.ts:
--------------------------------------------------------------------------------
1 | import fastify from 'fastify'
2 | import path from 'path'
3 | import autoroutes, { ERROR_LABEL } from '../src'
4 | import { mock, restore } from './utils/mock'
5 |
6 | const exampleErrorModule = `
7 | thisSyntaxIsInvalid :(
8 | `
9 |
10 | const exampleInvalidModule = `
11 | var whateverIdontCare = 3
12 | `
13 |
14 |
15 | describe('Invalid Routes', () => {
16 |
17 | beforeEach(() => {
18 | //
19 | })
20 |
21 | afterEach(() => {
22 | restore()
23 | })
24 |
25 | test('invalid type routes directory', (done) => {
26 | const server = fastify()
27 |
28 | const dir = mock('dir', {
29 | dirAsFile: '',
30 | })
31 |
32 | server.register(autoroutes, {
33 | dir: path.join(dir, 'dirAsFile'),
34 | })
35 |
36 | server.inject(
37 | {
38 | method: 'GET',
39 | url: '/',
40 | },
41 | (err, res) => {
42 | expect(err.message.startsWith(ERROR_LABEL)).toBeTruthy()
43 | done()
44 | }
45 | )
46 | })
47 |
48 | test('empty routes module', (done) => {
49 | const server = fastify()
50 |
51 | const dir = mock('dir', {
52 | 'index.js': '', // empty
53 | })
54 |
55 | server.register(autoroutes, {
56 | dir,
57 | })
58 |
59 | server.inject(
60 | {
61 | method: 'GET',
62 | url: '/',
63 | },
64 | (err, res) => {
65 | expect(err.message.startsWith(ERROR_LABEL)).toBeTruthy()
66 | done()
67 | }
68 | )
69 | })
70 |
71 | test('modules with error', (done) => {
72 | const server = fastify()
73 |
74 | const dir = mock('dir', {
75 | 'index.js': exampleErrorModule,
76 | })
77 |
78 | server.register(autoroutes, {
79 | dir,
80 | })
81 |
82 | server.inject(
83 | {
84 | method: 'GET',
85 | url: '/',
86 | },
87 | (err, res) => {
88 | expect(err).toBeInstanceOf(Error)
89 | done()
90 | }
91 | )
92 | })
93 |
94 | test('modules without valid routes', (done) => {
95 | const server = fastify()
96 |
97 | const dir = mock('dir', {
98 | 'index.js': exampleInvalidModule,
99 | })
100 |
101 | server.register(autoroutes, {
102 | dir,
103 | })
104 |
105 | server.inject(
106 | {
107 | method: 'GET',
108 | url: '/',
109 | },
110 | (err, res) => {
111 | expect(err.message.startsWith(ERROR_LABEL)).toBeTruthy()
112 | done()
113 | }
114 | )
115 | })
116 | })
117 |
118 |
--------------------------------------------------------------------------------
/tests/options.test.ts:
--------------------------------------------------------------------------------
1 | import fastify from 'fastify'
2 | import autoroutes, { ERROR_LABEL } from '../src'
3 | import { restore } from './utils/mock'
4 |
5 |
6 | describe('Options', () => {
7 |
8 | beforeEach(() => {
9 | //
10 | })
11 |
12 | afterEach(() => {
13 | restore()
14 | })
15 |
16 | test('no dir parameters', (done) => {
17 | const server = fastify()
18 |
19 | server.register(autoroutes)
20 |
21 | server.inject(
22 | {
23 | method: 'GET',
24 | url: '/does-not-really-matter',
25 | },
26 | (error) => {
27 | expect(error.message.startsWith(ERROR_LABEL)).toBeTruthy()
28 | done()
29 | }
30 | )
31 | })
32 |
33 | test('invalid dir parameters', (done) => {
34 | const server = fastify()
35 |
36 | // @ts-expect-error not valid 33 as dir
37 | server.register(autoroutes, {
38 | dir: 33,
39 | })
40 |
41 | server.inject(
42 | {
43 | method: 'GET',
44 | url: '/does-not-really-matter',
45 | },
46 | (error, res) => {
47 | console.log({ error, res })
48 | expect(error).not.toBe(undefined)
49 | done()
50 | }
51 | )
52 | })
53 |
54 | test('dir does not exists', (done) => {
55 | const server = fastify()
56 |
57 | server.register(autoroutes, {
58 | dir: './this-directory-does-not-exists',
59 | })
60 |
61 | server.inject(
62 | {
63 | method: 'GET',
64 | url: '/does-not-really-matter',
65 | },
66 | (error) => {
67 | expect(error.message.startsWith(ERROR_LABEL)).toBeTruthy()
68 | done()
69 | }
70 | )
71 | })
72 | })
73 |
--------------------------------------------------------------------------------
/tests/routes.test.ts:
--------------------------------------------------------------------------------
1 | import fastify from 'fastify'
2 | import autoroutes from '../src'
3 | import { mock, restore } from './utils/mock'
4 |
5 | const exampleGetRoute = `module.exports = function (server) {
6 | return {
7 | get: {
8 | handler: function (request, reply) {
9 | return reply.send('get')
10 | }
11 | }
12 | }
13 | }
14 | `
15 |
16 | const exampleGetRouteUrlParam = `module.exports = function (server) {
17 | return {
18 | get: {
19 | handler: function (request, reply) {
20 | return reply.send(request.params.PARAM)
21 | }
22 | }
23 | }
24 | }
25 | `
26 |
27 | const exampleGetRouteJSONParam = `module.exports = function (server) {
28 | return {
29 | get: {
30 | handler: function (request, reply) {
31 | return reply.send(JSON.stringify(request.params))
32 | }
33 | }
34 | }
35 | }
36 | `
37 |
38 | const exampleGetRouteDefaultModule = `
39 | 'use strict';
40 |
41 | Object.defineProperty(exports, "__esModule", {
42 | value: true
43 | });
44 |
45 | exports.default = function () {
46 | return {
47 | get: {
48 | handler: function (request, reply) {
49 | return reply.send('get')
50 | }
51 | }
52 | };
53 | };
54 | `
55 | describe('Routes', () => {
56 |
57 | beforeEach(() => {
58 | //
59 | })
60 |
61 | afterEach(() => {
62 | restore()
63 | })
64 |
65 | test('simple index', (done) => {
66 | const server = fastify()
67 |
68 | const dir = mock('routes', {
69 | 'index.js': exampleGetRoute,
70 | })
71 |
72 | server.register(autoroutes, {
73 | dir
74 | })
75 |
76 | server.inject(
77 | {
78 | method: 'GET',
79 | url: '/',
80 | },
81 | (err, res) => {
82 | expect(err).toBe(null)
83 | expect(res.payload).toBe('get')
84 | done()
85 | }
86 | )
87 | })
88 |
89 | test('nested routes', (done) => {
90 | const server = fastify()
91 |
92 | const dir = mock('routes', {
93 | users: {
94 | 'foo.js': exampleGetRoute,
95 | },
96 | })
97 |
98 | server.register(autoroutes, {
99 | dir
100 | })
101 |
102 | server.inject(
103 | {
104 | method: 'GET',
105 | url: '/users/foo',
106 | },
107 | (err, res) => {
108 | expect(err).toBe(null)
109 | expect(res.payload).toBe('get')
110 | done()
111 | }
112 | )
113 | })
114 |
115 | test('nested routes with trailing slashes', (done) => {
116 | const server = fastify()
117 |
118 | const dir = mock('routes', {
119 | users: {
120 | foo: {
121 | 'index.js': exampleGetRoute,
122 | },
123 | },
124 | })
125 |
126 | server.register(autoroutes, {
127 | dir
128 | })
129 |
130 | server.inject(
131 | {
132 | method: 'GET',
133 | url: '/users/foo/',
134 | },
135 | (err, res) => {
136 | expect(err).toBe(null)
137 | expect(res.payload).toBe('get')
138 | done()
139 | }
140 | )
141 | })
142 |
143 | test('nested routes with url parameter', (done) => {
144 | const server = fastify()
145 |
146 | const dir = mock('routes', {
147 |
148 | users: {
149 | '{PARAM}.js': exampleGetRouteUrlParam,
150 | },
151 | })
152 |
153 | server.register(autoroutes, {
154 | dir
155 | })
156 |
157 | const userId = 'foo'
158 |
159 | server.inject(
160 | {
161 | method: 'GET',
162 | url: `/users/${userId}`,
163 | },
164 | (err, res) => {
165 | expect(err).toBe(null)
166 | expect(res.payload).toBe(userId)
167 | done()
168 | }
169 | )
170 | })
171 |
172 | test(
173 | 'url parameters with : (not on windows)', (done) => {
174 | if (process.platform === 'win32') {
175 | done()
176 | } else {
177 | const server = fastify()
178 |
179 | const dir = mock('routes', {
180 | users: {
181 | '{USERID}.js': exampleGetRouteJSONParam,
182 | '{USERID}': {
183 | 'index.js': exampleGetRouteJSONParam,
184 | },
185 | },
186 | })
187 |
188 | server.register(autoroutes, {
189 | dir,
190 | })
191 |
192 | const USERID = 'foo'
193 |
194 | server.inject(
195 | {
196 | method: 'GET',
197 | url: `/users/${USERID}`,
198 | },
199 | (err, res) => {
200 | expect(err).toBe(null)
201 | expect(JSON.parse(res.payload).USERID).toBe(USERID)
202 |
203 | server.inject(
204 | {
205 | method: 'GET',
206 | url: `/users/${USERID}/`,
207 | },
208 | (err, res) => {
209 | expect(err).toBe(null)
210 | expect(JSON.parse(res.payload).USERID).toBe(USERID)
211 | done()
212 | }
213 | )
214 | }
215 | )
216 | }
217 | }
218 | )
219 |
220 | test(
221 | 'nested routes with url parameter with trailing slashes', (done) => {
222 | const server = fastify()
223 |
224 | const dir = mock('dir', {
225 | users: {
226 | '{PARAM}': {
227 | 'index.js': exampleGetRouteUrlParam,
228 | },
229 | },
230 | })
231 |
232 | server.register(autoroutes, {
233 | dir,
234 | })
235 |
236 | const userId = 'foo'
237 |
238 | server.inject(
239 | {
240 | method: 'GET',
241 | url: `/users/${userId}/`,
242 | },
243 | (err, res) => {
244 | expect(err).toBe(null)
245 | expect(res.payload).toBe(userId)
246 | done()
247 | }
248 | )
249 | }
250 | )
251 |
252 | test('example es6 exports default module', (done) => {
253 | const server = fastify()
254 |
255 | const dir = mock('dir', {
256 | 'index.js': exampleGetRouteDefaultModule,
257 | })
258 |
259 | server.register(autoroutes, {
260 | dir
261 | })
262 |
263 | server.inject(
264 | {
265 | method: 'GET',
266 | url: '/',
267 | },
268 | (err, res) => {
269 | expect(err).toBe(null)
270 | expect(res.payload).toBe('get')
271 | done()
272 | }
273 | )
274 | })
275 |
276 | test(
277 | 'skip routes with starting . charater', (done) => {
278 | const server = fastify()
279 |
280 | const dir = mock('dir', {
281 |
282 | '.hello.js': exampleGetRouteDefaultModule,
283 | })
284 |
285 | server.register(autoroutes, {
286 | dir,
287 | })
288 |
289 | server.inject(
290 | {
291 | method: 'GET',
292 | url: '/hello',
293 | },
294 | (err, res) => {
295 | expect(res.statusCode).toBe(404)
296 |
297 | server.inject(
298 | {
299 | method: 'GET',
300 | url: '/.hello',
301 | },
302 | (err, res) => {
303 | expect(res.statusCode).toBe(404)
304 |
305 | done()
306 | }
307 | )
308 | }
309 | )
310 | }
311 | )
312 |
313 | test(
314 | 'skip routes with starting _ charater', (done) => {
315 | const server = fastify()
316 |
317 | const dir = mock('dir', {
318 | '_hello.js': exampleGetRouteDefaultModule,
319 |
320 | })
321 |
322 | server.register(autoroutes, {
323 | dir
324 | })
325 |
326 | server.inject(
327 | {
328 | method: 'GET',
329 | url: '/hello',
330 | },
331 | (err, res) => {
332 | expect(res.statusCode).toBe(404)
333 |
334 | server.inject(
335 | {
336 | method: 'GET',
337 | url: '/_hello',
338 | },
339 | (err, res) => {
340 | expect(res.statusCode).toBe(404)
341 | done()
342 | }
343 | )
344 | }
345 | )
346 | }
347 | )
348 |
349 | test('skip routes ending with .test.js or .test.ts', (done) => {
350 | const server = fastify()
351 |
352 | const dir = mock('dir', {
353 | 'someJsRoute.test.js': exampleGetRouteDefaultModule,
354 | 'someTsRoute.test.ts': exampleGetRouteDefaultModule,
355 | })
356 |
357 | server.register(autoroutes, {
358 | dir
359 | })
360 |
361 | server.inject(
362 | {
363 | method: 'GET',
364 | url: '/someJsRoute',
365 | },
366 | (err, res) => {
367 | expect(res.statusCode).toBe(404)
368 |
369 | server.inject(
370 | {
371 | method: 'GET',
372 | url: '/someTsRoute',
373 | },
374 | (err, res) => {
375 | expect(res.statusCode).toBe(404)
376 | done()
377 | }
378 | )
379 | }
380 | )
381 | })
382 |
383 | test('expect route /status to work', (done) => {
384 | const server = fastify()
385 |
386 | const dir = mock('routes', {
387 | a: {'status.js': exampleGetRoute, }
388 | })
389 |
390 | server.register(autoroutes, {
391 | dir
392 | })
393 |
394 | server.inject(
395 | {
396 | method: 'GET',
397 | url: '/a/status',
398 | },
399 | (err, res) => {
400 | expect(err).toBe(null)
401 | expect(res.payload).toBe('get')
402 | done()
403 | }
404 | )
405 | })
406 | })
407 |
--------------------------------------------------------------------------------
/tests/utils/mock.ts:
--------------------------------------------------------------------------------
1 | import mockFs from 'mock-fs'
2 | import path from 'path'
3 |
4 | export function mock(dir: string, filesystem: { [key: string]: any }): string {
5 |
6 |
7 | mockFs({
8 | [dir]: {
9 | ...filesystem,
10 | },
11 | 'node_modules': mockFs.load(path.resolve(process.cwd(), 'node_modules')),
12 | }, { createCwd: true })
13 |
14 | return path.join(process.cwd(), dir)
15 | }
16 |
17 | export function restore() {
18 | mockFs.restore()
19 | }
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | "incremental": true, /* Enable incremental compilation */
7 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 | "lib": [
10 | "es2021"
11 | ], /* Specify library files to be included in the compilation. */
12 | // "allowJs": true, /* Allow javascript files to be compiled. */
13 | // "checkJs": true, /* Report errors in .js files. */
14 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
15 | "declaration": true, /* Generates corresponding '.d.ts' file. */
16 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
17 | "sourceMap": true, /* Generates corresponding '.map' file. */
18 | // "outFile": "./", /* Concatenate and emit output to single file. */
19 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
20 | "outDir": "./dist", /* Redirect output structure to the directory. */
21 | // "composite": true, /* Enable project compilation */
22 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
23 | // "removeComments": true, /* Do not emit comments to output. */
24 | // "noEmit": true, /* Do not emit outputs. */
25 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
28 |
29 | /* Strict Type-Checking Options */
30 | "strict": true, /* Enable all strict type-checking options. */
31 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
32 | // "strictNullChecks": true, /* Enable strict null checks. */
33 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
34 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
35 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
36 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
38 |
39 | /* Additional Checks */
40 | // "noUnusedLocals": true, /* Report errors on unused locals. */
41 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
42 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
43 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
44 |
45 | /* Module Resolution Options */
46 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
50 | // "typeRoots": [], /* List of folders to include type definitions from. */
51 | // "types": [], /* Type declaration files to be included in compilation. */
52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
56 |
57 | /* Source Map Options */
58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
62 |
63 | /* Experimental Options */
64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
66 |
67 | /* Advanced Options */
68 | "skipLibCheck": true, /* Skip type checking of declaration files. */
69 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
70 | },
71 | "include": ["src"],
72 | "exclude": ["examples", "tests", "dist"]
73 | }
74 |
--------------------------------------------------------------------------------