dx-server 0.9.0 → 0.10.0

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