router-http 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +74 -22
  2. package/package.json +3 -6
  3. package/src/index.js +87 -54
package/README.md CHANGED
@@ -6,9 +6,9 @@
6
6
 
7
7
  A middleware style router, similar to [express@router](https://github.com/pillarjs/router), plus:
8
8
 
9
- - Faster (x3 compared with Express).
9
+ - Faster (~30% more requests per second than Express).
10
10
  - Maintained and well tested.
11
- - Smaller (1.4 kB).
11
+ - Smaller (1.8 kB).
12
12
 
13
13
  Don't get me wrong: The original Express router is a piece of art. I used it for years and I just considered create this library after experienced a bug that never was addressed in the stable version due to the [lack of maintenance](https://github.com/pillarjs/router/pull/60).
14
14
 
@@ -108,20 +108,70 @@ router
108
108
 
109
109
  They are only will add if the condition is satisfied.
110
110
 
111
- ### Prefixing routes
111
+ Middlewares always run **before** route handlers, regardless of declaration order. This means you can declare `.use()` before or after `.get()` / `.post()` / etc., and the middleware will still execute first.
112
112
 
113
- In case you need you can prefix all the routes:
113
+ ### Nested routers
114
+
115
+ You can use a router as a middleware for another router. This is useful for prefixing routes or for modularizing your application.
116
+
117
+ When a sub-router is used as a middleware, it will only handle requests that match its prefix. If no route matches inside the sub-router, it will automatically call `next()` to pass control back to the parent router.
114
118
 
115
119
  ```js
116
- routes.get('/', (req, res) => res.end('Welcome to my API!'))
120
+ const createRouter = require('router-http')
121
+ const http = require('http')
117
122
 
118
- /**
119
- * Prefix all routes with the API version
120
- */
123
+ const final = (err, req, res) => {
124
+ res.statusCode = err ? 500 : 404
125
+ res.end(err ? err.message : 'Not Found')
126
+ }
127
+
128
+ // 1. Create a sub-router for API v1
129
+ const v1 = createRouter(final)
130
+ v1.get('/info', (req, res) => res.end('v1 info'))
131
+
132
+ // 2. Create another sub-router for API v2
133
+ const v2 = createRouter(final)
134
+ v2.get('/info', (req, res) => res.end('v2 info'))
135
+
136
+ // 3. Create the main router and mount sub-routers
121
137
  const router = createRouter(final)
138
+
122
139
  router
123
- .use('/latest', routes)
124
- .use('/v1', routes)
140
+ .use('/v1', v1)
141
+ .use('/v2', v2)
142
+ .get('/', (req, res) => res.end('Welcome to the main router'))
143
+
144
+ http.createServer(router).listen(3000)
145
+ ```
146
+
147
+ #### Exit Current Router
148
+
149
+ You can use `next('router')` to skip all remaining handlers in the current router and pass control back to the parent router. This is useful for conditional routing, such as a "Beta" router that only handles requests for certain users:
150
+
151
+ ```js
152
+ const beta = createRouter(final)
153
+
154
+ // Middleware to check if the user is a beta tester
155
+ beta.use((req, res, next) => {
156
+ if (!req.isBetaTester) return next('router')
157
+ next()
158
+ })
159
+
160
+ beta.get('/search', (req, res) => {
161
+ res.end('Using the new AI-powered search engine!')
162
+ })
163
+
164
+ const router = createRouter(final)
165
+
166
+ // Mount the beta router
167
+ router.use('/v1', beta)
168
+
169
+ // This will be reached if:
170
+ // 1. The user is NOT a beta tester (next('router') was called)
171
+ // 2. Or the path didn't match anything inside the beta router
172
+ router.get('/v1/search', (req, res) => {
173
+ res.end('Using the classic search engine.')
174
+ })
125
175
  ```
126
176
 
127
177
  ### Using the router
@@ -134,30 +184,32 @@ const server = http.createServer(router)
134
184
 
135
185
  ## Benchmark
136
186
 
137
- **express@4.18.2**
187
+ With all the improvements, router-http is approximately 30% faster than the express router:
188
+
189
+ **express@5.2.1**
138
190
 
139
191
  ```
140
192
  Running 30s test @ http://localhost:3000/user/123
141
193
  8 threads and 100 connections
142
194
  Thread Stats Avg Stdev Max +/- Stdev
143
- Latency 4.12ms 653.26us 21.71ms 89.35%
144
- Req/Sec 2.93k 159.60 5.99k 84.75%
145
- 700421 requests in 30.06s, 102.87MB read
146
- Requests/sec: 23304.22
147
- Transfer/sec: 3.42MB
195
+ Latency 1.23ms 1.40ms 96.27ms 99.61%
196
+ Req/Sec 10.15k 615.89 11.07k 86.24%
197
+ 2430687 requests in 30.10s, 356.98MB read
198
+ Requests/sec: 80752.48
199
+ Transfer/sec: 11.86MB
148
200
  ```
149
201
 
150
- **router-http@1.0.0**
202
+ **router-http**
151
203
 
152
204
  ```
153
205
  Running 30s test @ http://localhost:3000/user/123
154
206
  8 threads and 100 connections
155
207
  Thread Stats Avg Stdev Max +/- Stdev
156
- Latency 1.33ms 690.36us 30.28ms 97.16%
157
- Req/Sec 9.27k 1.09k 11.76k 89.58%
158
- 2214097 requests in 30.02s, 276.61MB read
159
- Requests/sec: 73754.53
160
- Transfer/sec: 9.21MB
208
+ Latency 0.97ms 1.27ms 84.82ms 99.77%
209
+ Req/Sec 12.91k 1.07k 14.67k 71.51%
210
+ 3092927 requests in 30.10s, 386.40MB read
211
+ Requests/sec: 102751.65
212
+ Transfer/sec: 12.84MB
161
213
  ```
162
214
 
163
215
  See more details, check [benchmark](/benchmark) section.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "router-http",
3
3
  "description": "Simple HTTP router compatible with Express",
4
4
  "homepage": "https://github.com/Kikobeats/router-http",
5
- "version": "1.0.11",
5
+ "version": "1.0.13",
6
6
  "main": "src/index.js",
7
7
  "author": {
8
8
  "email": "josefrancisco.verdu@gmail.com",
@@ -29,6 +29,7 @@
29
29
  "routes"
30
30
  ],
31
31
  "dependencies": {
32
+ "null-prototype-object": "~1.2.5",
32
33
  "trouter": "~4.0.0"
33
34
  },
34
35
  "devDependencies": {
@@ -46,7 +47,6 @@
46
47
  "nano-staged": "latest",
47
48
  "simple-git-hooks": "latest",
48
49
  "standard": "latest",
49
- "standard-markdown": "latest",
50
50
  "standard-version": "latest"
51
51
  },
52
52
  "engines": {
@@ -59,7 +59,7 @@
59
59
  "clean": "rm -rf node_modules",
60
60
  "contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
61
61
  "coverage": "c8 report --reporter=text-lcov > coverage/lcov.info",
62
- "lint": "standard-markdown README.md && standard",
62
+ "lint": "standard",
63
63
  "postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)",
64
64
  "prerelease": "npm run contributors",
65
65
  "pretest": "npm run lint",
@@ -84,9 +84,6 @@
84
84
  "prettier-standard",
85
85
  "standard --fix"
86
86
  ],
87
- "*.md": [
88
- "standard-markdown"
89
- ],
90
87
  "package.json": [
91
88
  "finepack"
92
89
  ]
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const NullProtoObj = require('null-prototype-object')
3
4
  const { Trouter } = require('trouter')
4
5
 
5
6
  const requiredFinalHandler = () => {
@@ -18,18 +19,18 @@ const value = x => {
18
19
 
19
20
  const parse = ({ url }) => {
20
21
  const index = url.indexOf('?', 1)
21
- const obj = { pathname: url, query: null, search: null }
22
- if (index !== -1) {
23
- obj.search = url.substring(index)
24
- obj.query = obj.search.substring(1)
25
- obj.pathname = url.substring(0, index)
22
+ if (index === -1) return { pathname: url, query: null, search: null }
23
+ const search = url.substring(index)
24
+ return {
25
+ pathname: url.substring(0, index),
26
+ query: search.substring(1),
27
+ search
26
28
  }
27
- return obj
28
29
  }
29
30
 
30
31
  const mutate = (str, req) => {
31
- req.url = req.url.substring(str.length) ?? '/'
32
- req.path = req.path.substring(str.length) ?? '/'
32
+ req.url = req.url.substring(str.length) || '/'
33
+ req.path = req.path.substring(str.length) || '/'
33
34
  }
34
35
 
35
36
  class Router extends Trouter {
@@ -46,7 +47,7 @@ class Router extends Trouter {
46
47
  /**
47
48
  * Middleware for specific routes
48
49
  */
49
- #middlewaresBy = []
50
+ #middlewaresBy = new NullProtoObj()
50
51
 
51
52
  /**
52
53
  * Middleware declaration, where the page is optional
@@ -64,65 +65,97 @@ class Router extends Trouter {
64
65
  page = lead(page)
65
66
  fns.filter(Boolean).forEach(fn => {
66
67
  const array = this.#middlewaresBy[page] ?? []
67
- // eslint-disable-next-line no-sequences
68
- array.length > 0 || array.push((r, _, nxt) => (mutate(page, r), nxt()))
68
+ if (array.length === 0) {
69
+ array.push((r, _, nxt) => {
70
+ mutate(page, r)
71
+ nxt()
72
+ })
73
+ }
69
74
  this.#middlewaresBy[page] = array.concat(fn)
70
75
  })
71
76
  }
72
77
  return this
73
78
  }
74
79
 
75
- handler = (req, res, info) => {
76
- info = info ?? parse(req)
77
- let fns = []
78
- let middlewares = this.#middlewares
79
- const route = this.find(req.method, info.pathname)
80
- const page = value((req.path = info.pathname))
81
- if (this.#middlewaresBy[page] !== undefined) {
82
- middlewares = middlewares.concat(this.#middlewaresBy[page])
83
- }
84
- if (route) {
85
- fns = route.handlers
86
- req.params = { ...req.params, ...route.params }
80
+ handler = (req, res, next) => {
81
+ const info = parse(req)
82
+ const pathname = info.pathname
83
+ req.path = pathname
84
+ const route = this.find(req.method, pathname)
85
+ const page = value(pathname)
86
+
87
+ const m = this.#middlewares
88
+ const mb = this.#middlewaresBy[page]
89
+ const f = route.handlers.length > 0 ? route.handlers : null
90
+
91
+ if (f) {
92
+ req.params = req.params
93
+ ? { ...req.params, ...route.params }
94
+ : route.params
95
+ } else {
96
+ req.params = req.params || {}
87
97
  }
88
- fns.push(this.unhandler)
89
- req.search = req.query ?? info.search
90
- req.query = req.query ?? info.query
91
- // Exit if only a single function
98
+
99
+ req.search = req.query || info.search
100
+ req.query = req.query || info.query
101
+
92
102
  let index = 0
93
- let size = middlewares.length
94
- const num = fns.length
95
- if (size === index && num === 1) return fns[0](undefined, req, res)
96
-
97
- // Otherwise loop thru all middlware
98
- const next = err => (err ? this.unhandler(err, req, res, next) : loop())
99
-
100
- const loop = () =>
101
- res.writableEnded ||
102
- (index < size &&
103
- (async () => {
104
- try {
105
- const mware = middlewares[index++]
106
- const result =
107
- index === size
108
- ? mware(undefined, req, res, next)
109
- : mware(req, res, next)
110
- if (result && typeof result.then === 'function') {
111
- await result
112
- }
113
- } catch (err) {
114
- return this.unhandler(err, req, res, next)
103
+ let sync = 0
104
+
105
+ const mLen = m.length
106
+ const mbLen = mb ? mb.length : 0
107
+ const fLen = f ? f.length : 0
108
+ const total = mLen + mbLen + fLen
109
+
110
+ const _next = err => {
111
+ if (err === 'router') {
112
+ if (next) return next()
113
+ index = total
114
+ err = undefined
115
+ }
116
+ if (err) return this.unhandler(err, req, res, next)
117
+ if (++sync > 100) {
118
+ sync = 0
119
+ return setImmediate(loop)
120
+ }
121
+ loop()
122
+ }
123
+
124
+ const loop = () => {
125
+ if (index < total) {
126
+ if (res.writableEnded) return
127
+ const i = index++
128
+ const mware =
129
+ i < mLen
130
+ ? m[i]
131
+ : i < mLen + mbLen
132
+ ? mb[i - mLen]
133
+ : f[i - mLen - mbLen]
134
+
135
+ try {
136
+ const result = mware(req, res, _next)
137
+ if (result && typeof result.then === 'function') {
138
+ return result.then(undefined, _next)
115
139
  }
116
- })())
140
+ } catch (err) {
141
+ return _next(err)
142
+ }
143
+ return
144
+ }
145
+
146
+ if (res.writableEnded) return
147
+
148
+ if (next) return next()
149
+
150
+ this.unhandler(undefined, req, res, _next)
151
+ }
117
152
 
118
- middlewares = middlewares.concat(fns)
119
- size += num
120
- loop() // init
153
+ loop()
121
154
  }
122
155
  }
123
156
 
124
157
  module.exports = (finalhandler = requiredFinalHandler()) => {
125
158
  const router = new Router(finalhandler)
126
- const handler = (req, res) => router.handler(req, res)
159
+ const handler = (req, res, next) => router.handler(req, res, next)
127
160
  return Object.assign(handler, router)
128
161
  }