zero-http 0.2.0 → 0.2.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.
- package/README.md +314 -115
- package/documentation/full-server.js +82 -1
- package/documentation/public/data/api.json +154 -33
- package/documentation/public/data/examples.json +35 -11
- package/documentation/public/data/options.json +14 -8
- package/documentation/public/index.html +109 -17
- package/documentation/public/scripts/data-sections.js +4 -4
- package/documentation/public/scripts/helpers.js +4 -4
- package/documentation/public/scripts/playground.js +201 -0
- package/documentation/public/scripts/ui.js +6 -6
- package/documentation/public/scripts/uploads.js +9 -9
- package/documentation/public/styles.css +12 -12
- package/documentation/public/vendor/icons/compress.svg +27 -0
- package/documentation/public/vendor/icons/https.svg +23 -0
- package/documentation/public/vendor/icons/router.svg +29 -0
- package/documentation/public/vendor/icons/sse.svg +25 -0
- package/documentation/public/vendor/icons/websocket.svg +21 -0
- package/index.js +21 -4
- package/lib/app.js +145 -15
- package/lib/body/json.js +3 -0
- package/lib/body/multipart.js +2 -0
- package/lib/body/raw.js +3 -0
- package/lib/body/text.js +3 -0
- package/lib/body/urlencoded.js +3 -0
- package/lib/{fetch.js → fetch/index.js} +30 -1
- package/lib/http/index.js +9 -0
- package/lib/{request.js → http/request.js} +7 -1
- package/lib/{response.js → http/response.js} +70 -1
- package/lib/middleware/compress.js +194 -0
- package/lib/middleware/index.js +12 -0
- package/lib/router/index.js +278 -0
- package/lib/sse/index.js +8 -0
- package/lib/sse/stream.js +322 -0
- package/lib/ws/connection.js +440 -0
- package/lib/ws/handshake.js +122 -0
- package/lib/ws/index.js +12 -0
- package/package.json +1 -1
- package/lib/router.js +0 -87
- /package/lib/{cors.js → middleware/cors.js} +0 -0
- /package/lib/{logger.js → middleware/logger.js} +0 -0
- /package/lib/{rateLimit.js → middleware/rateLimit.js} +0 -0
- /package/lib/{static.js → middleware/static.js} +0 -0
package/README.md
CHANGED
|
@@ -7,21 +7,26 @@
|
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
[](package.json)
|
|
9
9
|
|
|
10
|
-
> **Zero-dependency,
|
|
10
|
+
> **Zero-dependency, Express-like HTTP/HTTPS server with built-in WebSocket, Server-Sent Events, response compression, modular routing, and a tiny fetch client.**
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- **Zero dependencies** — implemented using Node core APIs only
|
|
15
15
|
- **Express-like API** — `createApp()`, `use()`, `get()`, `post()`, `put()`, `delete()`, `patch()`, `head()`, `options()`, `all()`, `listen()`
|
|
16
|
-
- **
|
|
16
|
+
- **HTTPS support** — pass `{ key, cert }` to `listen()` for TLS; `req.secure` and `req.protocol` available everywhere
|
|
17
|
+
- **Built-in WebSocket server** — `app.ws('/path', handler)` with RFC 6455 framing, ping/pong, sub-protocols, `verifyClient`, per-connection data store
|
|
18
|
+
- **Server-Sent Events** — `res.sse()` returns a rich stream controller with auto-IDs, keep-alive, retry hints, and event counting
|
|
19
|
+
- **Response compression** — `compress()` middleware with brotli/gzip/deflate negotiation, threshold and filter options
|
|
20
|
+
- **Router sub-apps** — `Router()` factory with `.use()` mounting, nested sub-routers, route chaining, and introspection via `app.routes()`
|
|
21
|
+
- **Protocol-aware routing** — `{ secure: true }` option on routes to match HTTPS-only or HTTP-only requests
|
|
22
|
+
- **Built-in middlewares** — `cors()`, `json()`, `urlencoded()`, `text()`, `raw()`, `multipart()`, `rateLimit()`, `logger()`, `compress()`
|
|
23
|
+
- **Body parser HTTPS enforcement** — `{ requireSecure: true }` on any body parser to reject non-TLS requests with 403
|
|
17
24
|
- **Streaming multipart parser** — writes file parts to disk and exposes `req.body.files` and `req.body.fields`
|
|
18
|
-
- **Tiny `fetch` replacement** —
|
|
25
|
+
- **Tiny `fetch` replacement** — server-side HTTP/HTTPS client with TLS options passthrough, progress callbacks, abort support
|
|
19
26
|
- **Static file serving** — 60+ MIME types, dotfile policy, caching, extension fallback
|
|
20
27
|
- **Error handling** — automatic 500 responses for thrown errors, global error handler via `app.onError()`
|
|
21
|
-
- **Path-prefix middleware** — `app.use('/api', handler)` with automatic URL rewriting
|
|
22
28
|
- **Rate limiting** — in-memory IP-based rate limiter with configurable windows
|
|
23
29
|
- **Request logger** — colorized dev/short/tiny log formats
|
|
24
|
-
|
|
25
30
|
|
|
26
31
|
```bash
|
|
27
32
|
npm install zero-http
|
|
@@ -38,7 +43,7 @@ app.post('/echo', (req, res) => res.json({ received: req.body }))
|
|
|
38
43
|
app.listen(3000)
|
|
39
44
|
```
|
|
40
45
|
|
|
41
|
-
Demo
|
|
46
|
+
## Demo
|
|
42
47
|
|
|
43
48
|
You can view the live documentation and playground at https://zero-http.molex.cloud, or run the demo locally:
|
|
44
49
|
|
|
@@ -52,41 +57,53 @@ node documentation/full-server.js
|
|
|
52
57
|
All exports are available from the package root:
|
|
53
58
|
|
|
54
59
|
```js
|
|
55
|
-
const {
|
|
60
|
+
const {
|
|
61
|
+
createApp, Router, cors, fetch,
|
|
62
|
+
json, urlencoded, text, raw, multipart,
|
|
63
|
+
static: serveStatic, rateLimit, logger, compress,
|
|
64
|
+
WebSocketConnection, SSEStream
|
|
65
|
+
} = require('zero-http')
|
|
56
66
|
```
|
|
57
67
|
|
|
58
68
|
| Export | Type | Description |
|
|
59
69
|
|---|---|---|
|
|
60
70
|
| `createApp()` | function | Create a new application instance (router + middleware stack). |
|
|
71
|
+
| `Router()` | function | Create a standalone Router for modular route grouping. |
|
|
61
72
|
| `cors` | function | CORS middleware factory. |
|
|
62
|
-
| `fetch` | function |
|
|
73
|
+
| `fetch` | function | Node HTTP/HTTPS client with TLS options passthrough. |
|
|
63
74
|
| `json` | function | JSON body parser factory. |
|
|
64
|
-
| `urlencoded` | function |
|
|
75
|
+
| `urlencoded` | function | URL-encoded body parser factory. |
|
|
65
76
|
| `text` | function | Text body parser factory. |
|
|
66
77
|
| `raw` | function | Raw bytes parser factory. |
|
|
67
78
|
| `multipart` | function | Streaming multipart parser factory. |
|
|
68
79
|
| `static` | function | Static file serving middleware factory. |
|
|
69
80
|
| `rateLimit` | function | In-memory rate-limiting middleware factory. |
|
|
70
81
|
| `logger` | function | Request-logging middleware factory. |
|
|
82
|
+
| `compress` | function | Response compression middleware (brotli/gzip/deflate). |
|
|
83
|
+
| `WebSocketConnection` | class | WebSocket connection wrapper (for advanced usage). |
|
|
84
|
+
| `SSEStream` | class | SSE stream controller (for advanced usage). |
|
|
71
85
|
|
|
72
|
-
createApp() methods
|
|
86
|
+
### createApp() methods
|
|
73
87
|
|
|
74
88
|
| Method | Signature | Description |
|
|
75
89
|
|---|---|---|
|
|
76
|
-
| `use` | `use(fn)` or `use(path, fn)` | Register middleware globally
|
|
77
|
-
| `get` | `get(path, ...handlers)` | Register GET route handlers. |
|
|
78
|
-
| `post` | `post(path, ...handlers)` | Register POST route handlers. |
|
|
79
|
-
| `put` | `put(path, ...handlers)` | Register PUT route handlers. |
|
|
80
|
-
| `delete` | `delete(path, ...handlers)` | Register DELETE route handlers. |
|
|
81
|
-
| `patch` | `patch(path, ...handlers)` | Register PATCH route handlers. |
|
|
82
|
-
| `options` | `options(path, ...handlers)` | Register OPTIONS route handlers. |
|
|
83
|
-
| `head` | `head(path, ...handlers)` | Register HEAD route handlers. |
|
|
84
|
-
| `all` | `all(path, ...handlers)` | Register handlers for ALL HTTP methods. |
|
|
90
|
+
| `use` | `use(fn)` or `use(path, fn)` or `use(path, router)` | Register middleware globally, scoped to a path prefix, or mount a sub-router. |
|
|
91
|
+
| `get` | `get(path, [opts], ...handlers)` | Register GET route handlers. Optional `{ secure }` options object. |
|
|
92
|
+
| `post` | `post(path, [opts], ...handlers)` | Register POST route handlers. |
|
|
93
|
+
| `put` | `put(path, [opts], ...handlers)` | Register PUT route handlers. |
|
|
94
|
+
| `delete` | `delete(path, [opts], ...handlers)` | Register DELETE route handlers. |
|
|
95
|
+
| `patch` | `patch(path, [opts], ...handlers)` | Register PATCH route handlers. |
|
|
96
|
+
| `options` | `options(path, [opts], ...handlers)` | Register OPTIONS route handlers. |
|
|
97
|
+
| `head` | `head(path, [opts], ...handlers)` | Register HEAD route handlers. |
|
|
98
|
+
| `all` | `all(path, [opts], ...handlers)` | Register handlers for ALL HTTP methods. |
|
|
99
|
+
| `ws` | `ws(path, [opts], handler)` | Register a WebSocket upgrade handler. |
|
|
85
100
|
| `onError` | `onError(fn)` | Register a global error handler `fn(err, req, res, next)`. |
|
|
86
|
-
| `listen` | `listen(port
|
|
101
|
+
| `listen` | `listen(port, [tlsOpts], [cb])` | Start HTTP or HTTPS server. Pass `{ key, cert }` for TLS. |
|
|
102
|
+
| `close` | `close([cb])` | Gracefully close the server. |
|
|
103
|
+
| `routes` | `routes()` | Return a flat list of all registered routes (introspection). |
|
|
87
104
|
| `handler` | property | Bound request handler for `http.createServer(app.handler)`. |
|
|
88
105
|
|
|
89
|
-
Request (`req`) properties & helpers
|
|
106
|
+
### Request (`req`) properties & helpers
|
|
90
107
|
|
|
91
108
|
| Property / Method | Type | Description |
|
|
92
109
|
|---|---|---|
|
|
@@ -97,11 +114,13 @@ Request (`req`) properties & helpers
|
|
|
97
114
|
| `params` | object | Route parameters (populated by router). |
|
|
98
115
|
| `body` | any | Parsed body (populated by body parsers). |
|
|
99
116
|
| `ip` | string | Remote IP address of the client. |
|
|
117
|
+
| `secure` | boolean | `true` when the connection is over TLS (HTTPS). |
|
|
118
|
+
| `protocol` | string | `'https'` or `'http'`. |
|
|
100
119
|
| `get(name)` | function | Get a request header (case-insensitive). |
|
|
101
120
|
| `is(type)` | function | Check if Content-Type matches a type (e.g. `'json'`, `'text/html'`). |
|
|
102
121
|
| `raw` | object | Underlying `http.IncomingMessage`. |
|
|
103
122
|
|
|
104
|
-
Response (`res`) helpers
|
|
123
|
+
### Response (`res`) helpers
|
|
105
124
|
|
|
106
125
|
| Method | Signature | Description |
|
|
107
126
|
|---|---|---|
|
|
@@ -114,53 +133,213 @@ Response (`res`) helpers
|
|
|
114
133
|
| `text` | `text(str)` | Set text/plain and send string. |
|
|
115
134
|
| `html` | `html(str)` | Set text/html and send string. |
|
|
116
135
|
| `redirect` | `redirect([status], url)` | Redirect to URL (default 302). |
|
|
136
|
+
| `sse` | `sse([opts])` | Open a Server-Sent Events stream. Returns an `SSEStream` controller. |
|
|
137
|
+
|
|
138
|
+
### WebSocket — `app.ws(path, [opts], handler)`
|
|
139
|
+
|
|
140
|
+
Register a WebSocket upgrade handler. The handler receives `(ws, req)` where `ws` is a `WebSocketConnection`.
|
|
141
|
+
|
|
142
|
+
| Option | Type | Default | Description |
|
|
143
|
+
|---|---:|---|---|
|
|
144
|
+
| `maxPayload` | number | `1048576` | Maximum incoming frame size in bytes (1 MB). |
|
|
145
|
+
| `pingInterval` | number | `30000` | Auto-ping interval in ms. `0` to disable. |
|
|
146
|
+
| `verifyClient` | function | — | `(req) => boolean` — return `false` to reject the upgrade with 403. |
|
|
147
|
+
|
|
148
|
+
**WebSocketConnection properties:**
|
|
149
|
+
|
|
150
|
+
| Property | Type | Description |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `id` | string | Unique connection ID (e.g. `ws_1_l8x3k`). |
|
|
153
|
+
| `readyState` | number | 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED. |
|
|
154
|
+
| `protocol` | string | Negotiated sub-protocol. |
|
|
155
|
+
| `headers` | object | Upgrade request headers. |
|
|
156
|
+
| `ip` | string | Remote IP address. |
|
|
157
|
+
| `query` | object | Parsed query params from the upgrade URL. |
|
|
158
|
+
| `url` | string | Full upgrade URL. |
|
|
159
|
+
| `secure` | boolean | `true` for WSS connections. |
|
|
160
|
+
| `extensions` | string | Requested WebSocket extensions header. |
|
|
161
|
+
| `maxPayload` | number | Max incoming payload bytes. |
|
|
162
|
+
| `connectedAt` | number | Timestamp (ms) of connection. |
|
|
163
|
+
| `uptime` | number | Milliseconds since connection (computed). |
|
|
164
|
+
| `bufferedAmount` | number | Bytes waiting to be flushed. |
|
|
165
|
+
| `data` | object | Arbitrary user-data store. |
|
|
166
|
+
|
|
167
|
+
**WebSocketConnection methods:**
|
|
168
|
+
|
|
169
|
+
| Method | Description |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `send(data, [opts])` | Send text or binary message. `opts.binary` to force binary frame. |
|
|
172
|
+
| `sendJSON(obj)` | Send JSON-serialised text message. |
|
|
173
|
+
| `ping([payload])` | Send a ping frame. |
|
|
174
|
+
| `pong([payload])` | Send a pong frame. |
|
|
175
|
+
| `close([code], [reason])` | Graceful close with optional status code. |
|
|
176
|
+
| `terminate()` | Forcefully destroy the socket. |
|
|
177
|
+
| `on(event, fn)` | Listen for `'message'`, `'close'`, `'error'`, `'ping'`, `'pong'`, `'drain'`. |
|
|
178
|
+
| `once(event, fn)` | One-time event listener. |
|
|
179
|
+
| `off(event, fn)` | Remove a listener. |
|
|
180
|
+
| `removeAllListeners([event])` | Remove all listeners for an event, or all events. |
|
|
181
|
+
| `listenerCount(event)` | Return the number of listeners for an event. |
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
app.ws('/chat', { maxPayload: 64 * 1024 }, (ws, req) => {
|
|
187
|
+
ws.send('Welcome!')
|
|
188
|
+
ws.on('message', data => ws.send('echo: ' + data))
|
|
189
|
+
ws.on('close', () => console.log(ws.id, 'left'))
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Server-Sent Events — `res.sse([opts])`
|
|
194
|
+
|
|
195
|
+
Open an SSE stream. Returns an `SSEStream` controller.
|
|
196
|
+
|
|
197
|
+
| Option | Type | Default | Description |
|
|
198
|
+
|---|---:|---|---|
|
|
199
|
+
| `status` | number | `200` | HTTP status code for the SSE response. |
|
|
200
|
+
| `retry` | number | — | Reconnection interval hint (ms) sent to client. |
|
|
201
|
+
| `keepAlive` | number | `0` | Auto keep-alive comment interval (ms). |
|
|
202
|
+
| `keepAliveComment` | string | `'ping'` | Comment text sent by the keep-alive timer. |
|
|
203
|
+
| `autoId` | boolean | `false` | Auto-increment event IDs. |
|
|
204
|
+
| `startId` | number | `1` | Starting value for auto-IDs. |
|
|
205
|
+
| `pad` | number | `0` | Bytes of initial padding (helps flush proxy buffers). |
|
|
206
|
+
| `headers` | object | — | Additional response headers. |
|
|
207
|
+
|
|
208
|
+
**SSEStream methods:**
|
|
209
|
+
|
|
210
|
+
| Method | Description |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `send(data, [id])` | Send an unnamed data event. Objects are auto-JSON-serialised. |
|
|
213
|
+
| `sendJSON(obj, [id])` | Alias for `send()` with an object. |
|
|
214
|
+
| `event(name, data, [id])` | Send a named event. |
|
|
215
|
+
| `comment(text)` | Send a comment line (keep-alive or debug). |
|
|
216
|
+
| `retry(ms)` | Update the reconnection interval hint. |
|
|
217
|
+
| `keepAlive(ms, [comment])` | Start/restart a keep-alive timer. |
|
|
218
|
+
| `flush()` | Flush buffered data through proxies. |
|
|
219
|
+
| `close()` | Close the stream from the server side. |
|
|
220
|
+
| `on(event, fn)` | Listen for `'close'` or `'error'` events. |
|
|
221
|
+
| `once(event, fn)` | One-time event listener. |
|
|
222
|
+
| `off(event, fn)` | Remove a specific event listener. |
|
|
223
|
+
| `removeAllListeners([event])` | Remove all listeners for an event, or all events. |
|
|
224
|
+
| `listenerCount(event)` | Return the number of listeners for an event. |
|
|
225
|
+
|
|
226
|
+
**SSEStream properties:** `connected`, `eventCount`, `bytesSent`, `connectedAt`, `uptime`, `lastEventId`, `secure`, `data`.
|
|
227
|
+
|
|
228
|
+
**SSEStream events:** `'close'` (client disconnect or server close), `'error'` (write error).
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
app.get('/events', (req, res) => {
|
|
234
|
+
const sse = res.sse({ retry: 5000, keepAlive: 30000, autoId: true })
|
|
235
|
+
sse.send('hello')
|
|
236
|
+
sse.event('update', { x: 1 })
|
|
237
|
+
sse.on('close', () => console.log('client disconnected'))
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Router sub-apps
|
|
242
|
+
|
|
243
|
+
Create modular route groups with `Router()`:
|
|
244
|
+
|
|
245
|
+
```js
|
|
246
|
+
const { createApp, Router, json } = require('zero-http')
|
|
247
|
+
const app = createApp()
|
|
248
|
+
const api = Router()
|
|
249
|
+
|
|
250
|
+
api.get('/users', (req, res) => res.json([]))
|
|
251
|
+
api.get('/users/:id', (req, res) => res.json({ id: req.params.id }))
|
|
252
|
+
|
|
253
|
+
app.use(json())
|
|
254
|
+
app.use('/api', api)
|
|
255
|
+
app.listen(3000)
|
|
256
|
+
|
|
257
|
+
// Introspection
|
|
258
|
+
console.log(app.routes())
|
|
259
|
+
// [{ method: 'GET', path: '/api/users' }, { method: 'GET', path: '/api/users/:id' }]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Route chaining:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
const router = Router()
|
|
266
|
+
router.route('/items')
|
|
267
|
+
.get((req, res) => res.json([]))
|
|
268
|
+
.post((req, res) => res.status(201).json(req.body))
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Protocol-aware routes:
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
// Only matches HTTPS requests
|
|
275
|
+
app.get('/secret', { secure: true }, (req, res) => res.json({ secret: 42 }))
|
|
276
|
+
// Only matches plain HTTP
|
|
277
|
+
app.get('/public', { secure: false }, (req, res) => res.json({ public: true }))
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Response compression — `compress([opts])`
|
|
281
|
+
|
|
282
|
+
Negotiates the best encoding from `Accept-Encoding`: brotli (`br`) > gzip > deflate. Brotli requires Node ≥ 11.7. Automatically skips SSE (`text/event-stream`) streams.
|
|
283
|
+
|
|
284
|
+
| Option | Type | Default | Description |
|
|
285
|
+
|---|---:|---|---|
|
|
286
|
+
| `threshold` | number | `1024` | Minimum response size in bytes before compressing. |
|
|
287
|
+
| `level` | number | `-1` | zlib compression level (1–9, or -1 for default). |
|
|
288
|
+
| `filter` | function | — | `(req, res) => boolean` — return `false` to skip compression. |
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
app.use(compress({ threshold: 512 }))
|
|
292
|
+
```
|
|
117
293
|
|
|
118
294
|
### Body parsers
|
|
119
295
|
|
|
120
|
-
|
|
296
|
+
All body parsers accept a `requireSecure` option. When `true`, non-HTTPS requests are rejected with `403 HTTPS required`.
|
|
121
297
|
|
|
122
|
-
json([opts])
|
|
298
|
+
#### json([opts])
|
|
123
299
|
|
|
124
300
|
| Option | Type | Default | Description |
|
|
125
301
|
|---|---:|---|---|
|
|
126
|
-
| `limit` | number
|
|
302
|
+
| `limit` | number\|string | none | Maximum body size (bytes or unit string like `'1mb'`). |
|
|
127
303
|
| `reviver` | function | — | Function passed to `JSON.parse` for custom reviving. |
|
|
128
304
|
| `strict` | boolean | `true` | When `true` only accepts objects/arrays (rejects primitives). |
|
|
129
|
-
| `type` | string
|
|
305
|
+
| `type` | string\|function | `'application/json'` | MIME matcher for the parser. |
|
|
306
|
+
| `requireSecure` | boolean | `false` | Reject non-HTTPS requests with 403. |
|
|
130
307
|
|
|
131
|
-
urlencoded([opts])
|
|
308
|
+
#### urlencoded([opts])
|
|
132
309
|
|
|
133
310
|
| Option | Type | Default | Description |
|
|
134
311
|
|---|---:|---|---|
|
|
135
|
-
| `extended` | boolean | `false` | When `true` supports
|
|
136
|
-
| `limit` | number
|
|
137
|
-
| `type` | string
|
|
312
|
+
| `extended` | boolean | `false` | When `true` supports nested bracket syntax (`a[b]=1`). |
|
|
313
|
+
| `limit` | number\|string | none | Maximum body size. |
|
|
314
|
+
| `type` | string\|function | `'application/x-www-form-urlencoded'` | MIME matcher. |
|
|
315
|
+
| `requireSecure` | boolean | `false` | Reject non-HTTPS requests with 403. |
|
|
138
316
|
|
|
139
|
-
text([opts])
|
|
317
|
+
#### text([opts])
|
|
140
318
|
|
|
141
319
|
| Option | Type | Default | Description |
|
|
142
320
|
|---|---:|---|---|
|
|
143
|
-
| `type` | string
|
|
144
|
-
| `limit` | number
|
|
321
|
+
| `type` | string\|function | `text/*` | MIME matcher for text bodies. |
|
|
322
|
+
| `limit` | number\|string | none | Maximum body size. |
|
|
145
323
|
| `encoding` | string | `utf8` | Character encoding used to decode bytes. |
|
|
324
|
+
| `requireSecure` | boolean | `false` | Reject non-HTTPS requests with 403. |
|
|
146
325
|
|
|
147
|
-
raw([opts])
|
|
326
|
+
#### raw([opts])
|
|
148
327
|
|
|
149
328
|
| Option | Type | Default | Description |
|
|
150
329
|
|---|---:|---|---|
|
|
151
|
-
| `type` | string
|
|
152
|
-
| `limit` | number
|
|
330
|
+
| `type` | string\|function | `application/octet-stream` | MIME matcher for raw parser. |
|
|
331
|
+
| `limit` | number\|string | none | Maximum body size. |
|
|
332
|
+
| `requireSecure` | boolean | `false` | Reject non-HTTPS requests with 403. |
|
|
153
333
|
|
|
154
|
-
multipart(opts)
|
|
334
|
+
#### multipart(opts)
|
|
155
335
|
|
|
156
336
|
Streaming multipart parser that writes file parts to disk and collects fields.
|
|
157
337
|
|
|
158
338
|
| Option | Type | Default | Description |
|
|
159
339
|
|---|---:|---|---|
|
|
160
|
-
| `dir` | string | `os.tmpdir()/zero-http-uploads` |
|
|
161
|
-
| `maxFileSize` | number | none | Maximum
|
|
162
|
-
|
|
163
|
-
Behavior: `multipart` writes file parts to disk with a generated name preserving the original extension when possible. On completion `req.body` will be `{ fields, files }` where `files` contains metadata: `originalFilename`, `storedName`, `path`, `contentType`, `size`.
|
|
340
|
+
| `dir` | string | `os.tmpdir()/zero-http-uploads` | Upload directory. |
|
|
341
|
+
| `maxFileSize` | number | none | Maximum file size in bytes. Returns 413 on exceed. |
|
|
342
|
+
| `requireSecure` | boolean | `false` | Reject non-HTTPS requests with 403. |
|
|
164
343
|
|
|
165
344
|
### static(rootPath, opts)
|
|
166
345
|
|
|
@@ -168,137 +347,157 @@ Serve static files from `rootPath`.
|
|
|
168
347
|
|
|
169
348
|
| Option | Type | Default | Description |
|
|
170
349
|
|---|---:|---|---|
|
|
171
|
-
| `index` | string
|
|
172
|
-
| `maxAge` | number
|
|
173
|
-
| `dotfiles` | string | `'ignore'` | `'allow'|'deny'|'ignore'
|
|
174
|
-
| `extensions` | string[] | — | Fallback extensions to try
|
|
175
|
-
| `setHeaders` | function | — | Hook `(res, filePath) => {}`
|
|
350
|
+
| `index` | string\|false | `'index.html'` | File to serve for directory requests. |
|
|
351
|
+
| `maxAge` | number\|string | `0` | Cache-Control `max-age` (ms or unit string). |
|
|
352
|
+
| `dotfiles` | string | `'ignore'` | `'allow'|'deny'|'ignore'`. |
|
|
353
|
+
| `extensions` | string[] | — | Fallback extensions to try. |
|
|
354
|
+
| `setHeaders` | function | — | Hook `(res, filePath) => {}` for custom headers. |
|
|
176
355
|
|
|
177
356
|
### cors([opts])
|
|
178
357
|
|
|
179
|
-
Small CORS middleware. Typical options:
|
|
180
|
-
|
|
181
358
|
| Option | Type | Default | Description |
|
|
182
359
|
|---|---:|---|---|
|
|
183
|
-
| `origin` | string
|
|
360
|
+
| `origin` | string\|boolean\|array | `'*'` | Allowed origin(s). `.suffix` for subdomain matching. |
|
|
184
361
|
| `methods` | string | `'GET,POST,PUT,DELETE,OPTIONS'` | Allowed methods. |
|
|
185
|
-
| `credentials` | boolean | `false` |
|
|
362
|
+
| `credentials` | boolean | `false` | Set `Access-Control-Allow-Credentials`. |
|
|
186
363
|
| `allowedHeaders` | string | — | Headers allowed in requests. |
|
|
187
364
|
|
|
188
365
|
### fetch(url, opts)
|
|
189
366
|
|
|
190
|
-
|
|
367
|
+
Node HTTP/HTTPS client. Returns `{ status, statusText, ok, secure, url, headers, text(), json(), arrayBuffer() }`.
|
|
191
368
|
|
|
192
369
|
| Option | Type | Default | Description |
|
|
193
370
|
|---|---:|---|---|
|
|
194
371
|
| `method` | string | `GET` | HTTP method. |
|
|
195
372
|
| `headers` | object | — | Request headers. |
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
|
|
202
|
-
Response: resolved value includes `ok`, `statusText`, and helpers `arrayBuffer()`, `text()`, `json()`.
|
|
373
|
+
| `body` | Buffer\|string\|Stream\|URLSearchParams\|object | — | Request body (objects auto-JSON-encoded). |
|
|
374
|
+
| `timeout` | number | — | Request timeout in ms. |
|
|
375
|
+
| `signal` | AbortSignal | — | Cancel the request. |
|
|
376
|
+
| `agent` | object | — | Custom agent for pooling/proxies. |
|
|
377
|
+
| `onDownloadProgress` / `onUploadProgress` | function | — | `{ loaded, total }` callbacks. |
|
|
203
378
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
```js
|
|
207
|
-
const r = await fetch('https://jsonplaceholder.typicode.com/todos/1', { timeout: 5000 })
|
|
208
|
-
const data = await r.json()
|
|
209
|
-
```
|
|
379
|
+
**TLS options** (passed through for `https:` URLs): `rejectUnauthorized`, `ca`, `cert`, `key`, `pfx`, `passphrase`, `servername`, `ciphers`, `secureProtocol`, `minVersion`, `maxVersion`.
|
|
210
380
|
|
|
211
381
|
### rateLimit([opts])
|
|
212
382
|
|
|
213
|
-
In-memory, per-IP rate-limiting middleware. Sets standard `X-RateLimit-*` headers.
|
|
214
|
-
|
|
215
383
|
| Option | Type | Default | Description |
|
|
216
384
|
|---|---:|---|---|
|
|
217
|
-
| `windowMs` | number | `60000` | Time window in
|
|
218
|
-
| `max` | number | `100` |
|
|
219
|
-
| `message` | string | `'Too many requests…'` | Error message
|
|
385
|
+
| `windowMs` | number | `60000` | Time window in ms. |
|
|
386
|
+
| `max` | number | `100` | Max requests per window per key. |
|
|
387
|
+
| `message` | string | `'Too many requests…'` | Error message. |
|
|
220
388
|
| `statusCode` | number | `429` | HTTP status for rate-limited responses. |
|
|
221
|
-
| `keyGenerator` | function | `(req) => req.ip` | Custom key extraction
|
|
222
|
-
|
|
223
|
-
Example:
|
|
224
|
-
|
|
225
|
-
```js
|
|
226
|
-
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }))
|
|
227
|
-
```
|
|
389
|
+
| `keyGenerator` | function | `(req) => req.ip` | Custom key extraction. |
|
|
228
390
|
|
|
229
391
|
### logger([opts])
|
|
230
392
|
|
|
231
|
-
Request-logging middleware that prints method, url, status, and response time.
|
|
232
|
-
|
|
233
393
|
| Option | Type | Default | Description |
|
|
234
394
|
|---|---:|---|---|
|
|
235
|
-
| `format` | string | `'dev'` |
|
|
395
|
+
| `format` | string | `'dev'` | `'dev'` (colorized), `'short'`, or `'tiny'`. |
|
|
236
396
|
| `logger` | function | `console.log` | Custom log function. |
|
|
237
397
|
| `colors` | boolean | auto (TTY) | Enable/disable ANSI colors. |
|
|
238
398
|
|
|
239
|
-
|
|
399
|
+
### HTTPS
|
|
240
400
|
|
|
241
401
|
```js
|
|
242
|
-
|
|
402
|
+
const fs = require('fs')
|
|
403
|
+
const { createApp } = require('zero-http')
|
|
404
|
+
const app = createApp()
|
|
405
|
+
|
|
406
|
+
app.listen(443, {
|
|
407
|
+
key: fs.readFileSync('key.pem'),
|
|
408
|
+
cert: fs.readFileSync('cert.pem')
|
|
409
|
+
}, () => console.log('HTTPS on 443'))
|
|
243
410
|
```
|
|
244
411
|
|
|
245
|
-
|
|
412
|
+
All modules respect HTTPS: `req.secure`, `req.protocol`, `ws.secure`, `sse.secure`, and `fetch()` response `secure` property.
|
|
246
413
|
|
|
247
|
-
|
|
414
|
+
## Examples
|
|
248
415
|
|
|
249
|
-
|
|
250
|
-
app.onError((err, req, res, next) => {
|
|
251
|
-
console.error(err)
|
|
252
|
-
res.status(500).json({ error: err.message })
|
|
253
|
-
})
|
|
254
|
-
```
|
|
416
|
+
WebSocket chat:
|
|
255
417
|
|
|
256
|
-
|
|
418
|
+
```js
|
|
419
|
+
const { createApp } = require('zero-http')
|
|
420
|
+
const app = createApp()
|
|
257
421
|
|
|
258
|
-
|
|
422
|
+
app.ws('/chat', (ws, req) => {
|
|
423
|
+
ws.send('Welcome to the chat!')
|
|
424
|
+
ws.on('message', msg => {
|
|
425
|
+
ws.send(`You said: ${msg}`)
|
|
426
|
+
})
|
|
427
|
+
})
|
|
259
428
|
|
|
260
|
-
|
|
261
|
-
app.use('/api', myApiRouter)
|
|
429
|
+
app.listen(3000)
|
|
262
430
|
```
|
|
263
431
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
Small JSON API:
|
|
432
|
+
Server-Sent Events:
|
|
267
433
|
|
|
268
434
|
```js
|
|
269
|
-
|
|
270
|
-
const
|
|
435
|
+
app.get('/events', (req, res) => {
|
|
436
|
+
const sse = res.sse({ retry: 5000, autoId: true, keepAlive: 30000 })
|
|
271
437
|
|
|
272
|
-
|
|
273
|
-
|
|
438
|
+
const interval = setInterval(() => {
|
|
439
|
+
sse.event('tick', { time: Date.now() })
|
|
440
|
+
}, 1000)
|
|
274
441
|
|
|
275
|
-
|
|
276
|
-
app.post('/items', (req, res) => {
|
|
277
|
-
items.push(req.body)
|
|
278
|
-
res.status(201)
|
|
279
|
-
res.json({ ok: true })
|
|
442
|
+
sse.on('close', () => clearInterval(interval))
|
|
280
443
|
})
|
|
281
444
|
```
|
|
282
445
|
|
|
283
|
-
|
|
446
|
+
Full-featured server:
|
|
284
447
|
|
|
285
448
|
```js
|
|
286
|
-
|
|
287
|
-
|
|
449
|
+
const path = require('path')
|
|
450
|
+
const { createApp, cors, json, urlencoded, text, compress,
|
|
451
|
+
static: serveStatic, logger, rateLimit, Router } = require('zero-http')
|
|
452
|
+
|
|
453
|
+
const app = createApp()
|
|
454
|
+
|
|
455
|
+
app.use(logger({ format: 'dev' }))
|
|
456
|
+
app.use(cors())
|
|
457
|
+
app.use(compress())
|
|
458
|
+
app.use(rateLimit({ windowMs: 60000, max: 200 }))
|
|
459
|
+
app.use(json({ limit: '1mb' }))
|
|
460
|
+
app.use(urlencoded({ extended: true }))
|
|
461
|
+
app.use(text())
|
|
462
|
+
app.use(serveStatic(path.join(__dirname, 'public')))
|
|
463
|
+
|
|
464
|
+
const api = Router()
|
|
465
|
+
api.get('/health', (req, res) => res.json({ status: 'ok' }))
|
|
466
|
+
api.get('/users/:id', (req, res) => res.json({ id: req.params.id }))
|
|
467
|
+
app.use('/api', api)
|
|
468
|
+
|
|
469
|
+
app.ws('/chat', (ws) => {
|
|
470
|
+
ws.on('message', msg => ws.send('echo: ' + msg))
|
|
288
471
|
})
|
|
289
|
-
```
|
|
290
472
|
|
|
291
|
-
|
|
473
|
+
app.get('/events', (req, res) => {
|
|
474
|
+
const sse = res.sse({ retry: 3000, autoId: true })
|
|
475
|
+
sse.send('connected')
|
|
476
|
+
sse.on('close', () => console.log('bye'))
|
|
477
|
+
})
|
|
292
478
|
|
|
293
|
-
|
|
294
|
-
|
|
479
|
+
app.onError((err, req, res) => {
|
|
480
|
+
res.status(500).json({ error: err.message })
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
app.listen(3000, () => console.log('Server running on :3000'))
|
|
295
484
|
```
|
|
296
485
|
|
|
297
486
|
## File layout
|
|
298
487
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
488
|
+
```
|
|
489
|
+
lib/
|
|
490
|
+
app.js — core App class (middleware, routing, listen, ws)
|
|
491
|
+
body/ — body parsers (json, urlencoded, text, raw, multipart)
|
|
492
|
+
fetch/ — server-side HTTP/HTTPS client
|
|
493
|
+
http/ — Request & Response wrappers
|
|
494
|
+
middleware/ — cors, logger, rateLimit, compress, static
|
|
495
|
+
router/ — Router with sub-app mounting & introspection
|
|
496
|
+
sse/ — SSEStream controller
|
|
497
|
+
ws/ — WebSocket connection & handshake
|
|
498
|
+
documentation/ — demo server, controllers, and public UI
|
|
499
|
+
test/ — integration tests
|
|
500
|
+
```
|
|
302
501
|
|
|
303
502
|
## Testing
|
|
304
503
|
|