├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── benchmarks
├── call.js
├── express.js
├── find-my-way.js
├── koa-router.js
├── koa-tree-router.js
├── router.js
├── routr.js
├── server-router.js
└── trek-router.js
├── package.json
├── runner.js
└── utils.js
/.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 | # mac files
61 | .DS_Store
62 |
63 | # vim swap files
64 | *.swp
65 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "8"
5 |
6 | notifications:
7 | email:
8 | on_success: never
9 | on_failure: always
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Tomas Della Vedova
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 | # router-benchmark
2 |
3 | [](http://standardjs.com/) [](https://travis-ci.org/delvedor/router-benchmark)
4 |
5 | Benchmark of the most commonly used http routers.
6 |
7 | Tested routers:
8 |
9 | - [find-my-way](https://github.com/delvedor/find-my-way)
10 | - [call](https://github.com/hapijs/call)
11 | - [express](https://www.npmjs.com/package/express)
12 | - [koa-router](https://github.com/alexmingoia/koa-router)
13 | - [koa-tree-router](https://github.com/steambap/koa-tree-router)
14 | - [router](https://github.com/pillarjs/router)
15 | - [routr](https://github.com/yahoo/routr)
16 | - [server-router](https://github.com/yoshuawuyts/server-router)
17 | - [trek-router](https://www.npmjs.com/package/trek-router)
18 |
19 | This benchmarks aims to test only http routers, so the method handling should be included.
20 | Do you know other routers? [PR](https://github.com/delvedor/router-benchmark/pulls)! :D
21 |
22 |
23 | ## Results
24 | *These benchmarks where taken under node v8.9.0, on a MacBook Pro Retina Late 2013 (i7, 16GB of RAM).*
25 |
26 | ```
27 | =======================
28 | find-my-way benchmark
29 | =======================
30 | short static: 10,102,066 ops/sec
31 | static with same radix: 3,887,679 ops/sec
32 | dynamic route: 1,637,929 ops/sec
33 | mixed static dynamic: 2,289,554 ops/sec
34 | long static: 5,403,719 ops/sec
35 | wildcard: 3,037,119 ops/sec
36 | all together: 525,798 ops/sec
37 |
38 | ================
39 | call benchmark
40 | ================
41 | short static: 3,123,503 ops/sec
42 | static with same radix: 3,094,106 ops/sec
43 | dynamic route: 578,251 ops/sec
44 | mixed static dynamic: 632,624 ops/sec
45 | long static: 3,491,147 ops/sec
46 | wildcard: 884,869 ops/sec
47 | all together: 181,587 ops/sec
48 |
49 | ================================================
50 | express benchmark (WARNING: includes handling)
51 | ================================================
52 | short static: 1,145,409 ops/sec
53 | static with same radix: 1,102,656 ops/sec
54 | dynamic route: 595,169 ops/sec
55 | mixed static dynamic: 513,327 ops/sec
56 | long static: 642,545 ops/sec
57 | wildcard: 407,398 ops/sec
58 | all together: 100,184 ops/sec
59 |
60 | ======================
61 | koa-router benchmark
62 | ======================
63 | short static: 1,004,122 ops/sec
64 | static with same radix: 1,029,369 ops/sec
65 | dynamic route: 1,015,635 ops/sec
66 | mixed static dynamic: 968,784 ops/sec
67 | long static: 1,027,857 ops/sec
68 | wildcard: 1,033,432 ops/sec
69 | all together: 161,220 ops/sec
70 |
71 | ===========================
72 | koa-tree-router benchmark
73 | ===========================
74 | short static: 11,756,182 ops/sec
75 | static with same radix: 6,212,981 ops/sec
76 | dynamic route: 3,221,744 ops/sec
77 | mixed static dynamic: 4,160,595 ops/sec
78 | long static: 7,723,753 ops/sec
79 | wildcard: 4,469,051 ops/sec
80 | all together: 924,587 ops/sec
81 |
82 | ===============================================
83 | router benchmark (WARNING: includes handling)
84 | ===============================================
85 | short static: 1,176,121 ops/sec
86 | static with same radix: 1,110,484 ops/sec
87 | dynamic route: 628,130 ops/sec
88 | mixed static dynamic: 536,107 ops/sec
89 | long static: 678,598 ops/sec
90 | wildcard: 356,475 ops/sec
91 | all together: 99,443 ops/sec
92 |
93 | =================
94 | routr benchmark
95 | =================
96 | short static: 4,562,784 ops/sec
97 | static with same radix: 2,530,725 ops/sec
98 | dynamic route: 850,739 ops/sec
99 | mixed static dynamic: 553,497 ops/sec
100 | long static: 520,461 ops/sec
101 | wildcard: 348,495 ops/sec
102 | all together: 108,098 ops/sec
103 |
104 | =========================
105 | server-router benchmark
106 | =========================
107 | short static: 2,500,623 ops/sec
108 | static with same radix: 2,404,634 ops/sec
109 | dynamic route: 1,102,154 ops/sec
110 | mixed static dynamic: 1,094,229 ops/sec
111 | long static: 1,555,080 ops/sec
112 | wildcard: 924,381 ops/sec
113 | all together: 215,779 ops/sec
114 |
115 | =======================
116 | trek-router benchmark
117 | =======================
118 | short static: 8,530,466 ops/sec
119 | static with same radix: 4,861,846 ops/sec
120 | dynamic route: 2,240,906 ops/sec
121 | mixed static dynamic: 2,539,650 ops/sec
122 | long static: 5,525,210 ops/sec
123 | wildcard: 3,533,566 ops/sec
124 | all together: 612,419 ops/sec
125 | ```
126 |
127 | ### Run the benchmarks
128 | Do you wan to run the benchmarks by yourself?
129 | Run the following:
130 | ```bash
131 | git clone https://github.com/delvedor/router-benchmark
132 | cd router-benchmark
133 | npm i
134 | npm start
135 | ```
136 |
137 |
138 | ## Router features
139 | | Router | Framework independent | Decode URI | Querystring handling | Regex route support | Multi-parametric route support | Max parameter length |
140 | | :------------ | :------------ | :------------ | :--------------------- | :------------------- |:------------------------------ |:--------------------- |
141 | | `find-my-way` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
142 | | `call` | ✓ | ✓ | ✗ | ? | ? | ? |
143 | | `express` | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ |
144 | | `koa-router` | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ |
145 | | `koa-tree-router` | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
146 | | `router` | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
147 | | `routr` | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
148 | | `server-router` | ✓ | ✓ | ✗ | ✗| ✗ | ✗ |
149 | | `trek-router` | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
150 |
151 | *Did you find incorrect data in the above table? Please send a pr!*
152 |
153 |
154 | ## How the benchmark is taken
155 |
156 | To emulate a real world situation every router registers the following routes:
157 | ```
158 | { method: 'GET', url: '/user' },
159 | { method: 'GET', url: '/user/comments' },
160 | { method: 'GET', url: '/user/avatar' },
161 | { method: 'GET', url: '/user/lookup/username/:username' },
162 | { method: 'GET', url: '/user/lookup/email/:address' },
163 | { method: 'GET', url: '/event/:id' },
164 | { method: 'GET', url: '/event/:id/comments' },
165 | { method: 'POST', url: '/event/:id/comment' },
166 | { method: 'GET', url: '/map/:location/events' },
167 | { method: 'GET', url: '/status' },
168 | { method: 'GET', url: '/very/deeply/nested/route/hello/there' },
169 | { method: 'GET', url: '/static/*' }
170 | ```
171 | Then the following routes are tested:
172 | ```
173 | short static: { method: 'GET', url: '/user' }
174 | static with same radix: { method: 'GET', url: '/user/comments' }
175 | dynamic route: { method: 'GET', url: '/user/lookup/username/john' }
176 | mixed static dynamic: { method: 'GET', url: '/event/abcd1234/comments' },
177 | long static: { method: 'GET', url: '/very/deeply/nested/route/hello/there' },
178 | wildcard: { method: 'GET', url: '/static/index.html' }
179 | all together: all the above at the same time
180 | ```
181 | Every test is executed 1 million times, the time is taken with `process.hrtime()`, the final result is expressed in operations per second.
182 |
183 |
184 | ### TODO:
185 | - [ ] Add a list of the supported features by every router
186 |
187 |
188 | ## License
189 |
190 | [MIT](https://github.com/delvedor/router-benchmark/blob/master/LICENSE)
191 |
192 | Copyright © 2017 Tomas Della Vedova
193 |
--------------------------------------------------------------------------------
/benchmarks/call.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { title, now, print, operations } = require('../utils')
4 | const call = require('call')
5 | const router = new call.Router()
6 |
7 | title('call benchmark')
8 |
9 | const routes = [
10 | { method: 'GET', path: '/user' },
11 | { method: 'GET', path: '/user/comments' },
12 | { method: 'GET', path: '/user/avatar' },
13 | { method: 'GET', path: '/user/lookup/username/{username}' },
14 | { method: 'GET', path: '/user/lookup/email/{address}' },
15 | { method: 'GET', path: '/event/{id}' },
16 | { method: 'GET', path: '/event/{id}/comments' },
17 | { method: 'POST', path: '/event/{id}/comment' },
18 | { method: 'GET', path: '/map/{location}/events' },
19 | { method: 'GET', path: '/status' },
20 | { method: 'GET', path: '/very/deeply/nested/route/hello/there' },
21 | { method: 'GET', path: '/static/{file*}' }
22 | ]
23 |
24 | function noop () {}
25 | var i = 0
26 | var time = 0
27 |
28 | routes.forEach(route => {
29 | router.add(route, noop)
30 | })
31 |
32 | time = now()
33 | for (i = 0; i < operations; i++) {
34 | router.route('get', '/user')
35 | }
36 | print('short static:', time)
37 |
38 | time = now()
39 | for (i = 0; i < operations; i++) {
40 | router.route('get', '/user/comments')
41 | }
42 | print('static with same radix:', time)
43 |
44 | time = now()
45 | for (i = 0; i < operations; i++) {
46 | router.route('get', '/user/lookup/username/john')
47 | }
48 | print('dynamic route:', time)
49 |
50 | time = now()
51 | for (i = 0; i < operations; i++) {
52 | router.route('get', '/event/abcd1234/comments')
53 | }
54 | print('mixed static dynamic:', time)
55 |
56 | time = now()
57 | for (i = 0; i < operations; i++) {
58 | router.route('get', '/very/deeply/nested/route/hello/there')
59 | }
60 | print('long static:', time)
61 |
62 | time = now()
63 | for (i = 0; i < operations; i++) {
64 | router.route('get', '/static/index.html')
65 | }
66 | print('wildcard:', time)
67 |
68 | time = now()
69 | for (i = 0; i < operations; i++) {
70 | router.route('get', '/user')
71 | router.route('get', '/user/comments')
72 | router.route('get', '/user/lookup/username/john')
73 | router.route('get', '/event/abcd1234/comments')
74 | router.route('get', '/very/deeply/nested/route/hello/there')
75 | router.route('get', '/static/index.html')
76 | }
77 | print('all together:', time)
78 |
--------------------------------------------------------------------------------
/benchmarks/express.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { title, now, print, operations } = require('../utils')
4 | const router = require('express/lib/router')()
5 |
6 | title('express benchmark (WARNING: includes handling)')
7 |
8 | const routes = [
9 | { method: 'GET', url: '/user' },
10 | { method: 'GET', url: '/user/comments' },
11 | { method: 'GET', url: '/user/avatar' },
12 | { method: 'GET', url: '/user/lookup/username/:username' },
13 | { method: 'GET', url: '/user/lookup/email/:address' },
14 | { method: 'GET', url: '/event/:id' },
15 | { method: 'GET', url: '/event/:id/comments' },
16 | { method: 'POST', url: '/event/:id/comment' },
17 | { method: 'GET', url: '/map/:location/events' },
18 | { method: 'GET', url: '/status' },
19 | { method: 'GET', url: '/very/deeply/nested/route/hello/there' },
20 | { method: 'GET', url: '/static/*' }
21 | ]
22 |
23 | function noop () {}
24 | var i = 0
25 | var time = 0
26 |
27 | routes.forEach(route => {
28 | if (route.method === 'GET') {
29 | router.route(route.url).get(noop)
30 | } else {
31 | router.route(route.url).post(noop)
32 | }
33 | })
34 |
35 | time = now()
36 | for (i = 0; i < operations; i++) {
37 | router.handle({ method: 'GET', url: '/user' })
38 | }
39 | print('short static:', time)
40 |
41 | time = now()
42 | for (i = 0; i < operations; i++) {
43 | router.handle({ method: 'GET', url: '/user/comments' })
44 | }
45 | print('static with same radix:', time)
46 |
47 | time = now()
48 | for (i = 0; i < operations; i++) {
49 | router.handle({ method: 'GET', url: '/user/lookup/username/john' })
50 | }
51 | print('dynamic route:', time)
52 |
53 | time = now()
54 | for (i = 0; i < operations; i++) {
55 | router.handle({ method: 'GET', url: '/event/abcd1234/comments' }, null, noop)
56 | }
57 | print('mixed static dynamic:', time)
58 |
59 | time = now()
60 | for (i = 0; i < operations; i++) {
61 | router.handle({ method: 'GET', url: '/very/deeply/nested/route/hello/there' }, null, noop)
62 | }
63 | print('long static:', time)
64 |
65 | time = now()
66 | for (i = 0; i < operations; i++) {
67 | router.handle({ method: 'GET', url: '/static/index.html' }, null, noop)
68 | }
69 | print('wildcard:', time)
70 |
71 | time = now()
72 | for (i = 0; i < operations; i++) {
73 | router.handle({ method: 'GET', url: '/user' })
74 | router.handle({ method: 'GET', url: '/user/comments' })
75 | router.handle({ method: 'GET', url: '/user/lookup/username/john' })
76 | router.handle({ method: 'GET', url: '/event/abcd1234/comments' }, null, noop)
77 | router.handle({ method: 'GET', url: '/very/deeply/nested/route/hello/there' }, null, noop)
78 | router.handle({ method: 'GET', url: '/static/index.html' }, null, noop)
79 | }
80 | print('all together:', time)
81 |
--------------------------------------------------------------------------------
/benchmarks/find-my-way.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { title, now, print, operations } = require('../utils')
4 | const router = require('find-my-way')()
5 |
6 | title('find-my-way benchmark')
7 |
8 | const routes = [
9 | { method: 'GET', url: '/user' },
10 | { method: 'GET', url: '/user/comments' },
11 | { method: 'GET', url: '/user/avatar' },
12 | { method: 'GET', url: '/user/lookup/username/:username' },
13 | { method: 'GET', url: '/user/lookup/email/:address' },
14 | { method: 'GET', url: '/event/:id' },
15 | { method: 'GET', url: '/event/:id/comments' },
16 | { method: 'POST', url: '/event/:id/comment' },
17 | { method: 'GET', url: '/map/:location/events' },
18 | { method: 'GET', url: '/status' },
19 | { method: 'GET', url: '/very/deeply/nested/route/hello/there' },
20 | { method: 'GET', url: '/static/*' }
21 | ]
22 |
23 | function noop () {}
24 | var i = 0
25 | var time = 0
26 |
27 | routes.forEach(route => {
28 | router.on(route.method, route.url, noop)
29 | })
30 |
31 | time = now()
32 | for (i = 0; i < operations; i++) {
33 | router.find('GET', '/user')
34 | }
35 | print('short static:', time)
36 |
37 | time = now()
38 | for (i = 0; i < operations; i++) {
39 | router.find('GET', '/user/comments')
40 | }
41 | print('static with same radix:', time)
42 |
43 | time = now()
44 | for (i = 0; i < operations; i++) {
45 | router.find('GET', '/user/lookup/username/john')
46 | }
47 | print('dynamic route:', time)
48 |
49 | time = now()
50 | for (i = 0; i < operations; i++) {
51 | router.find('GET', '/event/abcd1234/comments')
52 | }
53 | print('mixed static dynamic:', time)
54 |
55 | time = now()
56 | for (i = 0; i < operations; i++) {
57 | router.find('GET', '/very/deeply/nested/route/hello/there')
58 | }
59 | print('long static:', time)
60 |
61 | time = now()
62 | for (i = 0; i < operations; i++) {
63 | router.find('GET', '/static/index.html')
64 | }
65 | print('wildcard:', time)
66 |
67 | time = now()
68 | for (i = 0; i < operations; i++) {
69 | router.find('GET', '/user')
70 | router.find('GET', '/user/comments')
71 | router.find('GET', '/user/lookup/username/john')
72 | router.find('GET', '/event/abcd1234/comments')
73 | router.find('GET', '/very/deeply/nested/route/hello/there')
74 | router.find('GET', '/static/index.html')
75 | }
76 | print('all together:', time)
77 |
--------------------------------------------------------------------------------
/benchmarks/koa-router.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { title, now, print, operations } = require('../utils')
4 | const KoaRouter = require('koa-router')
5 | const router = new KoaRouter()
6 |
7 | title('koa-router benchmark')
8 |
9 | const routes = [
10 | { method: 'GET', url: '/user' },
11 | { method: 'GET', url: '/user/comments' },
12 | { method: 'GET', url: '/user/avatar' },
13 | { method: 'GET', url: '/user/lookup/username/:username' },
14 | { method: 'GET', url: '/user/lookup/email/:address' },
15 | { method: 'GET', url: '/event/:id' },
16 | { method: 'GET', url: '/event/:id/comments' },
17 | { method: 'POST', url: '/event/:id/comment' },
18 | { method: 'GET', url: '/map/:location/events' },
19 | { method: 'GET', url: '/status' },
20 | { method: 'GET', url: '/very/deeply/nested/route/hello/there' },
21 | { method: 'GET', url: '/static/*' }
22 | ]
23 |
24 | function noop () {}
25 | var i = 0
26 | var time = 0
27 |
28 | routes.forEach(route => {
29 | if (route.method === 'GET') {
30 | router.get(route.url, noop)
31 | } else {
32 | router.post(route.url, noop)
33 | }
34 | })
35 |
36 | time = now()
37 | for (i = 0; i < operations; i++) {
38 | router.match('/user', 'GET')
39 | }
40 | print('short static:', time)
41 |
42 | time = now()
43 | for (i = 0; i < operations; i++) {
44 | router.match('/user/comments', 'GET')
45 | }
46 | print('static with same radix:', time)
47 |
48 | time = now()
49 | for (i = 0; i < operations; i++) {
50 | router.match('/user/lookup/username/john', 'GET')
51 | }
52 | print('dynamic route:', time)
53 |
54 | time = now()
55 | for (i = 0; i < operations; i++) {
56 | router.match('/event/abcd1234/comments', 'GET')
57 | }
58 | print('mixed static dynamic:', time)
59 |
60 | time = now()
61 | for (i = 0; i < operations; i++) {
62 | router.match('/very/deeply/nested/route/hello/there', 'GET')
63 | }
64 | print('long static:', time)
65 |
66 | time = now()
67 | for (i = 0; i < operations; i++) {
68 | router.match('/static/index.html', 'GET')
69 | }
70 | print('wildcard:', time)
71 |
72 | time = now()
73 | for (i = 0; i < operations; i++) {
74 | router.match('/user', 'GET')
75 | router.match('/user/comments', 'GET')
76 | router.match('/user/lookup/username/john', 'GET')
77 | router.match('/event/abcd1234/comments', 'GET')
78 | router.match('/very/deeply/nested/route/hello/there', 'GET')
79 | router.match('/static/index.html', 'GET')
80 | }
81 | print('all together:', time)
82 |
--------------------------------------------------------------------------------
/benchmarks/koa-tree-router.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { title, now, print, operations } = require('../utils')
4 | const router = require('koa-tree-router')()
5 |
6 | title('koa-tree-router benchmark')
7 |
8 | const routes = [
9 | { method: 'GET', url: '/user' },
10 | { method: 'GET', url: '/user/comments' },
11 | { method: 'GET', url: '/user/avatar' },
12 | { method: 'GET', url: '/user/lookup/username/:username' },
13 | { method: 'GET', url: '/user/lookup/email/:address' },
14 | { method: 'GET', url: '/event/:id' },
15 | { method: 'GET', url: '/event/:id/comments' },
16 | { method: 'POST', url: '/event/:id/comment' },
17 | { method: 'GET', url: '/map/:location/events' },
18 | { method: 'GET', url: '/status' },
19 | { method: 'GET', url: '/very/deeply/nested/route/hello/there' },
20 | { method: 'GET', url: '/static/*file' }
21 | ]
22 |
23 | function noop () {}
24 | var i = 0
25 | var time = 0
26 |
27 | routes.forEach(({ method, url }) => {
28 | router.on(method, url, noop)
29 | })
30 |
31 | time = now()
32 | for (i = 0; i < operations; i++) {
33 | router.find('GET', '/user')
34 | }
35 | print('short static:', time)
36 |
37 | time = now()
38 | for (i = 0; i < operations; i++) {
39 | router.find('GET', '/user/comments')
40 | }
41 | print('static with same radix:', time)
42 |
43 | time = now()
44 | for (i = 0; i < operations; i++) {
45 | router.find('GET', '/user/lookup/username/john')
46 | }
47 | print('dynamic route:', time)
48 |
49 | time = now()
50 | for (i = 0; i < operations; i++) {
51 | router.find('GET', '/event/abcd1234/comments')
52 | }
53 | print('mixed static dynamic:', time)
54 |
55 | time = now()
56 | for (i = 0; i < operations; i++) {
57 | router.find('GET', '/very/deeply/nested/route/hello/there')
58 | }
59 | print('long static:', time)
60 |
61 | time = now()
62 | for (i = 0; i < operations; i++) {
63 | router.find('GET', '/static/index.html')
64 | }
65 | print('wildcard:', time)
66 |
67 | time = now()
68 | for (i = 0; i < operations; i++) {
69 | router.find('GET', '/user')
70 | router.find('GET', '/user/comments')
71 | router.find('GET', '/user/lookup/username/john')
72 | router.find('GET', '/event/abcd1234/comments')
73 | router.find('GET', '/very/deeply/nested/route/hello/there')
74 | router.find('GET', '/static/index.html')
75 | }
76 | print('all together:', time)
77 |
--------------------------------------------------------------------------------
/benchmarks/router.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { title, now, print, operations } = require('../utils')
4 | const router = require('router')()
5 |
6 | title('router benchmark (WARNING: includes handling)')
7 |
8 | const routes = [
9 | { method: 'GET', url: '/user' },
10 | { method: 'GET', url: '/user/comments' },
11 | { method: 'GET', url: '/user/avatar' },
12 | { method: 'GET', url: '/user/lookup/username/:username' },
13 | { method: 'GET', url: '/user/lookup/email/:address' },
14 | { method: 'GET', url: '/event/:id' },
15 | { method: 'GET', url: '/event/:id/comments' },
16 | { method: 'POST', url: '/event/:id/comment' },
17 | { method: 'GET', url: '/map/:location/events' },
18 | { method: 'GET', url: '/status' },
19 | { method: 'GET', url: '/very/deeply/nested/route/hello/there' },
20 | { method: 'GET', url: '/static/*' }
21 | ]
22 |
23 | function noop () {}
24 | var i = 0
25 | var time = 0
26 |
27 | routes.forEach(route => {
28 | if (route.method === 'GET') {
29 | router.get(route.url, noop)
30 | } else {
31 | router.post(route.url, noop)
32 | }
33 | })
34 |
35 | time = now()
36 | for (i = 0; i < operations; i++) {
37 | router({ method: 'GET', url: '/user' }, null, noop)
38 | }
39 | print('short static:', time)
40 |
41 | time = now()
42 | for (i = 0; i < operations; i++) {
43 | router({ method: 'GET', url: '/user/comments' }, null, noop)
44 | }
45 | print('static with same radix:', time)
46 |
47 | time = now()
48 | for (i = 0; i < operations; i++) {
49 | router({ method: 'GET', url: '/user/lookup/username/john' }, null, noop)
50 | }
51 | print('dynamic route:', time)
52 |
53 | time = now()
54 | for (i = 0; i < operations; i++) {
55 | router({ method: 'GET', url: '/event/abcd1234/comments' }, null, noop)
56 | }
57 | print('mixed static dynamic:', time)
58 |
59 | time = now()
60 | for (i = 0; i < operations; i++) {
61 | router({ method: 'GET', url: '/very/deeply/nested/route/hello/there' }, null, noop)
62 | }
63 | print('long static:', time)
64 |
65 | time = now()
66 | for (i = 0; i < operations; i++) {
67 | router({ method: 'GET', url: '/static/index.html' }, null, noop)
68 | }
69 | print('wildcard:', time)
70 |
71 | time = now()
72 | for (i = 0; i < operations; i++) {
73 | router({ method: 'GET', url: '/user' }, null, noop)
74 | router({ method: 'GET', url: '/user/comments' }, null, noop)
75 | router({ method: 'GET', url: '/user/lookup/username/john' }, null, noop)
76 | router({ method: 'GET', url: '/event/abcd1234/comments' }, null, noop)
77 | router({ method: 'GET', url: '/very/deeply/nested/route/hello/there' }, null, noop)
78 | router({ method: 'GET', url: '/static/index.html' }, null, noop)
79 | }
80 | print('all together:', time)
81 |
--------------------------------------------------------------------------------
/benchmarks/routr.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { title, now, print, operations } = require('../utils')
4 | const Routr = require('routr')
5 |
6 | title('routr benchmark')
7 |
8 | const router = new Routr([
9 | { name: 'first', method: 'GET', path: '/user' },
10 | { name: 'second', method: 'GET', path: '/user/comments' },
11 | { name: 'third', method: 'GET', path: '/user/avatar' },
12 | { name: 'fourth', method: 'GET', path: '/user/lookup/username/:username' },
13 | { name: 'fifth', method: 'GET', path: '/user/lookup/email/:address' },
14 | { name: 'sixth', method: 'get', path: '/event/:id' },
15 | { name: 'seventh', method: 'get', path: '/event/:id/comments' },
16 | { name: 'eighth', method: 'post', path: '/event/:id/comment' },
17 | { name: 'ninth', method: 'get', path: '/map/:location/events' },
18 | { name: 'tenth', method: 'get', path: '/status' },
19 | { name: 'eleventh', method: 'get', path: '/very/deeply/nested/route/hello/there' },
20 | { name: 'twelfth', method: 'get', path: '/static/*' }
21 | ])
22 |
23 | var i = 0
24 | var time = 0
25 |
26 | time = now()
27 | for (i = 0; i < operations; i++) {
28 | router.getRoute('/user', { method: 'GET' })
29 | }
30 | print('short static:', time)
31 |
32 | time = now()
33 | for (i = 0; i < operations; i++) {
34 | router.getRoute('/user/comments', { method: 'GET' })
35 | }
36 | print('static with same radix:', time)
37 |
38 | time = now()
39 | for (i = 0; i < operations; i++) {
40 | router.getRoute('/user/lookup/username/john', { method: 'GET' })
41 | }
42 | print('dynamic route:', time)
43 |
44 | time = now()
45 | for (i = 0; i < operations; i++) {
46 | router.getRoute('/event/abcd1234/comments', { method: 'GET' })
47 | }
48 | print('mixed static dynamic:', time)
49 |
50 | time = now()
51 | for (i = 0; i < operations; i++) {
52 | router.getRoute('/very/deeply/nested/route/hello/there', { method: 'GET' })
53 | }
54 | print('long static:', time)
55 |
56 | time = now()
57 | for (i = 0; i < operations; i++) {
58 | router.getRoute('/static/index.html', { method: 'GET' })
59 | }
60 | print('wildcard:', time)
61 |
62 | time = now()
63 | for (i = 0; i < operations; i++) {
64 | router.getRoute('/user', { method: 'GET' })
65 | router.getRoute('/user/comments', { method: 'GET' })
66 | router.getRoute('/user/lookup/username/john', { method: 'GET' })
67 | router.getRoute('/event/abcd1234/comments', { method: 'GET' })
68 | router.getRoute('/very/deeply/nested/route/hello/there', { method: 'GET' })
69 | router.getRoute('/static/index.html', { method: 'GET' })
70 | }
71 | print('all together:', time)
72 |
--------------------------------------------------------------------------------
/benchmarks/server-router.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { title, now, print, operations } = require('../utils')
4 | const router = require('server-router')()
5 |
6 | title('server-router benchmark')
7 |
8 | const routes = [
9 | { method: 'GET', url: '/user' },
10 | { method: 'GET', url: '/user/comments' },
11 | { method: 'GET', url: '/user/avatar' },
12 | { method: 'GET', url: '/user/lookup/username/:username' },
13 | { method: 'GET', url: '/user/lookup/email/:address' },
14 | { method: 'GET', url: '/event/:id' },
15 | { method: 'GET', url: '/event/:id/comments' },
16 | { method: 'POST', url: '/event/:id/comment' },
17 | { method: 'GET', url: '/map/:location/events' },
18 | { method: 'GET', url: '/status' },
19 | { method: 'GET', url: '/very/deeply/nested/route/hello/there' },
20 | { method: 'GET', url: '/static/*' }
21 | ]
22 |
23 | function noop () {}
24 | var i = 0
25 | var time = 0
26 |
27 | routes.forEach(route => {
28 | router.route(route.method, route.url, noop)
29 | })
30 |
31 | time = now()
32 | for (i = 0; i < operations; i++) {
33 | router._router.match('GET/user')
34 | }
35 | print('short static:', time)
36 |
37 | time = now()
38 | for (i = 0; i < operations; i++) {
39 | router._router.match('GET/user/comments')
40 | }
41 | print('static with same radix:', time)
42 |
43 | time = now()
44 | for (i = 0; i < operations; i++) {
45 | router._router.match('GET/user/lookup/username/john')
46 | }
47 | print('dynamic route:', time)
48 |
49 | time = now()
50 | for (i = 0; i < operations; i++) {
51 | router._router.match('GET/event/abcd1234/comments')
52 | }
53 | print('mixed static dynamic:', time)
54 |
55 | time = now()
56 | for (i = 0; i < operations; i++) {
57 | router._router.match('GET/very/deeply/nested/route/hello/there')
58 | }
59 | print('long static:', time)
60 |
61 | time = now()
62 | for (i = 0; i < operations; i++) {
63 | router._router.match('GET/static/index.html')
64 | }
65 | print('wildcard:', time)
66 |
67 | time = now()
68 | for (i = 0; i < operations; i++) {
69 | router._router.match('GET/user')
70 | router._router.match('GET/user/comments')
71 | router._router.match('GET/user/lookup/username/john')
72 | router._router.match('GET/event/abcd1234/comments')
73 | router._router.match('GET/very/deeply/nested/route/hello/there')
74 | router._router.match('GET/static/index.html')
75 | }
76 | print('all together:', time)
77 |
--------------------------------------------------------------------------------
/benchmarks/trek-router.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { title, now, print, operations } = require('../utils')
4 | const Router = require('trek-router')
5 |
6 | const router = new Router()
7 |
8 | title('trek-router benchmark')
9 |
10 | const routes = [
11 | { method: 'GET', url: '/user' },
12 | { method: 'GET', url: '/user/comments' },
13 | { method: 'GET', url: '/user/avatar' },
14 | { method: 'GET', url: '/user/lookup/username/:username' },
15 | { method: 'GET', url: '/user/lookup/email/:address' },
16 | { method: 'GET', url: '/event/:id' },
17 | { method: 'GET', url: '/event/:id/comments' },
18 | { method: 'POST', url: '/event/:id/comment' },
19 | { method: 'GET', url: '/map/:location/events' },
20 | { method: 'GET', url: '/status' },
21 | { method: 'GET', url: '/very/deeply/nested/route/hello/there' },
22 | { method: 'GET', url: '/static/*' }
23 | ]
24 |
25 | function noop () {}
26 | var i = 0
27 | var time = 0
28 |
29 | routes.forEach(route => {
30 | router.add(route.method, route.url, noop)
31 | })
32 |
33 | time = now()
34 | for (i = 0; i < operations; i++) {
35 | router.find('GET', '/user')
36 | }
37 | print('short static:', time)
38 |
39 | time = now()
40 | for (i = 0; i < operations; i++) {
41 | router.find('GET', '/user/comments')
42 | }
43 | print('static with same radix:', time)
44 |
45 | time = now()
46 | for (i = 0; i < operations; i++) {
47 | router.find('GET', '/user/lookup/username/john')
48 | }
49 | print('dynamic route:', time)
50 |
51 | time = now()
52 | for (i = 0; i < operations; i++) {
53 | router.find('GET', '/event/abcd1234/comments')
54 | }
55 | print('mixed static dynamic:', time)
56 |
57 | time = now()
58 | for (i = 0; i < operations; i++) {
59 | router.find('GET', '/very/deeply/nested/route/hello/there')
60 | }
61 | print('long static:', time)
62 |
63 | time = now()
64 | for (i = 0; i < operations; i++) {
65 | router.find('GET', '/static/index.html')
66 | }
67 | print('wildcard:', time)
68 |
69 | time = now()
70 | for (i = 0; i < operations; i++) {
71 | router.find('GET', '/user')
72 | router.find('GET', '/user/comments')
73 | router.find('GET', '/user/lookup/username/john')
74 | router.find('GET', '/event/abcd1234/comments')
75 | router.find('GET', '/very/deeply/nested/route/hello/there')
76 | router.find('GET', '/static/index.html')
77 | }
78 | print('all together:', time)
79 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "benchrouter",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "main": "index.js",
7 | "scripts": {
8 | "start": "node runner.js",
9 | "test": "standard"
10 | },
11 | "keywords": [],
12 | "author": "Tomas Della Vedova - @delvedor (http://delved.org)",
13 | "license": "MIT",
14 | "dependencies": {
15 | "call": "^5.0.1",
16 | "express": "^4.16.3",
17 | "find-my-way": "^1.11.0",
18 | "koa-router": "^7.4.0",
19 | "koa-tree-router": "^0.2.2",
20 | "router": "^1.3.2",
21 | "routr": "^2.1.0",
22 | "server-router": "^6.0.0",
23 | "trek-router": "^1.2.0"
24 | },
25 | "devDependencies": {
26 | "chalk": "^2.3.2",
27 | "standard": "^11.0.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/runner.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { fork } = require('child_process')
4 | const { resolve } = require('path')
5 | const { Queue } = require('./utils')
6 |
7 | const benchmarks = [
8 | 'find-my-way.js',
9 | 'call.js',
10 | 'express.js',
11 | 'koa-router.js',
12 | 'koa-tree-router.js',
13 | 'router.js',
14 | 'routr.js',
15 | 'server-router.js',
16 | 'trek-router.js'
17 | ]
18 |
19 | const queue = new Queue()
20 |
21 | benchmarks.forEach(file => {
22 | queue.add(runner.bind({ file: resolve('benchmarks', file) }))
23 | })
24 |
25 | function runner (done) {
26 | const process = fork(this.file)
27 | process.on('close', done)
28 | }
29 |
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const chalk = require('chalk')
4 |
5 | const operations = 1000000
6 |
7 | function now () {
8 | var ts = process.hrtime()
9 | return (ts[0] * 1e3) + (ts[1] / 1e6)
10 | }
11 |
12 | function getOpsSec (ms) {
13 | return Number(((operations * 1000) / ms).toFixed()).toLocaleString()
14 | }
15 |
16 | function print (name, time) {
17 | console.log(chalk.yellow(name), getOpsSec(now() - time), 'ops/sec')
18 | }
19 |
20 | function title (name) {
21 | console.log(chalk.green(`
22 | ${'='.repeat(name.length + 2)}
23 | ${name}
24 | ${'='.repeat(name.length + 2)}`))
25 | }
26 |
27 | function Queue () {
28 | this.q = []
29 | this.running = false
30 | }
31 |
32 | Queue.prototype.add = function add (job) {
33 | this.q.push(job)
34 | if (!this.running) this.run()
35 | }
36 |
37 | Queue.prototype.run = function run () {
38 | this.running = true
39 | const job = this.q.shift()
40 | job(() => {
41 | if (this.q.length) {
42 | this.run()
43 | } else {
44 | this.running = false
45 | }
46 | })
47 | }
48 |
49 | module.exports = { now, getOpsSec, print, title, Queue, operations }
50 |
--------------------------------------------------------------------------------