dx-server 0.9.0 → 0.10.1

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 (71) hide show
  1. package/README.md +246 -252
  2. package/cjs/body.d.ts +1 -1
  3. package/cjs/bodyHelpers.js +2 -2
  4. package/cjs/connect.d.ts +1 -1
  5. package/cjs/connect.js +1 -1
  6. package/cjs/dx.d.ts +1 -1
  7. package/cjs/dx.js +20 -11
  8. package/cjs/dxHelpers.d.ts +1 -1
  9. package/cjs/dxHelpers.js +8 -3
  10. package/cjs/express.js +14 -12
  11. package/cjs/polyfillWithResolvers.js +2 -3
  12. package/cjs/router.js +4 -3
  13. package/cjs/static.d.ts +5 -0
  14. package/cjs/static.js +8 -8
  15. package/cjs/staticHelpers.d.ts +15 -3
  16. package/cjs/staticHelpers.js +190 -8
  17. package/cjs/stream.js +2 -2
  18. package/{esm → cjs/vendors}/contentType.js +7 -3
  19. package/{esm → cjs/vendors}/etag.d.ts +3 -1
  20. package/cjs/{etag.js → vendors/etag.js} +1 -1
  21. package/cjs/vendors/fresh.d.ts +23 -0
  22. package/cjs/vendors/fresh.js +102 -0
  23. package/cjs/vendors/mime.d.ts +1 -0
  24. package/cjs/vendors/mime.js +42 -0
  25. package/cjs/vendors/mimeDb.d.ts +9413 -0
  26. package/cjs/vendors/mimeDb.js +9417 -0
  27. package/cjs/vendors/mimeScore.d.ts +5 -0
  28. package/cjs/vendors/mimeScore.js +50 -0
  29. package/cjs/vendors/onFinished.d.ts +14 -0
  30. package/cjs/vendors/onFinished.js +245 -0
  31. package/cjs/vendors/rangeParser.d.ts +10 -0
  32. package/cjs/vendors/rangeParser.js +125 -0
  33. package/esm/body.d.ts +1 -1
  34. package/esm/bodyHelpers.js +2 -2
  35. package/esm/connect.d.ts +1 -1
  36. package/esm/connect.js +1 -1
  37. package/esm/dx.d.ts +1 -1
  38. package/esm/dx.js +20 -11
  39. package/esm/dxHelpers.d.ts +1 -1
  40. package/esm/dxHelpers.js +8 -3
  41. package/esm/express.js +14 -12
  42. package/esm/polyfillWithResolvers.js +2 -3
  43. package/esm/router.js +4 -3
  44. package/esm/static.d.ts +5 -0
  45. package/esm/static.js +8 -8
  46. package/esm/staticHelpers.d.ts +15 -3
  47. package/esm/staticHelpers.js +190 -8
  48. package/esm/stream.js +2 -2
  49. package/{cjs → esm/vendors}/contentType.js +3 -7
  50. package/{cjs → esm/vendors}/etag.d.ts +3 -1
  51. package/esm/vendors/etag.js +90 -0
  52. package/esm/vendors/fresh.d.ts +23 -0
  53. package/esm/vendors/fresh.js +96 -0
  54. package/esm/vendors/mime.d.ts +1 -0
  55. package/esm/vendors/mime.js +35 -0
  56. package/esm/vendors/mimeDb.d.ts +9413 -0
  57. package/esm/vendors/mimeDb.js +9415 -0
  58. package/esm/vendors/mimeScore.d.ts +5 -0
  59. package/esm/vendors/mimeScore.js +46 -0
  60. package/esm/vendors/onFinished.d.ts +14 -0
  61. package/esm/vendors/onFinished.js +241 -0
  62. package/esm/vendors/rangeParser.d.ts +10 -0
  63. package/esm/vendors/rangeParser.js +121 -0
  64. package/package.json +1 -5
  65. package/cjs/file.d.ts +0 -3
  66. package/cjs/file.js +0 -12
  67. package/esm/etag.js +0 -90
  68. package/esm/file.d.ts +0 -3
  69. package/esm/file.js +0 -8
  70. /package/cjs/{contentType.d.ts → vendors/contentType.d.ts} +0 -0
  71. /package/esm/{contentType.d.ts → vendors/contentType.d.ts} +0 -0
package/README.md CHANGED
@@ -11,7 +11,7 @@ A modern, unopinionated, and performant Node.js server framework built on AsyncL
11
11
  - 🔗 **Chainable middleware** - Elegant middleware composition with [jchain](https://www.npmjs.com/package/jchain)
12
12
  - 🎯 **Type-safe** - Written in TypeScript with comprehensive type definitions
13
13
  - 🔄 **Express compatible** - Use existing Express middleware and applications
14
- - 📦 **Minimal dependencies** - Only one runtime dependency (`send` for static file serving, planned for removal)
14
+ - 📦 **Zero dependencies** - No runtime dependencies, all functionality built-in
15
15
  - 🛡️ **Built-in body parsing** - JSON, text, URL-encoded, and raw body parsing with size limits
16
16
  - 🗂️ **Static file serving** - Efficient static file handling with ETag support
17
17
  - 🔀 **Modern routing** - URLPattern-based routing (not Express patterns)
@@ -31,9 +31,9 @@ pnpm add dx-server jchain
31
31
 
32
32
  ### URLPattern Support
33
33
 
34
- dx-server uses the [URLPattern API](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) for routing, which is natively supported in Node.js v19.0.0 and later.
34
+ dx-server uses the [URLPattern API](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) for routing, which is natively supported in Node.js v23.8.0 and later.
35
35
 
36
- **For Node.js < 19.0.0**, you need to install a polyfill:
36
+ **For Node.js < 23.8.0**, you need to install a polyfill:
37
37
 
38
38
  ```bash
39
39
  npm install urlpattern-polyfill
@@ -57,9 +57,6 @@ if (typeof URLPattern === 'undefined') {
57
57
  }
58
58
  ```
59
59
 
60
- ### Future Roadmap
61
-
62
- **Zero Dependencies**: The `send` package (currently used for static file serving) is planned for removal in a future version. This will make dx-server a true zero-dependency framework. Until then, if you don't need static file serving, the `send` dependency won't be loaded or affect your application.
63
60
 
64
61
  ## Quick Start
65
62
 
@@ -71,23 +68,23 @@ import chain from 'jchain'
71
68
  import dxServer, {getReq, getRes, router, setHtml, setText} from 'dx-server'
72
69
 
73
70
  new Server().on('request', (req, res) => chain(
74
- dxServer(req, res),
75
- async next => {
76
- try {
77
- // Access req/res from anywhere - no prop drilling!
78
- getRes().setHeader('Cache-Control', 'no-cache')
79
- console.log(getReq().method, getReq().url)
80
- await next()
81
- } catch (e) {
82
- console.error(e)
83
- setHtml('internal server error', {status: 500})
84
- }
85
- },
86
- router.get({
87
- '/'() {setHtml('hello world')},
88
- '/health'() {setText('ok')}
89
- }),
90
- () => setHtml('not found', {status: 404}),
71
+ dxServer(req, res),
72
+ async next => {
73
+ try {
74
+ // Access req/res from anywhere - no prop drilling!
75
+ getRes().setHeader('Cache-Control', 'no-cache')
76
+ console.log(getReq().method, getReq().url)
77
+ await next()
78
+ } catch (e) {
79
+ console.error(e)
80
+ setHtml('internal server error', {status: 500})
81
+ }
82
+ },
83
+ router.get({
84
+ '/'() {setHtml('hello world')},
85
+ '/health'() {setText('ok')}
86
+ }),
87
+ () => setHtml('not found', {status: 404}),
91
88
  )()).listen(3000, () => console.log('server is listening at 3000'))
92
89
  ```
93
90
 
@@ -104,19 +101,19 @@ interface User {
104
101
  }
105
102
 
106
103
  new Server().on('request', (req, res) => chain(
107
- dxServer(req, res),
108
- router.post({
109
- async '/api/users'() {
110
- const body = await getJson<{name: string}>()
111
- if (!body?.name) {
112
- setJson({error: 'Name required'}, {status: 400})
113
- return
114
- }
115
- const user: User = {id: 1, name: body.name}
116
- setJson(user, {status: 201})
117
- }
118
- }),
119
- () => setJson({error: 'Not found'}, {status: 404})
104
+ dxServer(req, res),
105
+ router.post({
106
+ async '/api/users'() {
107
+ const body = await getJson<{name: string}>()
108
+ if (!body?.name) {
109
+ setJson({error: 'Name required'}, {status: 400})
110
+ return
111
+ }
112
+ const user: User = {id: 1, name: body.name}
113
+ setJson(user, {status: 201})
114
+ }
115
+ }),
116
+ () => setJson({error: 'Not found'}, {status: 404})
120
117
  )()).listen(3000)
121
118
  ```
122
119
 
@@ -126,17 +123,14 @@ new Server().on('request', (req, res) => chain(
126
123
  import {Server} from 'node:http'
127
124
  import chain from 'jchain'
128
125
  import dxServer, {chainStatic, setHtml} from 'dx-server'
129
- import {resolve, dirname} from 'node:path'
130
- import {fileURLToPath} from 'node:url'
126
+ import {resolve} from 'node:path'
131
127
 
132
128
  new Server().on('request', (req, res) => chain(
133
- dxServer(req, res),
134
- chainStatic('/*', {
135
- root: resolve(dirname(fileURLToPath(import.meta.url)), 'public'),
136
- index: ['index.html'],
137
- dotfiles: 'deny'
138
- }),
139
- () => setHtml('not found', {status: 404}),
129
+ dxServer(req, res),
130
+ chainStatic('/*', {
131
+ root: resolve(import.meta.dirname, 'public'),
132
+ }),
133
+ () => setHtml('not found', {status: 404}),
140
134
  )()).listen(3000)
141
135
  ```
142
136
 
@@ -144,16 +138,15 @@ new Server().on('request', (req, res) => chain(
144
138
 
145
139
  This example requires: `npm install express morgan helmet cors`
146
140
 
147
-
148
141
  ```javascript
149
142
  import {Server} from 'node:http'
150
143
  import {promisify} from 'node:util'
151
144
  import chain from 'jchain'
152
145
  import dxServer, {
153
- getReq, getRes,
154
- getBuffer, getJson, getRaw, getText, getUrlEncoded, getQuery,
155
- setHtml, setJson, setText, setEmpty, setBuffer, setRedirect, setNodeStream, setWebStream, setFile,
156
- router, connectMiddlewares, chainStatic, makeDxContext
146
+ getReq, getRes,
147
+ getBuffer, getJson, getRaw, getText, getUrlEncoded, getQuery,
148
+ setHtml, setJson, setText, setEmpty, setBuffer, setRedirect, setNodeStream, setWebStream, setFile,
149
+ router, connectMiddlewares, chainStatic, makeDxContext
157
150
  } from 'dx-server'
158
151
  import {expressApp} from 'dx-server/express'
159
152
  import express from 'express'
@@ -161,96 +154,96 @@ import morgan from 'morgan'
161
154
 
162
155
  // it is best practice to create custom error class for non-system error
163
156
  class ServerError extends Error {
164
- name = 'ServerError'
157
+ name = 'ServerError'
165
158
 
166
- constructor(message, status = 400, code = 'unknown') {
167
- super(message)
168
- this.status = status
169
- this.code = code
170
- }
159
+ constructor(message, status = 400, code = 'unknown') {
160
+ super(message)
161
+ this.status = status
162
+ this.code = code
163
+ }
171
164
  }
172
165
 
173
166
  const authContext = makeDxContext(async () => {
174
- if (getReq().headers.authorization) return {id: 1, name: 'joe (private)'}
167
+ if (getReq().headers.authorization) return {id: 1, name: 'joe (private)'}
175
168
  })
176
169
 
177
- const requireAuth = () => {
178
- if (!authContext.value) throw new ServerError('Unauthorized', 401, 'UNAUTHORIZED')
170
+ function requireAuth() {
171
+ if (!authContext.value) throw new ServerError('Unauthorized', 401, 'UNAUTHORIZED')
179
172
  }
180
173
 
181
174
  const serverChain = chain(
182
- next => {
183
- // this is the difference between express and dx-server
184
- // req, res can be accessed from anywhere via context which uses NodeJS's AsyncLocalStorage under the hood
185
- getRes().setHeader('Cache-Control', 'no-cache')
186
- return next() // must return or await
187
- },
188
- async next => {// global error catching for all following middlewares
189
- try {
190
- await next()
191
- } catch (e) {// only app error message should be shown to user
192
- if (e instanceof ServerError) setHtml(`${e.message} (code: ${e.code})`, {status: e.status})
193
- else {// report system error
194
- console.error(e)
195
- setHtml('internal server error (code: internal)', {status: 500})
196
- }
197
- }
198
- },
199
- connectMiddlewares(
200
- morgan('common'),
201
- // cors(),
202
- ),
203
- await expressApp(app => {// any express feature can be used. This requires express installed, with for e.g., `yarn add express`
204
- app.set('trust proxy', true)
205
- if (process.env.NODE_ENV !== 'production') app.set('json spaces', 2)
206
- app.use('/public', express.static('public'))
207
- }),
208
- authContext.chain(), // chain context will set the context value to authContext.value in every request
209
- router.post('/api/*', async ({next}) => {// example of catching error for all /api/* routes
210
- try {
211
- await next()
212
- } catch (e) {
213
- if (e instanceof ServerError) setJson({// only app error message should be shown to user
214
- error: e.message,
215
- code: e.code,
216
- }, {status: e.status})
217
- else {// report system error
218
- console.error(e)
219
- setJson({
220
- message: 'internal server error',
221
- code: 'internal'
222
- }, {status: 500})
223
- }
224
- }
225
- }),
226
- router.post({
227
- '/api/sample-public-api'() { // sample POST router
228
- setJson({name: 'joe'})
229
- },
230
- '/api/me'() { // sample private router
231
- requireAuth()
232
- setJson({name: authContext.value.name})
233
- },
234
- }),
235
- router.get('/', () => setHtml('ok')), // router.method() accepts 2 formats
236
- router.get('/health', () => setText('ok')),
237
- () => { // not found router
238
- throw new ServerError('Not found', 404, 'NOT_FOUND')
239
- },
175
+ next => {
176
+ // this is the difference between express and dx-server
177
+ // req, res can be accessed from anywhere via context which uses NodeJS's AsyncLocalStorage under the hood
178
+ getRes().setHeader('Cache-Control', 'no-cache')
179
+ return next() // must return or await
180
+ },
181
+ async next => {// global error catching for all following middlewares
182
+ try {
183
+ await next()
184
+ } catch (e) {// only app error message should be shown to user
185
+ if (e instanceof ServerError) setHtml(`${e.message} (code: ${e.code})`, {status: e.status})
186
+ else {// report system error
187
+ console.error(e)
188
+ setHtml('internal server error (code: internal)', {status: 500})
189
+ }
190
+ }
191
+ },
192
+ connectMiddlewares(
193
+ morgan('common'),
194
+ // cors(),
195
+ ),
196
+ await expressApp(app => {// any express feature can be used. This requires express installed, with for e.g., `yarn add express`
197
+ app.set('trust proxy', true)
198
+ if (process.env.NODE_ENV !== 'production') app.set('json spaces', 2)
199
+ app.use('/public', express.static('public'))
200
+ }),
201
+ authContext.chain(), // chain context will set the context value to authContext.value in every request
202
+ router.post('/api/*', async ({next}) => {// example of catching error for all /api/* routes
203
+ try {
204
+ await next()
205
+ } catch (e) {
206
+ if (e instanceof ServerError) setJson({// only app error message should be shown to user
207
+ error: e.message,
208
+ code: e.code,
209
+ }, {status: e.status})
210
+ else {// report system error
211
+ console.error(e)
212
+ setJson({
213
+ message: 'internal server error',
214
+ code: 'internal'
215
+ }, {status: 500})
216
+ }
217
+ }
218
+ }),
219
+ router.post({
220
+ '/api/sample-public-api'() { // sample POST router
221
+ setJson({name: 'joe'})
222
+ },
223
+ '/api/me'() { // sample private router
224
+ requireAuth()
225
+ setJson({name: authContext.value.name})
226
+ },
227
+ }),
228
+ router.get('/', () => setHtml('ok')), // router.method() accepts 2 formats
229
+ router.get('/health', () => setText('ok')),
230
+ () => { // not found router
231
+ throw new ServerError('Not found', 404, 'NOT_FOUND')
232
+ },
240
233
  )
241
234
 
242
235
  const tcpServer = new Server()
243
- .on('request', async (req, res) => {
244
- try {
245
- await chain(
246
- dxServer(req, res, {jsonBeautify: process.env.NODE_ENV !== 'production'}), // basic dx-server context
247
- serverChain,
248
- )()
249
- } catch (e) {
250
- console.error(e)
251
- res.end()
252
- }
253
- })
236
+ .on('request', async (req, res) => {
237
+ try {
238
+ await chain(
239
+ dxServer(req, res, {jsonBeautify: process.env.NODE_ENV !== 'production'}), // basic dx-server context
240
+ serverChain,
241
+ )()
242
+ } catch (e) {
243
+ console.error(e)
244
+ res.end()
245
+ }
246
+ })
254
247
 
255
248
  await promisify(tcpServer.listen.bind(tcpServer))(3000)
256
249
  console.log('server is listening at 3000')
@@ -267,9 +260,9 @@ dx-server uses Node.js AsyncLocalStorage to provide request/response context glo
267
260
  import {getReq, getRes} from 'dx-server'
268
261
 
269
262
  function someDeepFunction() {
270
- const req = getReq() // No need to pass req through multiple layers
271
- const res = getRes()
272
- res.setHeader('X-Custom', 'value')
263
+ const req = getReq() // No need to pass req through multiple layers
264
+ const res = getRes()
265
+ res.setHeader('X-Custom', 'value')
273
266
  }
274
267
  ```
275
268
 
@@ -286,11 +279,11 @@ const text = await getText()
286
279
 
287
280
  // Sync usage (requires chaining)
288
281
  chain(
289
- getJson.chain({bodyLimit: 1024 * 1024}), // 1MB limit
290
- next => {
291
- console.log(getJson.value) // Access synchronously
292
- return next()
293
- }
282
+ getJson.chain({bodyLimit: 1024 * 1024}), // 1MB limit
283
+ next => {
284
+ console.log(getJson.value) // Access synchronously
285
+ return next()
286
+ }
294
287
  )
295
288
  ```
296
289
 
@@ -303,21 +296,21 @@ import {makeDxContext, getReq} from 'dx-server'
303
296
 
304
297
  // Create auth context
305
298
  const authContext = makeDxContext(async () => {
306
- const token = getReq().headers.authorization
307
- if (!token) return null
308
- return await validateToken(token) // Your validation logic
299
+ const token = getReq().headers.authorization
300
+ if (!token) return null
301
+ return await validateToken(token) // Your validation logic
309
302
  })
310
303
 
311
304
  // Use in middleware
312
305
  chain(
313
- authContext.chain(), // Initialize for all requests
314
- next => {
315
- if (!authContext.value) {
316
- setJson({error: 'Unauthorized'}, {status: 401})
317
- return
318
- }
319
- return next()
306
+ authContext.chain(), // Initialize for all requests
307
+ next => {
308
+ if (!authContext.value) {
309
+ setJson({error: 'Unauthorized'}, {status: 401})
310
+ return
320
311
  }
312
+ return next()
313
+ }
321
314
  )
322
315
 
323
316
  ```
@@ -328,18 +321,18 @@ chain(
328
321
 
329
322
  ```javascript
330
323
  import dxServer, {
331
- // Request/Response access
332
- getReq, getRes,
333
-
334
- // Request body parsers
335
- getBuffer, getJson, getRaw, getText, getUrlEncoded, getQuery,
336
-
337
- // Response setters
338
- setHtml, setJson, setText, setEmpty, setBuffer, setRedirect,
339
- setNodeStream, setWebStream, setFile,
340
-
341
- // Utilities
342
- router, connectMiddlewares, chainStatic, makeDxContext
324
+ // Request/Response access
325
+ getReq, getRes,
326
+
327
+ // Request body parsers
328
+ getBuffer, getJson, getRaw, getText, getUrlEncoded, getQuery,
329
+
330
+ // Response setters
331
+ setHtml, setJson, setText, setEmpty, setBuffer, setRedirect,
332
+ setNodeStream, setWebStream, setFile,
333
+
334
+ // Utilities
335
+ router, connectMiddlewares, chainStatic, makeDxContext
343
336
  } from 'dx-server'
344
337
 
345
338
  // Express integration (requires express installed)
@@ -347,9 +340,9 @@ import {expressApp, expressRouter} from 'dx-server/express'
347
340
 
348
341
  // Low-level helpers
349
342
  import {
350
- setBufferBodyDefaultOptions,
351
- bufferFromReq, jsonFromReq, rawFromReq, textFromReq,
352
- urlEncodedFromReq, queryFromReq,
343
+ setBufferBodyDefaultOptions,
344
+ bufferFromReq, jsonFromReq, rawFromReq, textFromReq,
345
+ urlEncodedFromReq, queryFromReq,
353
346
  } from 'dx-server/helpers'
354
347
  ```
355
348
 
@@ -372,9 +365,9 @@ All body parsers are async, lazy-loaded, and cached per request:
372
365
  Options:
373
366
  ```typescript
374
367
  {
375
- bodyLimit?: number // Max body size in bytes (default: 100KB)
376
- urlEncodedParser?: (search: string) => any
377
- queryParser?: (search: string) => any
368
+ bodyLimit?: number // Max body size in bytes (default: 100KB)
369
+ urlEncodedParser?: (search: string) => any
370
+ queryParser?: (search: string) => any
378
371
  }
379
372
  ```
380
373
 
@@ -409,11 +402,12 @@ Options:
409
402
  - **`chainStatic(pattern, options)`** - Serve static files
410
403
  ```javascript
411
404
  chainStatic('/public/*', {
412
- root: '/path/to/files',
413
- index: ['index.html'],
414
- dotfiles: 'deny',
415
- etag: true,
416
- lastModified: true
405
+ root: '/path/to/files',
406
+ getPathname(matched){return matched.pathname}, // take URLPattern matched object, epects to return the file path
407
+ // the returned file path must be run through decodeURIComponent before returning
408
+ dotfiles: 'deny',
409
+ disableEtag: false,
410
+ lastModified: true
417
411
  })
418
412
  ```
419
413
 
@@ -426,15 +420,15 @@ import {router} from 'dx-server'
426
420
 
427
421
  // Single route
428
422
  router.get('/users/:id', ({matched}) => {
429
- const {id} = matched.pathname.groups
430
- setJson({userId: id})
423
+ const {id} = matched.pathname.groups
424
+ setJson({userId: id})
431
425
  })
432
426
 
433
427
  // Multiple routes
434
428
  router.post({
435
- '/api/users': () => { /* create user */ },
436
- '/api/users/:id': ({matched}) => { /* update user */ },
437
- '/api/users/:id/posts': ({matched}) => { /* get user posts */ }
429
+ '/api/users'() { /* create user */ },
430
+ '/api/users/:id'({matched}) { /* update user */ },
431
+ '/api/users/:id/posts'({matched}) { /* get user posts */ }
438
432
  })
439
433
 
440
434
  // All HTTP methods supported
@@ -452,8 +446,8 @@ router.method('CUSTOM', pattern, handler)
452
446
 
453
447
  // With prefix option
454
448
  router.get({
455
- '/users': listUsers,
456
- '/users/:id': getUser
449
+ '/users': listUsers,
450
+ '/users/:id': getUser
457
451
  }, {prefix: '/api'}) // Routes become /api/users, /api/users/:id
458
452
  ```
459
453
 
@@ -482,21 +476,21 @@ import cors from 'cors'
482
476
  import helmet from 'helmet'
483
477
 
484
478
  chain(
485
- // Use entire Express app
486
- await expressApp(app => {
487
- app.set('trust proxy', true)
488
- app.set('json spaces', 2)
489
- app.use(helmet())
490
- app.use('/static', express.static('public'))
491
- }),
492
-
493
- // Or use Express router
494
- expressRouter(router => {
495
- router.use(cors())
496
- router.get('/legacy', (req, res) => {
497
- res.json({message: 'Express route'})
498
- })
479
+ // Use entire Express app
480
+ await expressApp(app => {
481
+ app.set('trust proxy', true)
482
+ app.set('json spaces', 2)
483
+ app.use(helmet())
484
+ app.use('/static', express.static('public'))
485
+ }),
486
+
487
+ // Or use Express router
488
+ expressRouter(router => {
489
+ router.use(cors())
490
+ router.get('/legacy', (req, res) => {
491
+ res.json({message: 'Express route'})
499
492
  })
493
+ })
500
494
  )
501
495
  ```
502
496
 
@@ -506,15 +500,15 @@ Pure functions for custom implementations:
506
500
 
507
501
  ```javascript
508
502
  import {
509
- setBufferBodyDefaultOptions,
510
- bufferFromReq, jsonFromReq, rawFromReq,
511
- textFromReq, urlEncodedFromReq, queryFromReq
503
+ setBufferBodyDefaultOptions,
504
+ bufferFromReq, jsonFromReq, rawFromReq,
505
+ textFromReq, urlEncodedFromReq, queryFromReq
512
506
  } from 'dx-server/helpers'
513
507
 
514
508
  // Set global defaults
515
509
  setBufferBodyDefaultOptions({
516
- bodyLimit: 10 * 1024 * 1024, // 10MB
517
- queryParser: (search) => myCustomParser(search)
510
+ bodyLimit: 10 * 1024 * 1024, // 10MB
511
+ queryParser(search){return myCustomParser(search)}
518
512
  })
519
513
 
520
514
  // Use directly with req/res (no context required)
@@ -529,9 +523,9 @@ Always set appropriate body size limits to prevent DoS attacks:
529
523
 
530
524
  ```javascript
531
525
  chain(
532
- getJson.chain({bodyLimit: 1024 * 1024}), // 1MB limit
533
- // or globally:
534
- dxServer(req, res, {bodyLimit: 5 * 1024 * 1024}) // 5MB
526
+ getJson.chain({bodyLimit: 1024 * 1024}), // 1MB limit
527
+ // or globally:
528
+ dxServer(req, res, {bodyLimit: 5 * 1024 * 1024}) // 5MB
535
529
  )
536
530
  ```
537
531
 
@@ -540,26 +534,26 @@ Never expose internal errors to clients:
540
534
 
541
535
  ```javascript
542
536
  class AppError extends Error {
543
- constructor(message, status = 400, code = 'ERROR') {
544
- super(message)
545
- this.status = status
546
- this.code = code
547
- }
537
+ constructor(message, status = 400, code = 'ERROR') {
538
+ super(message)
539
+ this.status = status
540
+ this.code = code
541
+ }
548
542
  }
549
543
 
550
544
  chain(
551
- async next => {
552
- try {
553
- await next()
554
- } catch (error) {
555
- if (error instanceof AppError) {
556
- setJson({error: error.message, code: error.code}, {status: error.status})
557
- } else {
558
- console.error(error) // Log for debugging
559
- setJson({error: 'Internal server error'}, {status: 500})
560
- }
561
- }
545
+ async next => {
546
+ try {
547
+ await next()
548
+ } catch (error) {
549
+ if (error instanceof AppError) {
550
+ setJson({error: error.message, code: error.code}, {status: error.status})
551
+ } else {
552
+ console.error(error) // Log for debugging
553
+ setJson({error: 'Internal server error'}, {status: 500})
554
+ }
562
555
  }
556
+ }
563
557
  )
564
558
  ```
565
559
 
@@ -568,14 +562,14 @@ Always validate input data:
568
562
 
569
563
  ```javascript
570
564
  router.post('/api/users', async () => {
571
- const data = await getJson()
572
-
573
- // Validate
574
- if (!data?.email || !isValidEmail(data.email)) {
575
- throw new AppError('Invalid email', 400, 'INVALID_EMAIL')
576
- }
577
-
578
- // Process...
565
+ const data = await getJson()
566
+
567
+ // Validate
568
+ if (!data?.email || !isValidEmail(data.email)) {
569
+ throw new AppError('Invalid email', 400, 'INVALID_EMAIL')
570
+ }
571
+
572
+ // Process...
579
573
  })
580
574
  ```
581
575
 
@@ -587,13 +581,13 @@ import helmet from 'helmet'
587
581
  import cors from 'cors'
588
582
 
589
583
  chain(
590
- connectMiddlewares(
591
- helmet(),
592
- cors({
593
- origin: process.env.ALLOWED_ORIGINS?.split(','),
594
- credentials: true
595
- })
596
- )
584
+ connectMiddlewares(
585
+ helmet(),
586
+ cors({
587
+ origin: process.env.ALLOWED_ORIGINS?.split(','),
588
+ credentials: true
589
+ })
590
+ )
597
591
  )
598
592
  ```
599
593
 
@@ -604,14 +598,14 @@ chain(
604
598
  import busboy from 'busboy'
605
599
 
606
600
  router.post('/upload', () => {
607
- const req = getReq()
608
- const bb = busboy({headers: req.headers, limits: {fileSize: 10 * 1024 * 1024}})
609
-
610
- bb.on('file', (name, file, info) => {
611
- // Handle file stream
612
- })
613
-
614
- req.pipe(bb)
601
+ const req = getReq()
602
+ const bb = busboy({headers: req.headers, limits: {fileSize: 10 * 1024 * 1024}})
603
+
604
+ bb.on('file', (name, file, info) => {
605
+ // Handle file stream
606
+ })
607
+
608
+ req.pipe(bb)
615
609
  })
616
610
  ```
617
611
 
@@ -622,11 +616,11 @@ import {WebSocketServer} from 'ws'
622
616
  const wss = new WebSocketServer({noServer: true})
623
617
 
624
618
  server.on('upgrade', (request, socket, head) => {
625
- if (request.url === '/ws') {
626
- wss.handleUpgrade(request, socket, head, (ws) => {
627
- wss.emit('connection', ws, request)
628
- })
629
- }
619
+ if (request.url === '/ws') {
620
+ wss.handleUpgrade(request, socket, head, ws => {
621
+ wss.emit('connection', ws, request)
622
+ })
623
+ }
630
624
  })
631
625
  ```
632
626
 
@@ -635,12 +629,12 @@ server.on('upgrade', (request, socket, head) => {
635
629
  import rateLimit from 'express-rate-limit'
636
630
 
637
631
  chain(
638
- connectMiddlewares(
639
- rateLimit({
640
- windowMs: 15 * 60 * 1000, // 15 minutes
641
- max: 100 // limit each IP to 100 requests per windowMs
642
- })
643
- )
632
+ connectMiddlewares(
633
+ rateLimit({
634
+ windowMs: 15 * 60 * 1000, // 15 minutes
635
+ max: 100 // limit each IP to 100 requests per windowMs
636
+ })
637
+ )
644
638
  )
645
639
  ```
646
640
 
@@ -661,14 +655,14 @@ chain(
661
655
  ```javascript
662
656
  // Express
663
657
  app.get('/users/:id', (req, res) => {
664
- const {id} = req.params
665
- res.json({userId: id})
658
+ const {id} = req.params
659
+ res.json({userId: id})
666
660
  })
667
661
 
668
662
  // dx-server
669
663
  router.get('/users/:id', ({matched}) => {
670
- const {id} = matched.pathname.groups
671
- setJson({userId: id})
664
+ const {id} = matched.pathname.groups
665
+ setJson({userId: id})
672
666
  })
673
667
  ```
674
668