sockress 0.2.2 → 0.2.3
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 +374 -74
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,74 +1,374 @@
|
|
|
1
|
-
# Sockress Server
|
|
2
|
-
|
|
3
|
-
Sockress is a socket-first
|
|
4
|
-
|
|
5
|
-
Created by
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
app.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
`
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
1
|
+
# Sockress Server
|
|
2
|
+
|
|
3
|
+
Sockress is a socket-first Node.js framework that mirrors the Express API while automatically upgrading compatible requests to WebSockets. HTTP clients (Postman, curl, third-party services) continue to work with zero changes, so a single codebase can serve both realtime and REST consumers.
|
|
4
|
+
|
|
5
|
+
**Created by [Also Coder](https://alsocoder.com) · GitHub [@alsocoders](https://github.com/alsocoders)**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Express-style routing (`app.get`, `app.post`, `app.put`, `app.patch`, `app.delete`, `app.head`, `app.options`, `app.all`)
|
|
12
|
+
- Unified middleware pipeline for HTTP and WebSocket transports
|
|
13
|
+
- Automatic CORS handling with configurable origins
|
|
14
|
+
- Cookie parsing and setting via `req.cookies` and `res.cookie()`
|
|
15
|
+
- File uploads via `createUploader()` (multer-compatible) that work on both transports
|
|
16
|
+
- Static file serving via `serveStatic()` helper
|
|
17
|
+
- Request context object (`req.context`) for passing data through middleware
|
|
18
|
+
- Graceful shutdown hooks (automatically closes on `beforeExit`, `SIGINT`, `SIGTERM`)
|
|
19
|
+
- Heartbeat management for long-lived WebSocket connections
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install sockress
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Sockress supports both ESM and CommonJS:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
// ESM
|
|
33
|
+
import { sockress, createUploader, serveStatic } from 'sockress';
|
|
34
|
+
|
|
35
|
+
// CommonJS
|
|
36
|
+
const { sockress, createUploader, serveStatic } = require('sockress');
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { sockress } from 'sockress';
|
|
45
|
+
|
|
46
|
+
const app = sockress();
|
|
47
|
+
|
|
48
|
+
app.use((req, res, next) => {
|
|
49
|
+
console.log(`[${req.method}] ${req.path}`);
|
|
50
|
+
next();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
app.get('/ping', (req, res) => {
|
|
54
|
+
res.json({ ok: true, via: 'sockress' });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
app.listen(5051, (err, address) => {
|
|
58
|
+
if (err) throw err;
|
|
59
|
+
console.log(`Sockress listening on ${address?.url}`);
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Routing
|
|
66
|
+
|
|
67
|
+
Sockress supports all standard HTTP methods:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
app.get('/users', (req, res) => {
|
|
71
|
+
res.json({ users: [] });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
app.post('/users', (req, res) => {
|
|
75
|
+
const { name, email } = req.body;
|
|
76
|
+
res.json({ id: 1, name, email });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
app.put('/users/:id', (req, res) => {
|
|
80
|
+
const { id } = req.params;
|
|
81
|
+
res.json({ id, updated: true });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
app.patch('/users/:id', (req, res) => {
|
|
85
|
+
res.json({ patched: true });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
app.delete('/users/:id', (req, res) => {
|
|
89
|
+
res.status(204).end();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
app.all('/catch-all', (req, res) => {
|
|
93
|
+
res.json({ method: req.method });
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Route parameters are available via `req.params`:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
app.get('/users/:userId/posts/:postId', (req, res) => {
|
|
101
|
+
const { userId, postId } = req.params;
|
|
102
|
+
res.json({ userId, postId });
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Middleware
|
|
109
|
+
|
|
110
|
+
Middleware works the same way as Express. Use `app.use()` to register global or path-scoped middleware:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// Global middleware
|
|
114
|
+
app.use((req, res, next) => {
|
|
115
|
+
console.log(`${req.method} ${req.path}`);
|
|
116
|
+
next();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Path-scoped middleware
|
|
120
|
+
app.use('/api', (req, res, next) => {
|
|
121
|
+
req.context.apiVersion = 'v1';
|
|
122
|
+
next();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Multiple middleware
|
|
126
|
+
app.use('/secure', authMiddleware, validateMiddleware, handler);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Error Handling
|
|
130
|
+
|
|
131
|
+
Error handlers have 4 parameters `(err, req, res, next)`:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
app.use((err, req, res, next) => {
|
|
135
|
+
console.error(err);
|
|
136
|
+
res.status(500).json({ error: err.message });
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## File Uploads
|
|
143
|
+
|
|
144
|
+
Use `createUploader()` to handle file uploads. It works for both HTTP and WebSocket transports:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { sockress, createUploader } from 'sockress';
|
|
148
|
+
import path from 'path';
|
|
149
|
+
|
|
150
|
+
const app = sockress();
|
|
151
|
+
const uploadsDir = path.join(process.cwd(), 'uploads');
|
|
152
|
+
|
|
153
|
+
const uploader = createUploader({
|
|
154
|
+
dest: uploadsDir,
|
|
155
|
+
preserveFilename: true,
|
|
156
|
+
limits: { fileSize: 2 * 1024 * 1024 } // 2MB
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Single file upload
|
|
160
|
+
app.post('/avatar', uploader.single('avatar'), (req, res) => {
|
|
161
|
+
if (!req.file) {
|
|
162
|
+
return res.status(400).json({ error: 'avatar missing' });
|
|
163
|
+
}
|
|
164
|
+
res.json({ path: req.file.path, name: req.file.name });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Multiple files
|
|
168
|
+
app.post('/gallery', uploader.array('images', 5), (req, res) => {
|
|
169
|
+
if (!req.files || req.files.length === 0) {
|
|
170
|
+
return res.status(400).json({ error: 'images missing' });
|
|
171
|
+
}
|
|
172
|
+
res.json({ count: req.files.length });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Multiple fields
|
|
176
|
+
app.post('/documents', uploader.fields([
|
|
177
|
+
{ name: 'avatar', maxCount: 1 },
|
|
178
|
+
{ name: 'documents', maxCount: 10 }
|
|
179
|
+
]), (req, res) => {
|
|
180
|
+
const avatar = req.files?.avatar?.[0];
|
|
181
|
+
const documents = req.files?.documents || [];
|
|
182
|
+
res.json({ avatar, documents: documents.length });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Any files
|
|
186
|
+
app.post('/upload', uploader.any(), (req, res) => {
|
|
187
|
+
const files = req.files || {};
|
|
188
|
+
res.json({ files: Object.keys(files) });
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Uploaded files are available via:
|
|
193
|
+
- `req.file` - first uploaded file (when using `single()`)
|
|
194
|
+
- `req.files` - object mapping field names to arrays of files
|
|
195
|
+
|
|
196
|
+
Each file has:
|
|
197
|
+
- `fieldName` - form field name
|
|
198
|
+
- `name` - original filename
|
|
199
|
+
- `type` - MIME type
|
|
200
|
+
- `size` - file size in bytes
|
|
201
|
+
- `buffer` - file contents as Buffer
|
|
202
|
+
- `path` - file path on disk (if `dest` was configured)
|
|
203
|
+
- `lastModified` - file last modified timestamp (if available)
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Static File Serving
|
|
208
|
+
|
|
209
|
+
Use `serveStatic()` to serve static files:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import { sockress, serveStatic } from 'sockress';
|
|
213
|
+
import path from 'path';
|
|
214
|
+
|
|
215
|
+
const app = sockress();
|
|
216
|
+
const uploadsDir = path.join(process.cwd(), 'uploads');
|
|
217
|
+
|
|
218
|
+
app.use('/uploads', serveStatic(uploadsDir, {
|
|
219
|
+
stripPrefix: '/uploads',
|
|
220
|
+
maxAge: 60_000, // cache for 60 seconds
|
|
221
|
+
index: 'index.html' // default file for directories
|
|
222
|
+
}));
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Options:
|
|
226
|
+
- `stripPrefix` - remove this prefix from the request path before resolving files
|
|
227
|
+
- `maxAge` - cache control max age in milliseconds
|
|
228
|
+
- `index` - default file to serve for directories (default: `'index.html'`)
|
|
229
|
+
|
|
230
|
+
You can also use the convenience method:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
app.useStatic('/uploads', uploadsDir, { maxAge: 60_000 });
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Request Object
|
|
239
|
+
|
|
240
|
+
The `req` object provides:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
req.id // unique request ID
|
|
244
|
+
req.method // HTTP method (GET, POST, etc.)
|
|
245
|
+
req.path // request path
|
|
246
|
+
req.query // parsed query string (object)
|
|
247
|
+
req.params // route parameters (object)
|
|
248
|
+
req.headers // request headers (object)
|
|
249
|
+
req.body // parsed request body
|
|
250
|
+
req.cookies // parsed cookies (object)
|
|
251
|
+
req.file // first uploaded file (if any)
|
|
252
|
+
req.files // all uploaded files (object mapping field names to arrays)
|
|
253
|
+
req.type // 'http' or 'socket'
|
|
254
|
+
req.ip // client IP address
|
|
255
|
+
req.protocol // 'http', 'https', 'ws', or 'wss'
|
|
256
|
+
req.secure // boolean indicating if connection is secure
|
|
257
|
+
req.context // plain object for passing data through middleware
|
|
258
|
+
req.raw // IncomingMessage (HTTP only)
|
|
259
|
+
req.get(field) // get header value by name
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Response Object
|
|
265
|
+
|
|
266
|
+
The `res` object provides:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
res.status(code) // set status code
|
|
270
|
+
res.set(field, value) // set header
|
|
271
|
+
res.append(field, value) // append to header
|
|
272
|
+
res.cookie(name, value, opts) // set cookie
|
|
273
|
+
res.clearCookie(name, opts) // clear cookie
|
|
274
|
+
res.json(payload) // send JSON response
|
|
275
|
+
res.send(payload) // send response (auto-detects type)
|
|
276
|
+
res.end() // end response
|
|
277
|
+
res.isSent() // check if response was sent
|
|
278
|
+
res.raw // ServerResponse (HTTP only)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Configuration
|
|
284
|
+
|
|
285
|
+
Create a Sockress app with custom options:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
const app = sockress({
|
|
289
|
+
cors: {
|
|
290
|
+
origin: ['http://localhost:3000', 'https://example.com'],
|
|
291
|
+
credentials: true,
|
|
292
|
+
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
293
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
294
|
+
exposedHeaders: ['X-Custom-Header'],
|
|
295
|
+
maxAge: 600
|
|
296
|
+
},
|
|
297
|
+
socket: {
|
|
298
|
+
path: '/sockress', // WebSocket path (default: '/sockress')
|
|
299
|
+
heartbeatInterval: 30_000, // heartbeat interval in ms (default: 30000)
|
|
300
|
+
idleTimeout: 120_000 // idle timeout in ms (default: 120000)
|
|
301
|
+
},
|
|
302
|
+
bodyLimit: 1_000_000 // max body size in bytes (default: 1000000)
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Listening
|
|
309
|
+
|
|
310
|
+
Start the server with `app.listen()`:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
// Simple
|
|
314
|
+
app.listen(5051);
|
|
315
|
+
|
|
316
|
+
// With callback
|
|
317
|
+
app.listen(5051, (err, address) => {
|
|
318
|
+
if (err) {
|
|
319
|
+
console.error('Failed to start server:', err);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
console.log(`Server listening on ${address?.url}`);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// With host
|
|
326
|
+
app.listen(5051, '0.0.0.0', (err, address) => {
|
|
327
|
+
if (err) throw err;
|
|
328
|
+
console.log(`Server listening on ${address?.url}`);
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
The callback receives:
|
|
333
|
+
- `err` - Error if server failed to start, or `null`
|
|
334
|
+
- `address` - Server address info with `hostname` and `url` properties
|
|
335
|
+
|
|
336
|
+
Sockress automatically registers shutdown hooks for `beforeExit`, `SIGINT`, and `SIGTERM` to gracefully close the server.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Manual Shutdown
|
|
341
|
+
|
|
342
|
+
You can manually close the server:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
const server = app.listen(5051);
|
|
346
|
+
// ... later
|
|
347
|
+
await app.close();
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Companion Client
|
|
353
|
+
|
|
354
|
+
Pair Sockress with [`sockress-client`](https://www.npmjs.com/package/sockress-client) to get automatic socket transports, FormData serialization, and seamless HTTP fallback:
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
import { sockressClient } from 'sockress-client';
|
|
358
|
+
|
|
359
|
+
const api = sockressClient({ baseUrl: 'http://localhost:5051' });
|
|
360
|
+
const response = await api.post('/api/auth/login', {
|
|
361
|
+
body: { email: 'user@example.com', password: 'secret' }
|
|
362
|
+
});
|
|
363
|
+
console.log(response.body.token);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Links
|
|
369
|
+
|
|
370
|
+
- Website: [https://alsocoder.com](https://alsocoder.com)
|
|
371
|
+
- GitHub: [https://github.com/alsocoders/sockress](https://github.com/alsocoders/sockress)
|
|
372
|
+
- Issues: [https://github.com/alsocoders/sockress/issues](https://github.com/alsocoders/sockress/issues)
|
|
373
|
+
|
|
374
|
+
PRs and feedback welcome!
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sockress",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Socket-first Express-compatible server with optional HTTP fallback, built-in WebSocket transport, uploader, and static helpers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|