princejs 2.1.0 → 2.1.4

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.
@@ -1,518 +1,518 @@
1
- <div align="center">
2
-
3
- # 👑 PrinceJS
4
-
5
- **Ultra-clean, modern & minimal Bun web framework.**
6
- Built by a 13-year-old Nigerian developer. Among the top three in performance.
7
-
8
- [![npm version](https://img.shields.io/npm/v/princejs?style=flat-square)](https://www.npmjs.com/package/princejs)
9
- [![GitHub stars](https://img.shields.io/github/stars/MatthewTheCoder1218/princejs?style=flat-square)](https://github.com/MatthewTheCoder1218/princejs)
10
- [![npm downloads](https://img.shields.io/npm/dt/princejs?style=flat-square)](https://www.npmjs.com/package/princejs)
11
- [![license](https://img.shields.io/github/license/MatthewTheCoder1218/princejs?style=flat-square)](https://github.com/MatthewTheCoder1218/princejs/blob/main/LICENSE)
12
-
13
- [**Website**](https://princejs.vercel.app) · [**npm**](https://www.npmjs.com/package/princejs) · [**GitHub**](https://github.com/MatthewTheCoder1218/princejs) · [**Twitter**](https://twitter.com/princejs_bun)
14
-
15
- </div>
16
-
17
- ---
18
-
19
- ## ⚡ Performance
20
-
21
- Benchmarked with `oha -c 100 -z 30s` on Windows 10:
22
-
23
- | Framework | Req/s | Total |
24
- |-----------|------:|------:|
25
- | Elysia | 25,312 | 759k |
26
- | Hono | 22,124 | 664k |
27
- | **PrinceJS** | **21,748** | **653k** |
28
- | Express | 9,325 | 280k |
29
-
30
- > PrinceJS is **2.3× faster than Express** and sits comfortably in the top 3 — at just **4.4kB gzipped**.
31
-
32
- ---
33
-
34
- ## 🚀 Quick Start
35
-
36
- ```bash
37
- npm install princejs
38
- bun add princejs
39
- yarn add princejs
40
- ```
41
-
42
- ```ts
43
- import { prince } from "princejs";
44
- import { cors, logger } from "princejs/middleware";
45
-
46
- const app = prince();
47
-
48
- app.use(cors());
49
- app.use(logger());
50
-
51
- app.get("/", () => ({ message: "Hello PrinceJS!" }));
52
- app.get("/users/:id", (req) => ({ id: req.params.id }));
53
-
54
- app.listen(3000);
55
- ```
56
-
57
- ---
58
-
59
- ## 🧰 Features
60
-
61
- | Feature | Import |
62
- |---------|--------|
63
- | Routing | `princejs` |
64
- | Middleware (CORS, Logger, Rate Limit, Auth, JWT) | `princejs/middleware` |
65
- | Zod Validation | `princejs/middleware` |
66
- | **Cookies & IP Detection** | `princejs` |
67
- | File Uploads | `princejs/helpers` |
68
- | WebSockets | `princejs` |
69
- | Server-Sent Events | `princejs/helpers` |
70
- | Sessions | `princejs/middleware` |
71
- | Response Compression | `princejs/middleware` |
72
- | In-memory Cache | `princejs/helpers` |
73
- | Cron Scheduler | `princejs/scheduler` |
74
- | **OpenAPI + Scalar Docs** | `princejs` |
75
- | JSX / SSR | `princejs/jsx` |
76
- | SQLite Database | `princejs/db` |
77
- | Plugin System | `princejs` |
78
- | End-to-End Type Safety | `princejs/client` |
79
- | Deploy Adapters | `princejs/vercel` · `princejs/cloudflare` · `princejs/deno` · `princejs/node` |
80
- | Lifecycle Hooks | `princejs` |
81
-
82
- ---
83
-
84
- ## 🍪 Cookies & 🌐 IP Detection
85
-
86
- ### Reading Cookies
87
-
88
- Cookies are automatically parsed from the request:
89
-
90
- ```ts
91
- app.get("/profile", (req) => ({
92
- sessionId: req.cookies?.sessionId,
93
- theme: req.cookies?.theme,
94
- allCookies: req.cookies // Record<string, string>
95
- }));
96
- ```
97
-
98
- ### Setting Cookies
99
-
100
- Use the response builder to set cookies with full control:
101
-
102
- ```ts
103
- app.get("/login", (req) =>
104
- app.response()
105
- .status(200)
106
- .json({ ok: true })
107
- .cookie("sessionId", "abc123", {
108
- maxAge: 3600, // 1 hour
109
- path: "/",
110
- httpOnly: true, // not accessible from JS
111
- secure: true, // HTTPS only
112
- sameSite: "Strict" // CSRF protection
113
- })
114
- );
115
-
116
- // Chain multiple cookies
117
- app.response()
118
- .json({ ok: true })
119
- .cookie("session", "xyz")
120
- .cookie("theme", "dark")
121
- .cookie("lang", "en");
122
- ```
123
-
124
- ### Client IP Detection
125
-
126
- Automatically detect client IP from request headers:
127
-
128
- ```ts
129
- app.get("/api/data", (req) => ({
130
- clientIp: req.ip,
131
- data: [...]
132
- }));
133
- ```
134
-
135
- **Supported headers** (in priority order):
136
- - `X-Forwarded-For` — Load balancers, proxies (first IP in list)
137
- - `X-Real-IP` — Nginx, Apache reverse proxy
138
- - `CF-Connecting-IP` — Cloudflare
139
- - `X-Client-IP` — Other proxy services
140
- - Fallback — `127.0.0.1` (localhost)
141
-
142
- **Use cases:**
143
- - 🔒 Rate limiting per IP
144
- - 📊 Geolocation analytics
145
- - 🚨 IP-based access control
146
- - 👥 User tracking & fraud detection
147
-
148
- ```ts
149
- // Rate limit by IP
150
- app.use((req, next) => {
151
- const ip = req.ip;
152
- const count = ipTracker.getCount(ip) || 0;
153
- if (count > 100) return new Response("Too many requests", { status: 429 });
154
- ipTracker.increment(ip);
155
- return next();
156
- });
157
-
158
- // IP-based security
159
- app.post("/admin", (req) => {
160
- if (!ALLOWED_IPS.includes(req.ip!)) {
161
- return new Response("Forbidden", { status: 403 });
162
- }
163
- return { authorized: true };
164
- });
165
- ```
166
-
167
- ---
168
-
169
- ## 📖 OpenAPI + Scalar Docs ✨
170
-
171
- Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) UI — all from a single `app.openapi()` call. Routes, validation, and docs stay in sync automatically.
172
-
173
- ```ts
174
- import { prince } from "princejs";
175
- import { z } from "zod";
176
-
177
- const app = prince();
178
-
179
- const api = app.openapi({ title: "My API", version: "1.0.0" }, "/docs", { theme: "moon" });
180
-
181
- api.route("GET", "/users/:id", {
182
- summary: "Get user by ID",
183
- tags: ["users"],
184
- schema: {
185
- response: z.object({ id: z.string(), name: z.string() }),
186
- },
187
- }, (req) => ({ id: req.params!.id, name: "Alice" }));
188
-
189
- api.route("POST", "/users", {
190
- summary: "Create user",
191
- tags: ["users"],
192
- schema: {
193
- body: z.object({ name: z.string().min(2), email: z.string().email() }),
194
- response: z.object({ id: z.string(), name: z.string(), email: z.string() }),
195
- },
196
- }, (req) => ({ id: crypto.randomUUID(), ...req.parsedBody }));
197
-
198
- app.listen(3000);
199
- // → GET /docs Scalar UI
200
- // → GET /docs.json Raw OpenAPI JSON
201
- ```
202
-
203
- `api.route()` does three things at once:
204
-
205
- - ✅ Registers the route on PrinceJS
206
- - ✅ Auto-wires `validate(schema.body)` — no separate import needed
207
- - ✅ Writes the full OpenAPI spec entry
208
-
209
- | `schema` key | Runtime | Scalar Docs |
210
- |---|---|---|
211
- | `body` | ✅ Validates request | ✅ requestBody model |
212
- | `query` | — | ✅ Typed query params |
213
- | `response` | — | ✅ 200 response model |
214
-
215
- > Routes on `app.get()` / `app.post()` stay private — never appear in docs.
216
-
217
- **Themes:** `default` · `moon` · `purple` · `solarized` · `bluePlanet` · `deepSpace` · `saturn` · `kepler` · `mars`
218
-
219
- ---
220
-
221
- ## 🔌 Plugin System
222
-
223
- Share bundles of routes and middleware as reusable plugins:
224
-
225
- ```ts
226
- import { prince, type PrincePlugin } from "princejs";
227
-
228
- const usersPlugin: PrincePlugin<{ prefix?: string }> = (app, opts) => {
229
- const base = opts?.prefix ?? "";
230
-
231
- app.use((req, next) => {
232
- (req as any).fromPlugin = true;
233
- return next();
234
- });
235
-
236
- app.get(`${base}/users`, (req) => ({
237
- ok: true,
238
- fromPlugin: (req as any).fromPlugin,
239
- }));
240
- };
241
-
242
- const app = prince();
243
- app.plugin(usersPlugin, { prefix: "/api" });
244
- ```
245
-
246
- ---
247
-
248
- ## 🎣 Lifecycle Hooks
249
-
250
- React to key moments in request processing with lifecycle hooks:
251
-
252
- ```ts
253
- import { prince } from "princejs";
254
-
255
- const app = prince();
256
-
257
- // Called for every incoming request
258
- app.onRequest((req) => {
259
- console.log(`📥 Request received: ${req.method} ${req.url}`);
260
- });
261
-
262
- // Called before handler execution
263
- app.onBeforeHandle((req, path, method) => {
264
- console.log(`🔍 About to handle: ${method} ${path}`);
265
- (req as any).startTime = Date.now();
266
- });
267
-
268
- // Called after successful handler execution
269
- app.onAfterHandle((req, res, path, method) => {
270
- const duration = Date.now() - (req as any).startTime;
271
- console.log(`✅ Response: ${method} ${path} ${res.status} (${duration}ms)`);
272
- });
273
-
274
- // Called when handler throws an error
275
- app.onError((err, req, path, method) => {
276
- console.error(`❌ Error in ${method} ${path}:`, err.message);
277
- // Send alert, log to monitoring service, etc.
278
- });
279
-
280
- app.get("/users", () => ({ users: [] }));
281
- ```
282
-
283
- **Hook execution order:**
284
- 1. `onRequest` — early for request-wide setup
285
- 2. `onBeforeHandle` — just before route handler runs
286
- 3. Handler executes
287
- 4. `onAfterHandle` — after success (on error, skipped)
288
- 5. `onError` — only if handler throws (skips onAfterHandle)
289
-
290
- **Use cases:**
291
- - 📊 Metrics & observability
292
- - 🔍 Request inspection & debugging
293
- - ⏱️ Timing & performance monitoring
294
- - 🚨 Error tracking & alerting
295
- - 🔐 Security audits & compliance logging
296
-
297
- ---
298
-
299
- ## 🔒 End-to-End Type Safety
300
-
301
- Define a contract once — your client gets full TypeScript autocompletion automatically:
302
-
303
- ```ts
304
- type ApiContract = {
305
- "GET /users/:id": {
306
- params: { id: string };
307
- response: { id: string; name: string };
308
- };
309
- "POST /users": {
310
- body: { name: string };
311
- response: { id: string; ok: boolean };
312
- };
313
- };
314
-
315
- import { createClient } from "princejs/client";
316
-
317
- const client = createClient<ApiContract>("http://localhost:3000");
318
-
319
- const user = await client.get("/users/:id", { params: { id: "42" } });
320
- console.log(user.name); // typed as string ✅
321
- ```
322
-
323
- ---
324
-
325
- ## 🌍 Deploy Adapters
326
-
327
- **Vercel Edge** — `api/[[...route]].ts`
328
- ```ts
329
- import { toVercel } from "princejs/vercel";
330
- export default toVercel(app);
331
- ```
332
-
333
- **Cloudflare Workers** — `src/index.ts`
334
- ```ts
335
- import { toWorkers } from "princejs/cloudflare";
336
- export default toWorkers(app);
337
- ```
338
-
339
- **Deno Deploy** — `main.ts`
340
- ```ts
341
- import { toDeno } from "princejs/deno";
342
- Deno.serve(toDeno(app));
343
- ```
344
-
345
- **Node Adapter** - `server.ts`
346
- ```ts
347
- import { createServer } from "http";
348
- import { toNode, toExpress } from "princejs/node";
349
-
350
- const app = prince();
351
- app.get("/", () => ({ message: "Hello from Node!" }));
352
-
353
- // Native Node.js http
354
- const server = createServer(toNode(app));
355
- server.listen(3000);
356
-
357
- // Or with Express
358
- import express from "express";
359
- const expressApp = express();
360
- expressApp.all("*", toExpress(app));
361
- expressApp.listen(3000);
362
- ```
363
-
364
- ---
365
-
366
- ## 🎯 Full Example
367
-
368
- ```ts
369
- import { prince } from "princejs";
370
- import { cors, logger, rateLimit, auth, apiKey, jwt, session, compress, serve } from "princejs/middleware";
371
- import { validate } from "princejs/validation";
372
- import { cache, upload, sse } from "princejs/helpers";
373
- import { cron } from "princejs/scheduler";
374
- import { Html, Head, Body, H1, P, render } from "princejs/jsx";
375
- import { db } from "princejs/db";
376
- import { z } from "zod";
377
-
378
- const app = prince(true);
379
-
380
- // ==========================================
381
- // LIFECYCLE HOOKS - Timing & Observability
382
- // ==========================================
383
- app.onRequest((req) => {
384
- (req as any).startTime = Date.now();
385
- });
386
-
387
- app.onBeforeHandle((req, path, method) => {
388
- console.log(`🔍 Handling: ${method} ${path}`);
389
- });
390
-
391
- app.onAfterHandle((req, res, path, method) => {
392
- const duration = Date.now() - (req as any).startTime;
393
- console.log(`✅ ${method} ${path} → ${res.status} (${duration}ms)`);
394
- });
395
-
396
- app.onError((err, req, path, method) => {
397
- console.error(`❌ ${method} ${path} failed:`, err.message);
398
- });
399
-
400
- // ==========================================
401
- // GLOBAL MIDDLEWARE
402
- // ==========================================
403
- app.use(cors());
404
- app.use(logger());
405
- app.use(rateLimit({ max: 100, window: 60 }));
406
- app.use(serve({ root: "./public" }));
407
- app.use(jwt(key));
408
- app.use(session({ secret: "key" }));
409
- app.use(compress());
410
-
411
- // ==========================================
412
- // ROUTES
413
- // ==========================================
414
-
415
- // JSX
416
- const Page = () => Html(Head("Test Page"), Body(H1("Hello World"), P("This is a test")));
417
- app.get("/jsx", () => render(Page()));
418
-
419
- // Cookies & IP Detection
420
- app.post("/login", (req) =>
421
- app.response()
422
- .json({ ok: true, ip: req.ip })
423
- .cookie("sessionId", "user_123", {
424
- httpOnly: true,
425
- secure: true,
426
- sameSite: "Strict",
427
- maxAge: 86400 // 24 hours
428
- })
429
- );
430
-
431
- app.get("/profile", (req) => ({
432
- sessionId: req.cookies?.sessionId,
433
- clientIp: req.ip,
434
- }));
435
-
436
- // Database
437
- const users = db.sqlite("./db.sqlite", "CREATE TABLE users...");
438
- app.get("/users", () => users.query("SELECT * FROM users"));
439
-
440
- // WebSockets
441
- app.ws("/chat", {
442
- open: (ws) => ws.send("Welcome!"),
443
- message: (ws, msg) => ws.send(`Echo: ${msg}`),
444
- });
445
-
446
- // Auth
447
- app.get("/protected", auth(), (req) => ({ user: req.user }));
448
- app.get("/api", apiKey({ keys: ["key_123"] }), (req) => ({ ok: true }));
449
-
450
- // Helpers
451
- app.get("/data", cache(60)(() => ({ time: Date.now() })));
452
- app.post("/upload", upload(), (req) => ({ files: Object.keys(req.files || {}) }));
453
- app.get("/events", sse(), (req) => {
454
- setInterval(() => req.sseSend({ time: Date.now() }), 1000);
455
- });
456
-
457
- // ==========================================
458
- // CRON JOBS
459
- // ==========================================
460
- cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
461
-
462
- // ==========================================
463
- // OPENAPI + SCALAR DOCS
464
- // ==========================================
465
- const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");
466
- api.route("GET", "/items", {
467
- summary: "List items",
468
- tags: ["items"],
469
- schema: {
470
- query: z.object({ q: z.string().optional() }),
471
- response: z.array(z.object({ id: z.string(), name: z.string() })),
472
- },
473
- }, () => [{ id: "1", name: "Widget" }]);
474
-
475
- app.listen(3000);
476
- ```
477
-
478
- ---
479
-
480
- ## 📦 Installation
481
-
482
- ```bash
483
- bun add princejs
484
- # or
485
- npm install princejs
486
- # or
487
- yarn add princejs
488
- ```
489
-
490
- ---
491
-
492
- ## 🤝 Contributing
493
-
494
- ```bash
495
- git clone https://github.com/MatthewTheCoder1218/princejs
496
- cd princejs
497
- bun install
498
- bun test
499
- ```
500
-
501
- ---
502
-
503
- ## 🔗 Links
504
-
505
- - 🌐 Website: [princejs.vercel.app](https://princejs.vercel.app)
506
- - 📦 npm: [npmjs.com/package/princejs](https://www.npmjs.com/package/princejs)
507
- - 💻 GitHub: [github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
508
- - 🐦 Twitter: [@princejs_bun](https://twitter.com/princejs_bun)
509
-
510
- ---
511
-
512
- <div align="center">
513
-
514
- **PrinceJS: Small in size. Giant in capability. 👑**
515
-
516
- *Built with ❤️ in Nigeria*
517
-
1
+ <div align="center">
2
+
3
+ # 👑 PrinceJS
4
+
5
+ **Ultra-clean, modern & minimal Bun web framework.**
6
+ Built by a 13-year-old Nigerian developer. Among the top three in performance.
7
+
8
+ [![npm version](https://img.shields.io/npm/v/princejs?style=flat-square)](https://www.npmjs.com/package/princejs)
9
+ [![GitHub stars](https://img.shields.io/github/stars/MatthewTheCoder1218/princejs?style=flat-square)](https://github.com/MatthewTheCoder1218/princejs)
10
+ [![npm downloads](https://img.shields.io/npm/dt/princejs?style=flat-square)](https://www.npmjs.com/package/princejs)
11
+ [![license](https://img.shields.io/github/license/MatthewTheCoder1218/princejs?style=flat-square)](https://github.com/MatthewTheCoder1218/princejs/blob/main/LICENSE)
12
+
13
+ [**Website**](https://princejs.vercel.app) · [**npm**](https://www.npmjs.com/package/princejs) · [**GitHub**](https://github.com/MatthewTheCoder1218/princejs) · [**Twitter**](https://twitter.com/princejs_bun)
14
+
15
+ </div>
16
+
17
+ ---
18
+
19
+ ## ⚡ Performance
20
+
21
+ Benchmarked with `oha -c 100 -z 30s` on Windows 10:
22
+
23
+ | Framework | Req/s | Total |
24
+ |-----------|------:|------:|
25
+ | Elysia | 25,312 | 759k |
26
+ | Hono | 22,124 | 664k |
27
+ | **PrinceJS** | **21,748** | **653k** |
28
+ | Express | 9,325 | 280k |
29
+
30
+ > PrinceJS is **2.3× faster than Express** and sits comfortably in the top 3 — at just **4.4kB gzipped**.
31
+
32
+ ---
33
+
34
+ ## 🚀 Quick Start
35
+
36
+ ```bash
37
+ npm install princejs
38
+ bun add princejs
39
+ yarn add princejs
40
+ ```
41
+
42
+ ```ts
43
+ import { prince } from "princejs";
44
+ import { cors, logger } from "princejs/middleware";
45
+
46
+ const app = prince();
47
+
48
+ app.use(cors());
49
+ app.use(logger());
50
+
51
+ app.get("/", () => ({ message: "Hello PrinceJS!" }));
52
+ app.get("/users/:id", (req) => ({ id: req.params.id }));
53
+
54
+ app.listen(3000);
55
+ ```
56
+
57
+ ---
58
+
59
+ ## 🧰 Features
60
+
61
+ | Feature | Import |
62
+ |---------|--------|
63
+ | Routing | `princejs` |
64
+ | Middleware (CORS, Logger, Rate Limit, Auth, JWT) | `princejs/middleware` |
65
+ | Zod Validation | `princejs/middleware` |
66
+ | **Cookies & IP Detection** | `princejs` |
67
+ | File Uploads | `princejs/helpers` |
68
+ | WebSockets | `princejs` |
69
+ | Server-Sent Events | `princejs/helpers` |
70
+ | Sessions | `princejs/middleware` |
71
+ | Response Compression | `princejs/middleware` |
72
+ | In-memory Cache | `princejs/helpers` |
73
+ | Cron Scheduler | `princejs/scheduler` |
74
+ | **OpenAPI + Scalar Docs** | `princejs` |
75
+ | JSX / SSR | `princejs/jsx` |
76
+ | SQLite Database | `princejs/db` |
77
+ | Plugin System | `princejs` |
78
+ | End-to-End Type Safety | `princejs/client` |
79
+ | Deploy Adapters | `princejs/vercel` · `princejs/cloudflare` · `princejs/deno` · `princejs/node` |
80
+ | Lifecycle Hooks | `princejs` |
81
+
82
+ ---
83
+
84
+ ## 🍪 Cookies & 🌐 IP Detection
85
+
86
+ ### Reading Cookies
87
+
88
+ Cookies are automatically parsed from the request:
89
+
90
+ ```ts
91
+ app.get("/profile", (req) => ({
92
+ sessionId: req.cookies?.sessionId,
93
+ theme: req.cookies?.theme,
94
+ allCookies: req.cookies // Record<string, string>
95
+ }));
96
+ ```
97
+
98
+ ### Setting Cookies
99
+
100
+ Use the response builder to set cookies with full control:
101
+
102
+ ```ts
103
+ app.get("/login", (req) =>
104
+ app.response()
105
+ .status(200)
106
+ .json({ ok: true })
107
+ .cookie("sessionId", "abc123", {
108
+ maxAge: 3600, // 1 hour
109
+ path: "/",
110
+ httpOnly: true, // not accessible from JS
111
+ secure: true, // HTTPS only
112
+ sameSite: "Strict" // CSRF protection
113
+ })
114
+ );
115
+
116
+ // Chain multiple cookies
117
+ app.response()
118
+ .json({ ok: true })
119
+ .cookie("session", "xyz")
120
+ .cookie("theme", "dark")
121
+ .cookie("lang", "en");
122
+ ```
123
+
124
+ ### Client IP Detection
125
+
126
+ Automatically detect client IP from request headers:
127
+
128
+ ```ts
129
+ app.get("/api/data", (req) => ({
130
+ clientIp: req.ip,
131
+ data: [...]
132
+ }));
133
+ ```
134
+
135
+ **Supported headers** (in priority order):
136
+ - `X-Forwarded-For` — Load balancers, proxies (first IP in list)
137
+ - `X-Real-IP` — Nginx, Apache reverse proxy
138
+ - `CF-Connecting-IP` — Cloudflare
139
+ - `X-Client-IP` — Other proxy services
140
+ - Fallback — `127.0.0.1` (localhost)
141
+
142
+ **Use cases:**
143
+ - 🔒 Rate limiting per IP
144
+ - 📊 Geolocation analytics
145
+ - 🚨 IP-based access control
146
+ - 👥 User tracking & fraud detection
147
+
148
+ ```ts
149
+ // Rate limit by IP
150
+ app.use((req, next) => {
151
+ const ip = req.ip;
152
+ const count = ipTracker.getCount(ip) || 0;
153
+ if (count > 100) return new Response("Too many requests", { status: 429 });
154
+ ipTracker.increment(ip);
155
+ return next();
156
+ });
157
+
158
+ // IP-based security
159
+ app.post("/admin", (req) => {
160
+ if (!ALLOWED_IPS.includes(req.ip!)) {
161
+ return new Response("Forbidden", { status: 403 });
162
+ }
163
+ return { authorized: true };
164
+ });
165
+ ```
166
+
167
+ ---
168
+
169
+ ## 📖 OpenAPI + Scalar Docs ✨
170
+
171
+ Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) UI — all from a single `app.openapi()` call. Routes, validation, and docs stay in sync automatically.
172
+
173
+ ```ts
174
+ import { prince } from "princejs";
175
+ import { z } from "zod";
176
+
177
+ const app = prince();
178
+
179
+ const api = app.openapi({ title: "My API", version: "1.0.0" }, "/docs", { theme: "moon" });
180
+
181
+ api.route("GET", "/users/:id", {
182
+ summary: "Get user by ID",
183
+ tags: ["users"],
184
+ schema: {
185
+ response: z.object({ id: z.string(), name: z.string() }),
186
+ },
187
+ }, (req) => ({ id: req.params!.id, name: "Alice" }));
188
+
189
+ api.route("POST", "/users", {
190
+ summary: "Create user",
191
+ tags: ["users"],
192
+ schema: {
193
+ body: z.object({ name: z.string().min(2), email: z.string().email() }),
194
+ response: z.object({ id: z.string(), name: z.string(), email: z.string() }),
195
+ },
196
+ }, (req) => ({ id: crypto.randomUUID(), ...req.parsedBody }));
197
+
198
+ app.listen(3000);
199
+ // → GET /docs Scalar UI
200
+ // → GET /docs.json Raw OpenAPI JSON
201
+ ```
202
+
203
+ `api.route()` does three things at once:
204
+
205
+ - ✅ Registers the route on PrinceJS
206
+ - ✅ Auto-wires `validate(schema.body)` — no separate import needed
207
+ - ✅ Writes the full OpenAPI spec entry
208
+
209
+ | `schema` key | Runtime | Scalar Docs |
210
+ |---|---|---|
211
+ | `body` | ✅ Validates request | ✅ requestBody model |
212
+ | `query` | — | ✅ Typed query params |
213
+ | `response` | — | ✅ 200 response model |
214
+
215
+ > Routes on `app.get()` / `app.post()` stay private — never appear in docs.
216
+
217
+ **Themes:** `default` · `moon` · `purple` · `solarized` · `bluePlanet` · `deepSpace` · `saturn` · `kepler` · `mars`
218
+
219
+ ---
220
+
221
+ ## 🔌 Plugin System
222
+
223
+ Share bundles of routes and middleware as reusable plugins:
224
+
225
+ ```ts
226
+ import { prince, type PrincePlugin } from "princejs";
227
+
228
+ const usersPlugin: PrincePlugin<{ prefix?: string }> = (app, opts) => {
229
+ const base = opts?.prefix ?? "";
230
+
231
+ app.use((req, next) => {
232
+ (req as any).fromPlugin = true;
233
+ return next();
234
+ });
235
+
236
+ app.get(`${base}/users`, (req) => ({
237
+ ok: true,
238
+ fromPlugin: (req as any).fromPlugin,
239
+ }));
240
+ };
241
+
242
+ const app = prince();
243
+ app.plugin(usersPlugin, { prefix: "/api" });
244
+ ```
245
+
246
+ ---
247
+
248
+ ## 🎣 Lifecycle Hooks
249
+
250
+ React to key moments in request processing with lifecycle hooks:
251
+
252
+ ```ts
253
+ import { prince } from "princejs";
254
+
255
+ const app = prince();
256
+
257
+ // Called for every incoming request
258
+ app.onRequest((req) => {
259
+ console.log(`📥 Request received: ${req.method} ${req.url}`);
260
+ });
261
+
262
+ // Called before handler execution
263
+ app.onBeforeHandle((req, path, method) => {
264
+ console.log(`🔍 About to handle: ${method} ${path}`);
265
+ (req as any).startTime = Date.now();
266
+ });
267
+
268
+ // Called after successful handler execution
269
+ app.onAfterHandle((req, res, path, method) => {
270
+ const duration = Date.now() - (req as any).startTime;
271
+ console.log(`✅ Response: ${method} ${path} ${res.status} (${duration}ms)`);
272
+ });
273
+
274
+ // Called when handler throws an error
275
+ app.onError((err, req, path, method) => {
276
+ console.error(`❌ Error in ${method} ${path}:`, err.message);
277
+ // Send alert, log to monitoring service, etc.
278
+ });
279
+
280
+ app.get("/users", () => ({ users: [] }));
281
+ ```
282
+
283
+ **Hook execution order:**
284
+ 1. `onRequest` — early for request-wide setup
285
+ 2. `onBeforeHandle` — just before route handler runs
286
+ 3. Handler executes
287
+ 4. `onAfterHandle` — after success (on error, skipped)
288
+ 5. `onError` — only if handler throws (skips onAfterHandle)
289
+
290
+ **Use cases:**
291
+ - 📊 Metrics & observability
292
+ - 🔍 Request inspection & debugging
293
+ - ⏱️ Timing & performance monitoring
294
+ - 🚨 Error tracking & alerting
295
+ - 🔐 Security audits & compliance logging
296
+
297
+ ---
298
+
299
+ ## 🔒 End-to-End Type Safety
300
+
301
+ Define a contract once — your client gets full TypeScript autocompletion automatically:
302
+
303
+ ```ts
304
+ type ApiContract = {
305
+ "GET /users/:id": {
306
+ params: { id: string };
307
+ response: { id: string; name: string };
308
+ };
309
+ "POST /users": {
310
+ body: { name: string };
311
+ response: { id: string; ok: boolean };
312
+ };
313
+ };
314
+
315
+ import { createClient } from "princejs/client";
316
+
317
+ const client = createClient<ApiContract>("http://localhost:3000");
318
+
319
+ const user = await client.get("/users/:id", { params: { id: "42" } });
320
+ console.log(user.name); // typed as string ✅
321
+ ```
322
+
323
+ ---
324
+
325
+ ## 🌍 Deploy Adapters
326
+
327
+ **Vercel Edge** — `api/[[...route]].ts`
328
+ ```ts
329
+ import { toVercel } from "princejs/vercel";
330
+ export default toVercel(app);
331
+ ```
332
+
333
+ **Cloudflare Workers** — `src/index.ts`
334
+ ```ts
335
+ import { toWorkers } from "princejs/cloudflare";
336
+ export default toWorkers(app);
337
+ ```
338
+
339
+ **Deno Deploy** — `main.ts`
340
+ ```ts
341
+ import { toDeno } from "princejs/deno";
342
+ Deno.serve(toDeno(app));
343
+ ```
344
+
345
+ **Node Adapter** - `server.ts`
346
+ ```ts
347
+ import { createServer } from "http";
348
+ import { toNode, toExpress } from "princejs/node";
349
+
350
+ const app = prince();
351
+ app.get("/", () => ({ message: "Hello from Node!" }));
352
+
353
+ // Native Node.js http
354
+ const server = createServer(toNode(app));
355
+ server.listen(3000);
356
+
357
+ // Or with Express
358
+ import express from "express";
359
+ const expressApp = express();
360
+ expressApp.all("*", toExpress(app));
361
+ expressApp.listen(3000);
362
+ ```
363
+
364
+ ---
365
+
366
+ ## 🎯 Full Example
367
+
368
+ ```ts
369
+ import { prince } from "princejs";
370
+ import { cors, logger, rateLimit, auth, apiKey, jwt, session, compress, serve } from "princejs/middleware";
371
+ import { validate } from "princejs/validation";
372
+ import { cache, upload, sse } from "princejs/helpers";
373
+ import { cron } from "princejs/scheduler";
374
+ import { Html, Head, Body, H1, P, render } from "princejs/jsx";
375
+ import { db } from "princejs/db";
376
+ import { z } from "zod";
377
+
378
+ const app = prince(true);
379
+
380
+ // ==========================================
381
+ // LIFECYCLE HOOKS - Timing & Observability
382
+ // ==========================================
383
+ app.onRequest((req) => {
384
+ (req as any).startTime = Date.now();
385
+ });
386
+
387
+ app.onBeforeHandle((req, path, method) => {
388
+ console.log(`🔍 Handling: ${method} ${path}`);
389
+ });
390
+
391
+ app.onAfterHandle((req, res, path, method) => {
392
+ const duration = Date.now() - (req as any).startTime;
393
+ console.log(`✅ ${method} ${path} → ${res.status} (${duration}ms)`);
394
+ });
395
+
396
+ app.onError((err, req, path, method) => {
397
+ console.error(`❌ ${method} ${path} failed:`, err.message);
398
+ });
399
+
400
+ // ==========================================
401
+ // GLOBAL MIDDLEWARE
402
+ // ==========================================
403
+ app.use(cors());
404
+ app.use(logger());
405
+ app.use(rateLimit({ max: 100, window: 60 }));
406
+ app.use(serve({ root: "./public" }));
407
+ app.use(jwt(key));
408
+ app.use(session({ secret: "key" }));
409
+ app.use(compress());
410
+
411
+ // ==========================================
412
+ // ROUTES
413
+ // ==========================================
414
+
415
+ // JSX
416
+ const Page = () => Html(Head("Test Page"), Body(H1("Hello World"), P("This is a test")));
417
+ app.get("/jsx", () => render(Page()));
418
+
419
+ // Cookies & IP Detection
420
+ app.post("/login", (req) =>
421
+ app.response()
422
+ .json({ ok: true, ip: req.ip })
423
+ .cookie("sessionId", "user_123", {
424
+ httpOnly: true,
425
+ secure: true,
426
+ sameSite: "Strict",
427
+ maxAge: 86400 // 24 hours
428
+ })
429
+ );
430
+
431
+ app.get("/profile", (req) => ({
432
+ sessionId: req.cookies?.sessionId,
433
+ clientIp: req.ip,
434
+ }));
435
+
436
+ // Database
437
+ const users = db.sqlite("./db.sqlite", "CREATE TABLE users...");
438
+ app.get("/users", () => users.query("SELECT * FROM users"));
439
+
440
+ // WebSockets
441
+ app.ws("/chat", {
442
+ open: (ws) => ws.send("Welcome!"),
443
+ message: (ws, msg) => ws.send(`Echo: ${msg}`),
444
+ });
445
+
446
+ // Auth
447
+ app.get("/protected", auth(), (req) => ({ user: req.user }));
448
+ app.get("/api", apiKey({ keys: ["key_123"] }), (req) => ({ ok: true }));
449
+
450
+ // Helpers
451
+ app.get("/data", cache(60)(() => ({ time: Date.now() })));
452
+ app.post("/upload", upload(), (req) => ({ files: Object.keys(req.files || {}) }));
453
+ app.get("/events", sse(), (req) => {
454
+ setInterval(() => req.sseSend({ time: Date.now() }), 1000);
455
+ });
456
+
457
+ // ==========================================
458
+ // CRON JOBS
459
+ // ==========================================
460
+ cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
461
+
462
+ // ==========================================
463
+ // OPENAPI + SCALAR DOCS
464
+ // ==========================================
465
+ const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");
466
+ api.route("GET", "/items", {
467
+ summary: "List items",
468
+ tags: ["items"],
469
+ schema: {
470
+ query: z.object({ q: z.string().optional() }),
471
+ response: z.array(z.object({ id: z.string(), name: z.string() })),
472
+ },
473
+ }, () => [{ id: "1", name: "Widget" }]);
474
+
475
+ app.listen(3000);
476
+ ```
477
+
478
+ ---
479
+
480
+ ## 📦 Installation
481
+
482
+ ```bash
483
+ bun add princejs
484
+ # or
485
+ npm install princejs
486
+ # or
487
+ yarn add princejs
488
+ ```
489
+
490
+ ---
491
+
492
+ ## 🤝 Contributing
493
+
494
+ ```bash
495
+ git clone https://github.com/MatthewTheCoder1218/princejs
496
+ cd princejs
497
+ bun install
498
+ bun test
499
+ ```
500
+
501
+ ---
502
+
503
+ ## 🔗 Links
504
+
505
+ - 🌐 Website: [princejs.vercel.app](https://princejs.vercel.app)
506
+ - 📦 npm: [npmjs.com/package/princejs](https://www.npmjs.com/package/princejs)
507
+ - 💻 GitHub: [github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
508
+ - 🐦 Twitter: [@princejs_bun](https://twitter.com/princejs_bun)
509
+
510
+ ---
511
+
512
+ <div align="center">
513
+
514
+ **PrinceJS: Small in size. Giant in capability. 👑**
515
+
516
+ *Built with ❤️ in Nigeria*
517
+
518
518
  </div>