owebjs 1.5.4-dev → 1.5.7-dev

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 CHANGED
@@ -1,270 +1,434 @@
1
- # Oweb
2
-
3
- A flexible and modern web framework built on top of Fastify, designed for creating scalable and maintainable web applications with file-based routing and hot module replacement.
4
-
5
- <p align="center">
6
- <img src="https://img.shields.io/npm/v/owebjs" alt="npm version">
7
- <img src="https://img.shields.io/npm/l/owebjs" alt="license">
8
- <img src="https://img.shields.io/npm/dt/owebjs" alt="downloads">
9
- </p>
10
-
11
- ## Features
12
-
13
- - **File-based Routing**: Automatically generate routes based on your file structure
14
- - **Hot Module Replacement (HMR)**: Update your routes without restarting the server
15
- - **Middleware Support**: Use hooks to add middleware functionality
16
- - **Error Handling**: Global and route-specific error handling
17
- - **TypeScript Support**: Built with TypeScript for better developer experience
18
- - **Plugin System**: Extend functionality with plugins
19
- - **uWebSockets.js Support**: Optional high-performance WebSocket server
20
-
21
- ## Installation
22
-
23
- ```bash
24
- npm install owebjs
25
- ```
26
-
27
- ## Quick Start
28
-
29
- ```javascript
30
- import Oweb from 'owebjs';
31
-
32
- // Create and setup the app
33
- const app = await new Oweb().setup();
34
-
35
- // Load routes from a directory
36
- await app.loadRoutes({
37
- directory: 'routes',
38
- hmr: {
39
- enabled: true, // Enable hot module replacement
40
- },
41
- });
42
-
43
- // Start the server
44
- await app.start({ port: 3000 });
45
- console.log('Server running at http://localhost:3000');
46
- ```
47
-
48
- ## Creating Routes
49
-
50
- Routes are automatically generated based on your file structure. Create a file in your routes directory:
51
-
52
- ```javascript
53
- // routes/hello.js
54
- import { Route } from 'owebjs';
55
-
56
- export default class extends Route {
57
- async handle(req, res) {
58
- res.send({ message: 'Hello, World!' });
59
- }
60
- }
61
- ```
62
-
63
- This will create a GET route at `/hello`.
64
-
65
- ### Dynamic Routes
66
-
67
- Use brackets to create dynamic route parameters:
68
-
69
- ```javascript
70
- // routes/users/[id].js
71
- import { Route } from 'owebjs';
72
-
73
- export default class extends Route {
74
- async handle(req, res) {
75
- res.send({ userId: req.params.id });
76
- }
77
- }
78
- ```
79
-
80
- This will create a GET route at `/users/:id`.
81
-
82
- ### Parameter Validation with Matchers
83
-
84
- Use matchers to validate dynamic route parameters:
85
-
86
- ```javascript
87
- // routes/users/[id=integer].js
88
- import { Route } from 'owebjs';
89
-
90
- export default class extends Route {
91
- async handle(req, res) {
92
- res.send({ userId: req.params.id });
93
- }
94
- }
95
- ```
96
-
97
- ```javascript
98
- // matchers/integer.js
99
- export default function (val) {
100
- return !isNaN(val);
101
- }
102
- ```
103
-
104
- Then configure Oweb to use your matchers directory:
105
-
106
- ```javascript
107
- await app.loadRoutes({
108
- directory: 'routes',
109
- matchersDirectory: 'matchers', // Directory containing custom matchers
110
- hmr: {
111
- enabled: true,
112
- matchersDirectory: 'matchers', // Optional: Enable HMR for matchers
113
- },
114
- });
115
- ```
116
-
117
- Now you can use your custom matchers in route filenames with the syntax `[paramName=matcherName]`.
118
-
119
- ### HTTP Methods
120
-
121
- Specify the HTTP method in the filename:
122
-
123
- ```javascript
124
- // routes/api/users.post.js
125
- import { Route } from 'owebjs';
126
-
127
- export default class extends Route {
128
- async handle(req, res) {
129
- // Create a new user
130
- const user = req.body;
131
- res.status(201).send({ id: 1, ...user });
132
- }
133
- }
134
- ```
135
-
136
- ## Middleware (Hooks)
137
-
138
- Create hooks to add middleware functionality:
139
-
140
- ```javascript
141
- // routes/_hooks.js
142
- import { Hook } from 'owebjs';
143
-
144
- export default class extends Hook {
145
- handle(req, res, done) {
146
- console.log(`${req.method} ${req.url}`);
147
- done(); // Continue to the next hook or route handler
148
- }
149
- }
150
- ```
151
-
152
- Hooks are applied to all routes in the current directory and its subdirectories.
153
-
154
- ## Error Handling
155
-
156
- ### Global Error Handler
157
-
158
- ```javascript
159
- app.setInternalErrorHandler((req, res, error) => {
160
- console.error(error);
161
- res.status(500).send({
162
- error: 'Internal Server Error',
163
- message: error.message,
164
- });
165
- });
166
- ```
167
-
168
- ### Route-specific Error Handler
169
-
170
- ```javascript
171
- import { Route } from 'owebjs';
172
-
173
- export default class extends Route {
174
- async handle(req, res) {
175
- throw new Error('Something went wrong');
176
- }
177
-
178
- handleError(req, res, error) {
179
- res.status(500).send({
180
- error: 'Route Error',
181
- message: error.message,
182
- });
183
- }
184
- }
185
- ```
186
-
187
- ## Plugins
188
-
189
- Oweb supports Fastify plugins and comes with some built-in plugins:
190
-
191
- ### Using Fastify Plugins
192
-
193
- ```javascript
194
- import Oweb from 'owebjs';
195
- import fastifyMultipart from '@fastify/multipart';
196
-
197
- const app = await new Oweb().setup();
198
-
199
- // Register Fastify plugin
200
- await app.register(fastifyMultipart, {
201
- limits: {
202
- fileSize: 10 * 1024 * 1024, // 10MB
203
- },
204
- });
205
- ```
206
-
207
- ### Using Built-in Plugins
208
-
209
- ```javascript
210
- import { Route } from 'owebjs';
211
- import { ChunkUpload } from 'owebjs/dist/plugins';
212
-
213
- export default class extends Route {
214
- async handle(req, res) {
215
- const file = await req.file();
216
- const buffer = await file.toBuffer();
217
-
218
- await ChunkUpload(
219
- {
220
- buffer,
221
- fileName: file.filename,
222
- currentChunk: +req.query.currentChunk,
223
- totalChunks: +req.query.totalChunks,
224
- },
225
- {
226
- path: './uploads',
227
- maxChunkSize: 5 * 1024 * 1024, // 5MB
228
- },
229
- );
230
-
231
- return res.status(204).send();
232
- }
233
- }
234
- ```
235
-
236
- ## Advanced Configuration
237
-
238
- ### uWebSockets.js Support
239
-
240
- ```javascript
241
- const app = await new Oweb({ uWebSocketsEnabled: true }).setup();
242
- ```
243
-
244
- ### Custom Route Options
245
-
246
- ```javascript
247
- import { Route } from 'owebjs';
248
-
249
- export default class extends Route {
250
- constructor() {
251
- super({
252
- schema: {
253
- body: {
254
- type: 'object',
255
- required: ['username', 'password'],
256
- properties: {
257
- username: { type: 'string' },
258
- password: { type: 'string' },
259
- },
260
- },
261
- },
262
- });
263
- }
264
-
265
- async handle(req, res) {
266
- // Body is validated according to the schema
267
- res.send({ success: true });
268
- }
269
- }
270
- ```
1
+ # Oweb
2
+
3
+ <p align="center">
4
+ <strong>A high-performance file-based web framework with seamless Fastify compatibility and native real-time capabilities</strong><br/>
5
+ Build APIs and real-time endpoints with a clean folder structure and fast iteration.
6
+ </p>
7
+
8
+ <p align="center">
9
+ <a href="https://www.npmjs.com/package/owebjs"><img src="https://img.shields.io/npm/v/owebjs" alt="npm version"></a>
10
+ <a href="https://www.npmjs.com/package/owebjs"><img src="https://img.shields.io/npm/dm/owebjs" alt="npm downloads"></a>
11
+ <a href="https://github.com/owebjs/oweb/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/owebjs" alt="license"></a>
12
+ </p>
13
+
14
+ ## What Is Oweb?
15
+
16
+ Oweb is a route-per-file framework built on Fastify, with an optional **uWebSockets runtime mode** for high-throughput workloads.
17
+
18
+ You keep Fastify compatibility and plugin ergonomics, while getting a cleaner architecture, built-in HMR, and first-class SSE/WebSocket support.
19
+
20
+ ## Feature Overview
21
+
22
+ - **Dual runtime**: Fastify-compatible default runtime + optional `uWebSockets.js` runtime
23
+ - File-based routing
24
+ - Dynamic params (`[id]`), method files (`.post`, `.put`, ...), and matcher params (`[id=integer]`)
25
+ - Hierarchical hooks via `_hooks.js` / `_hooks.ts`
26
+ - Route-level and global error handling
27
+ - Built-in HMR for routes and matchers
28
+ - SSE via iterable / async-iterable route handlers
29
+ - WebSocket routes with `WebSocketRoute`
30
+ - Fastify plugin ecosystem support
31
+ - TypeScript-first codebase and typings
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ npm install owebjs
37
+ ```
38
+
39
+ ## Runtime Modes (Fastify vs uWebSockets)
40
+
41
+ Use the same Oweb API in both modes.
42
+
43
+ ### Default mode (Fastify runtime)
44
+
45
+ ```js
46
+ import Oweb from 'owebjs';
47
+
48
+ const app = await new Oweb().setup();
49
+ ```
50
+
51
+ ### High-performance mode (uWebSockets runtime)
52
+
53
+ ```js
54
+ import Oweb from 'owebjs';
55
+
56
+ const app = await new Oweb({ uWebSocketsEnabled: true }).setup();
57
+ ```
58
+
59
+ When to prefer `uWebSocketsEnabled: true`:
60
+
61
+ - very high connection count
62
+ - aggressive WebSocket usage
63
+ - throughput-focused deployments
64
+
65
+ ## Benchmark
66
+
67
+ **Machine**: Windows 11, Ryzen 5 5500, 16GB RAM, 6C/12T, SSD (Base 3.60 GHz, boost ~4.1 GHz observed)
68
+
69
+ **Method**: `autocannon -c 100 -d 40 -p 10 localhost:3000` \* 2, taking the
70
+ second average
71
+
72
+ | Runtime | Version | Requests/sec |
73
+ | ------------------------- | --------- | -----------: |
74
+ | uWebSockets.js | 20.52.0 | 79,149 |
75
+ | **Oweb (uWS)** | 1.5.7-dev | 70,535 |
76
+ | 0http | 4.4.0 | 46,605 |
77
+ | Fastify | 4.23.2 | 46,238 |
78
+ | **Oweb (Fastify)** | 1.5.7-dev | 42,570 |
79
+ | Node.js http.createServer | 24.5.0 | 42,544 |
80
+ | Express | 5.2.1 | 24,913 |
81
+
82
+ This is a synthetic "Hello, Word!" benchmark that aims to evaluate the framework overhead.
83
+ The overhead that each framework has on your application depends on your application.
84
+ You should always benchmark if performance matters to you.
85
+
86
+ ## First App (2 Minutes)
87
+
88
+ Start with a minimal app, then we will add route conventions step by step.
89
+
90
+ ```js
91
+ import Oweb from 'owebjs';
92
+
93
+ const app = await new Oweb({ uWebSocketsEnabled: true }).setup();
94
+
95
+ await app.loadRoutes({
96
+ directory: 'routes',
97
+ hmr: {
98
+ enabled: true,
99
+ },
100
+ });
101
+
102
+ const { err, address } = await app.start({ port: 3000, host: '127.0.0.1' });
103
+ if (err) throw err;
104
+
105
+ console.log(`Server running at ${address}`);
106
+ ```
107
+
108
+ What this does:
109
+
110
+ - Creates an Oweb app instance (here in uWebSockets mode)
111
+ - Loads your route files from `routes/`
112
+ - Enables HMR for development
113
+ - Starts the HTTP server
114
+
115
+ ## How Oweb Maps Files to URLs
116
+
117
+ The file system is the routing table. Here is a representative structure:
118
+
119
+ ```txt
120
+ routes/
121
+ _hooks.js
122
+ hello.js
123
+ users/
124
+ [id].js
125
+ auth/
126
+ login.post.js
127
+ matcher/
128
+ [id=integer].js
129
+ events/
130
+ sse.js
131
+ ws/
132
+ echo.js
133
+
134
+ matchers/
135
+ integer.js
136
+ ```
137
+
138
+ Now let's go through each convention in isolation.
139
+
140
+ ## Routing Conventions
141
+
142
+ ### 1) Basic route
143
+
144
+ Use a normal file for a `GET` route.
145
+
146
+ `routes/hello.js` -> `GET /hello`
147
+
148
+ ```js
149
+ import { Route } from 'owebjs';
150
+
151
+ export default class HelloRoute extends Route {
152
+ handle() {
153
+ return { message: 'hello-world' };
154
+ }
155
+ }
156
+ ```
157
+
158
+ ### 2) Dynamic params
159
+
160
+ Put parameter names in brackets.
161
+
162
+ `routes/users/[id].js` -> `GET /users/:id`
163
+
164
+ ```js
165
+ import { Route } from 'owebjs';
166
+
167
+ export default class UserRoute extends Route {
168
+ handle(req) {
169
+ return { id: req.params.id };
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### 3) HTTP method suffix
175
+
176
+ Use filename suffixes when an endpoint is not `GET`.
177
+
178
+ `routes/auth/login.post.js` -> `POST /auth/login`
179
+
180
+ Supported suffixes: `.get`, `.post`, `.put`, `.patch`, `.delete`
181
+
182
+ ```js
183
+ import { Route } from 'owebjs';
184
+
185
+ export default class LoginPostRoute extends Route {
186
+ handle(req, res) {
187
+ return res.status(201).send({ method: req.method, body: req.body });
188
+ }
189
+ }
190
+ ```
191
+
192
+ ### 4) Matcher params
193
+
194
+ Matcher params add filename-level validation.
195
+
196
+ `routes/matcher/[id=integer].js` + `matchers/integer.js`
197
+
198
+ ```js
199
+ // matchers/integer.js
200
+ export default function integerMatcher(value) {
201
+ return /^-?\d+$/.test(String(value));
202
+ }
203
+ ```
204
+
205
+ Then register the matcher directory:
206
+
207
+ ```js
208
+ await app.loadRoutes({
209
+ directory: 'routes',
210
+ matchersDirectory: 'matchers',
211
+ hmr: {
212
+ enabled: true,
213
+ matchersDirectory: 'matchers',
214
+ },
215
+ });
216
+ ```
217
+
218
+ If the matcher returns `false`, the route is treated as not matched.
219
+
220
+ ## Hooks (Directory Middleware)
221
+
222
+ Hooks are defined with `_hooks.js` and run for that folder scope.
223
+
224
+ Example root hook:
225
+
226
+ ```js
227
+ import { Hook } from 'owebjs';
228
+
229
+ export default class RootHook extends Hook {
230
+ handle(req, _res, done) {
231
+ req.locals ??= {};
232
+ req.locals.trace = ['root'];
233
+ done();
234
+ }
235
+ }
236
+ ```
237
+
238
+ Key behavior:
239
+
240
+ - Hooks apply to the current directory and child directories
241
+ - Nested folders can add more hooks
242
+ - Hooks run before the route handler
243
+ - Scoped folders using parentheses (like `(admin)`) creates a hook boundary
244
+
245
+ ### Scoped hook groups
246
+
247
+ Oweb's route walker inspects parent hook paths and looks for the nearest folder whose name is wrapped in parentheses (for example `(api)` or `(admin)`).
248
+
249
+ When such a folder exists, hook resolution is cut at that scope boundary. In practice, hooks above that scoped folder are excluded.
250
+
251
+ Example:
252
+
253
+ ```txt
254
+ routes/
255
+ _hooks.js # global hook
256
+ (admin)/
257
+ _hooks.js # admin scope boundary hook
258
+ users/
259
+ _hooks.js
260
+ [id].js
261
+ ```
262
+
263
+ For `routes/(admin)/users/[id].js`, Oweb uses hooks inside that scoped chain and stops climbing above the `(admin)` boundary.
264
+
265
+ ## Error Handling
266
+
267
+ You can handle errors globally, or per route when you need custom behavior.
268
+
269
+ ### Global internal error handler
270
+
271
+ ```js
272
+ app.setInternalErrorHandler((req, res, error) => {
273
+ res.status(500).send({
274
+ source: 'global-handler',
275
+ message: error.message,
276
+ });
277
+ });
278
+ ```
279
+
280
+ ### Route-specific `handleError`
281
+
282
+ ```js
283
+ import { Route } from 'owebjs';
284
+
285
+ export default class RouteWithCustomError extends Route {
286
+ handle() {
287
+ throw new Error('route-specific-error');
288
+ }
289
+
290
+ handleError(_req, res, err) {
291
+ return res.status(409).send({
292
+ source: 'route-handleError',
293
+ message: err.message,
294
+ });
295
+ }
296
+ }
297
+ ```
298
+
299
+ Use route-level handling when you want endpoint-specific status codes or payload shape.
300
+
301
+ ## SSE (Server-Sent Events)
302
+
303
+ If a route returns an iterable or async iterable, Oweb streams it as SSE.
304
+
305
+ ```js
306
+ import { setTimeout as delay } from 'node:timers/promises';
307
+ import { Route } from 'owebjs';
308
+
309
+ export default class SseRoute extends Route {
310
+ async *handle(req, res) {
311
+ if (req.query?.deny === '1') {
312
+ return res.status(401).send({ code: 'error.unauthorized' });
313
+ }
314
+
315
+ yield 'event-1';
316
+ await delay(20);
317
+
318
+ yield { step: 2 };
319
+ await delay(20);
320
+
321
+ yield 3;
322
+ }
323
+ }
324
+ ```
325
+
326
+ This is useful for live feeds, progress updates, and long-running operations.
327
+
328
+ ## WebSockets
329
+
330
+ Create a file that exports a class extending `WebSocketRoute`.
331
+
332
+ `routes/ws/echo.js` -> `WS /ws/echo`
333
+
334
+ ```js
335
+ import { WebSocketRoute } from 'owebjs';
336
+
337
+ export default class EchoSocketRoute extends WebSocketRoute {
338
+ open(ws) {
339
+ ws.send('ready');
340
+ }
341
+
342
+ message(ws, message, isBinary) {
343
+ ws.send(message, isBinary);
344
+ }
345
+ }
346
+ ```
347
+
348
+ Works in both runtime modes:
349
+
350
+ - Fastify WebSocket adapter (default)
351
+ - Native `uWebSockets.js` mode
352
+
353
+ ## HMR (Hot Module Replacement)
354
+
355
+ Enable HMR while loading routes:
356
+
357
+ ```js
358
+ await app.loadRoutes({
359
+ directory: 'routes',
360
+ matchersDirectory: 'matchers',
361
+ hmr: {
362
+ enabled: true,
363
+ directory: 'routes',
364
+ matchersDirectory: 'matchers',
365
+ },
366
+ });
367
+ ```
368
+
369
+ Notes:
370
+
371
+ - HMR is disabled in `NODE_ENV=production`
372
+ - Route hook files (`_hooks.js` / `_hooks.ts`) are not hot-reloaded
373
+ - For hook changes, restart the server
374
+
375
+ ## Fastify Plugin Compatibility
376
+
377
+ Because Oweb sits on Fastify, you can register Fastify plugins directly.
378
+
379
+ ```js
380
+ import multipart from '@fastify/multipart';
381
+
382
+ await app.register(multipart, {
383
+ limits: {
384
+ fileSize: 10 * 1024 * 1024,
385
+ },
386
+ });
387
+ ```
388
+
389
+ ## Built-in Plugin: Chunk Upload
390
+
391
+ Oweb exposes `ChunkUpload` from `owebjs/plugins` for chunked upload workflows.
392
+
393
+ ```js
394
+ import { Route } from 'owebjs';
395
+ import { ChunkUpload, ChunkUploadStatus } from 'owebjs/plugins';
396
+
397
+ export default class ChunkUploadRoute extends Route {
398
+ async handle(req, res) {
399
+ const file = await req.file();
400
+ const buffer = await file.toBuffer();
401
+
402
+ const result = await ChunkUpload(
403
+ {
404
+ buffer,
405
+ fileName: file.filename,
406
+ currentChunk: Number(req.query.currentChunk),
407
+ totalChunks: Number(req.query.totalChunks),
408
+ },
409
+ {
410
+ path: './uploads',
411
+ maxChunkSize: 1024 * 1024,
412
+ maxFileSize: 10 * 1024 * 1024,
413
+ },
414
+ );
415
+
416
+ if (
417
+ result.status === ChunkUploadStatus.ChunkTooLarge ||
418
+ result.status === ChunkUploadStatus.FileTooLarge
419
+ ) {
420
+ return res.status(413).send(result);
421
+ }
422
+
423
+ return res.send(result);
424
+ }
425
+ }
426
+ ```
427
+
428
+ ## TypeScript
429
+
430
+ Oweb supports `.ts` route files and exports framework typings. You can keep the same file conventions and class model in TypeScript projects.
431
+
432
+ ## License
433
+
434
+ MIT