├── .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 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![Build Status](https://travis-ci.org/delvedor/router-benchmark.svg?branch=master)](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 | --------------------------------------------------------------------------------