princejs 1.9.4 โ 1.9.6
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 +316 -0
- package/package.json +1 -1
- package/Readme.md +0 -398
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="./src/images/og.png" alt="PrinceJS"/>
|
|
4
|
+
|
|
5
|
+
# ๐ PrinceJS
|
|
6
|
+
|
|
7
|
+
**Ultra-clean, modern & minimal Bun web framework.**
|
|
8
|
+
Built by a 13-year-old Nigerian developer. Among the top three in performance.
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/princejs)
|
|
11
|
+
[](https://github.com/MatthewTheCoder1218/princejs)
|
|
12
|
+
[](https://www.npmjs.com/package/princejs)
|
|
13
|
+
[](https://github.com/MatthewTheCoder1218/princejs/blob/main/LICENSE)
|
|
14
|
+
|
|
15
|
+
[**Website**](https://princejs.vercel.app) ยท [**npm**](https://www.npmjs.com/package/princejs) ยท [**GitHub**](https://github.com/MatthewTheCoder1218/princejs) ยท [**Twitter**](https://twitter.com/princejs_bun)
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## โก Performance
|
|
22
|
+
|
|
23
|
+
Benchmarked with `oha -c 100 -z 30s` on Windows 10:
|
|
24
|
+
|
|
25
|
+
| Framework | Req/s | Total |
|
|
26
|
+
|-----------|------:|------:|
|
|
27
|
+
| Elysia | 25,312 | 759k |
|
|
28
|
+
| Hono | 22,124 | 664k |
|
|
29
|
+
| **PrinceJS** | **21,748** | **653k** |
|
|
30
|
+
| Express | 9,325 | 280k |
|
|
31
|
+
|
|
32
|
+
> PrinceJS is **2.3ร faster than Express** and sits comfortably in the top 3 โ at just **4.4kB gzipped**.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ๐ Quick Start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bun create princejs my-app
|
|
40
|
+
cd my-app
|
|
41
|
+
bun dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { prince } from "princejs";
|
|
46
|
+
import { cors, logger } from "princejs/middleware";
|
|
47
|
+
|
|
48
|
+
const app = prince();
|
|
49
|
+
|
|
50
|
+
app.use(cors());
|
|
51
|
+
app.use(logger());
|
|
52
|
+
|
|
53
|
+
app.get("/", () => ({ message: "Hello PrinceJS!" }));
|
|
54
|
+
app.get("/users/:id", (req) => ({ id: req.params.id }));
|
|
55
|
+
|
|
56
|
+
app.listen(3000);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## ๐งฐ Features
|
|
62
|
+
|
|
63
|
+
| Feature | Import |
|
|
64
|
+
|---------|--------|
|
|
65
|
+
| Routing | `princejs` |
|
|
66
|
+
| Middleware (CORS, Logger, Rate Limit, Auth, JWT) | `princejs/middleware` |
|
|
67
|
+
| Zod Validation | `princejs/middleware` |
|
|
68
|
+
| File Uploads | `princejs/helpers` |
|
|
69
|
+
| WebSockets | `princejs` |
|
|
70
|
+
| Server-Sent Events | `princejs/helpers` |
|
|
71
|
+
| Sessions | `princejs/middleware` |
|
|
72
|
+
| Response Compression | `princejs/middleware` |
|
|
73
|
+
| In-memory Cache | `princejs/helpers` |
|
|
74
|
+
| Cron Scheduler | `princejs/scheduler` |
|
|
75
|
+
| **OpenAPI + Scalar Docs** | `princejs` |
|
|
76
|
+
| JSX / SSR | `princejs/jsx` |
|
|
77
|
+
| SQLite Database | `princejs/db` |
|
|
78
|
+
| Plugin System | `princejs` |
|
|
79
|
+
| End-to-End Type Safety | `princejs/client` |
|
|
80
|
+
| Deploy Adapters | `princejs/vercel` ยท `princejs/cloudflare` ยท `princejs/deno` |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## ๐ OpenAPI + Scalar Docs โจ
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { prince } from "princejs";
|
|
90
|
+
import { z } from "zod";
|
|
91
|
+
|
|
92
|
+
const app = prince();
|
|
93
|
+
|
|
94
|
+
const api = app.openapi({ title: "My API", version: "1.0.0" }, "/docs", { theme: "moon" });
|
|
95
|
+
|
|
96
|
+
api.route("GET", "/users/:id", {
|
|
97
|
+
summary: "Get user by ID",
|
|
98
|
+
tags: ["users"],
|
|
99
|
+
schema: {
|
|
100
|
+
response: z.object({ id: z.string(), name: z.string() }),
|
|
101
|
+
},
|
|
102
|
+
}, (req) => ({ id: req.params!.id, name: "Alice" }));
|
|
103
|
+
|
|
104
|
+
api.route("POST", "/users", {
|
|
105
|
+
summary: "Create user",
|
|
106
|
+
tags: ["users"],
|
|
107
|
+
schema: {
|
|
108
|
+
body: z.object({ name: z.string().min(2), email: z.string().email() }),
|
|
109
|
+
response: z.object({ id: z.string(), name: z.string(), email: z.string() }),
|
|
110
|
+
},
|
|
111
|
+
}, (req) => ({ id: crypto.randomUUID(), ...req.parsedBody }));
|
|
112
|
+
|
|
113
|
+
app.listen(3000);
|
|
114
|
+
// โ GET /docs Scalar UI
|
|
115
|
+
// โ GET /docs.json Raw OpenAPI JSON
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`api.route()` does three things at once:
|
|
119
|
+
|
|
120
|
+
- โ
Registers the route on PrinceJS
|
|
121
|
+
- โ
Auto-wires `validate(schema.body)` โ no separate import needed
|
|
122
|
+
- โ
Writes the full OpenAPI spec entry
|
|
123
|
+
|
|
124
|
+
| `schema` key | Runtime | Scalar Docs |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `body` | โ
Validates request | โ
requestBody model |
|
|
127
|
+
| `query` | โ | โ
Typed query params |
|
|
128
|
+
| `response` | โ | โ
200 response model |
|
|
129
|
+
|
|
130
|
+
> Routes on `app.get()` / `app.post()` stay private โ never appear in docs.
|
|
131
|
+
|
|
132
|
+
**Themes:** `default` ยท `moon` ยท `purple` ยท `solarized` ยท `bluePlanet` ยท `deepSpace` ยท `saturn` ยท `kepler` ยท `mars`
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## ๐ Plugin System
|
|
137
|
+
|
|
138
|
+
Share bundles of routes and middleware as reusable plugins:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { prince, type PrincePlugin } from "princejs";
|
|
142
|
+
|
|
143
|
+
const usersPlugin: PrincePlugin<{ prefix?: string }> = (app, opts) => {
|
|
144
|
+
const base = opts?.prefix ?? "";
|
|
145
|
+
|
|
146
|
+
app.use((req, next) => {
|
|
147
|
+
(req as any).fromPlugin = true;
|
|
148
|
+
return next();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
app.get(`${base}/users`, (req) => ({
|
|
152
|
+
ok: true,
|
|
153
|
+
fromPlugin: (req as any).fromPlugin,
|
|
154
|
+
}));
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const app = prince();
|
|
158
|
+
app.plugin(usersPlugin, { prefix: "/api" });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## ๐ End-to-End Type Safety
|
|
164
|
+
|
|
165
|
+
Define a contract once โ your client gets full TypeScript autocompletion automatically:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
type ApiContract = {
|
|
169
|
+
"GET /users/:id": {
|
|
170
|
+
params: { id: string };
|
|
171
|
+
response: { id: string; name: string };
|
|
172
|
+
};
|
|
173
|
+
"POST /users": {
|
|
174
|
+
body: { name: string };
|
|
175
|
+
response: { id: string; ok: boolean };
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
import { createClient } from "princejs/client";
|
|
180
|
+
|
|
181
|
+
const client = createClient<ApiContract>("http://localhost:3000");
|
|
182
|
+
|
|
183
|
+
const user = await client.get("/users/:id", { params: { id: "42" } });
|
|
184
|
+
console.log(user.name); // typed as string โ
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## ๐ Deploy Adapters
|
|
190
|
+
|
|
191
|
+
**Vercel Edge** โ `api/[[...route]].ts`
|
|
192
|
+
```ts
|
|
193
|
+
import { toVercel } from "princejs/vercel";
|
|
194
|
+
export default toVercel(app);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Cloudflare Workers** โ `src/index.ts`
|
|
198
|
+
```ts
|
|
199
|
+
import { toWorkers } from "princejs/cloudflare";
|
|
200
|
+
export default toWorkers(app);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Deno Deploy** โ `main.ts`
|
|
204
|
+
```ts
|
|
205
|
+
import { toDeno } from "princejs/deno";
|
|
206
|
+
Deno.serve(toDeno(app));
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## ๐ฏ Full Example
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { prince } from "princejs";
|
|
215
|
+
import { cors, logger, rateLimit, auth, apiKey, jwt, session, compress, serve } from "princejs/middleware";
|
|
216
|
+
import { validate } from "princejs/validation";
|
|
217
|
+
import { cache, upload, sse } from "princejs/helpers";
|
|
218
|
+
import { cron } from "princejs/scheduler";
|
|
219
|
+
import { Html, Head, Body, H1, P, render } from "princejs/jsx";
|
|
220
|
+
import { db } from "princejs/db";
|
|
221
|
+
import { z } from "zod";
|
|
222
|
+
|
|
223
|
+
const app = prince(true);
|
|
224
|
+
|
|
225
|
+
// Global middleware
|
|
226
|
+
app.use(cors());
|
|
227
|
+
app.use(logger());
|
|
228
|
+
app.use(rateLimit({ max: 100, window: 60 }));
|
|
229
|
+
app.use(serve({ root: "./public" }));
|
|
230
|
+
app.use(jwt(key));
|
|
231
|
+
app.use(session({ secret: "key" }));
|
|
232
|
+
app.use(compress());
|
|
233
|
+
|
|
234
|
+
// JSX
|
|
235
|
+
const Page = () => Html(Head("Test Page"), Body(H1("Hello World"), P("This is a test")));
|
|
236
|
+
app.get("/jsx", () => render(Page()));
|
|
237
|
+
|
|
238
|
+
// Database
|
|
239
|
+
const users = db.sqlite("./db.sqlite", "CREATE TABLE users...");
|
|
240
|
+
app.get("/users", () => users.query("SELECT * FROM users"));
|
|
241
|
+
|
|
242
|
+
// WebSockets
|
|
243
|
+
app.ws("/chat", {
|
|
244
|
+
open: (ws) => ws.send("Welcome!"),
|
|
245
|
+
message: (ws, msg) => ws.send(`Echo: ${msg}`),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Auth
|
|
249
|
+
app.get("/protected", auth(), (req) => ({ user: req.user }));
|
|
250
|
+
app.get("/api", apiKey({ keys: ["key_123"] }), handler);
|
|
251
|
+
|
|
252
|
+
// Helpers
|
|
253
|
+
app.get("/data", cache(60)(() => ({ time: Date.now() })));
|
|
254
|
+
app.post("/upload", upload(), (req) => ({ files: Object.keys(req.files || {}) }));
|
|
255
|
+
app.get("/events", sse(), (req) => {
|
|
256
|
+
setInterval(() => req.sseSend({ time: Date.now() }), 1000);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Cron
|
|
260
|
+
cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
|
|
261
|
+
|
|
262
|
+
// OpenAPI + Scalar docs
|
|
263
|
+
const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");
|
|
264
|
+
api.route("GET", "/items", {
|
|
265
|
+
summary: "List items",
|
|
266
|
+
tags: ["items"],
|
|
267
|
+
schema: {
|
|
268
|
+
query: z.object({ q: z.string().optional() }),
|
|
269
|
+
response: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
270
|
+
},
|
|
271
|
+
}, () => [{ id: "1", name: "Widget" }]);
|
|
272
|
+
|
|
273
|
+
app.listen(3000);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## ๐ฆ Installation
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
bun add princejs
|
|
282
|
+
# or
|
|
283
|
+
npm install princejs
|
|
284
|
+
# or
|
|
285
|
+
yarn add princejs
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## ๐ค Contributing
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
git clone https://github.com/MatthewTheCoder1218/princejs
|
|
294
|
+
cd princejs
|
|
295
|
+
bun install
|
|
296
|
+
bun test
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## ๐ Links
|
|
302
|
+
|
|
303
|
+
- ๐ Website: [princejs.vercel.app](https://princejs.vercel.app)
|
|
304
|
+
- ๐ฆ npm: [npmjs.com/package/princejs](https://www.npmjs.com/package/princejs)
|
|
305
|
+
- ๐ป GitHub: [github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
|
|
306
|
+
- ๐ฆ Twitter: [@princejs_bun](https://twitter.com/princejs_bun)
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
<div align="center">
|
|
311
|
+
|
|
312
|
+
**PrinceJS: Small in size. Giant in capability. ๐**
|
|
313
|
+
|
|
314
|
+
*Built with โค๏ธ in Nigeria*
|
|
315
|
+
|
|
316
|
+
</div>
|
package/package.json
CHANGED
package/Readme.md
DELETED
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
# ๐ **PrinceJS**
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-
|
|
5
|
-
### โก Ultra-clean, modern & minimal Bun web framework built by a 13 year old. Among the top three in performance.
|
|
6
|
-
|
|
7
|
-

|
|
8
|
-

|
|
9
|
-

|
|
10
|
-

|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## ๐ Quick Start
|
|
15
|
-
```bash
|
|
16
|
-
bun create princejs my-app
|
|
17
|
-
cd my-app
|
|
18
|
-
bun dev
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
import { prince } from "princejs";
|
|
23
|
-
import { cors, logger } from "princejs/middleware";
|
|
24
|
-
|
|
25
|
-
const app = prince();
|
|
26
|
-
|
|
27
|
-
app.use(cors());
|
|
28
|
-
app.use(logger());
|
|
29
|
-
|
|
30
|
-
app.get("/", () => ({ message: "Hello PrinceJS!" }));
|
|
31
|
-
app.get("/users/:id", (req) => ({ id: req.params.id }));
|
|
32
|
-
|
|
33
|
-
app.listen(3000);
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## ๐งฐ Features
|
|
39
|
-
|
|
40
|
-
```ts
|
|
41
|
-
import { cors, logger, rateLimit, serve } from "princejs/middleware";
|
|
42
|
-
import { validate } from "princejs/validation";
|
|
43
|
-
import { z } from "zod";
|
|
44
|
-
|
|
45
|
-
app
|
|
46
|
-
.use(cors())
|
|
47
|
-
.use(logger())
|
|
48
|
-
.use(rateLimit({ max: 100, window: 60 }))
|
|
49
|
-
.use(serve({ root: "./public" }))
|
|
50
|
-
.use(validate(z.object({
|
|
51
|
-
name: z.string(),
|
|
52
|
-
age: z.number()
|
|
53
|
-
})));
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Middleware
|
|
57
|
-
|
|
58
|
-
* CORS
|
|
59
|
-
* Logger
|
|
60
|
-
* Rate Limiting
|
|
61
|
-
* Static Files
|
|
62
|
-
|
|
63
|
-
### Validation (Zod)
|
|
64
|
-
|
|
65
|
-
### File Uploads
|
|
66
|
-
|
|
67
|
-
### Response Builder
|
|
68
|
-
|
|
69
|
-
### WebSocket Support
|
|
70
|
-
|
|
71
|
-
### Auth & API Keys
|
|
72
|
-
|
|
73
|
-
### Server-Sent Events
|
|
74
|
-
|
|
75
|
-
### Sessions
|
|
76
|
-
|
|
77
|
-
### Response Compression
|
|
78
|
-
|
|
79
|
-
### Route-level Middleware
|
|
80
|
-
|
|
81
|
-
### Plugin System
|
|
82
|
-
|
|
83
|
-
You can share bundles of routes and middleware as plugins.
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
import { prince, type PrincePlugin } from "princejs";
|
|
87
|
-
|
|
88
|
-
const usersPlugin: PrincePlugin<{ prefix?: string }> = (app, opts) => {
|
|
89
|
-
const base = opts?.prefix ?? "";
|
|
90
|
-
|
|
91
|
-
// plugin-wide middleware
|
|
92
|
-
app.use((req, next) => {
|
|
93
|
-
(req as any).fromPlugin = true;
|
|
94
|
-
return next();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// plugin routes
|
|
98
|
-
app.get(`${base}/users`, (req) => ({
|
|
99
|
-
ok: true,
|
|
100
|
-
fromPlugin: (req as any).fromPlugin,
|
|
101
|
-
}));
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const app = prince();
|
|
105
|
-
app.plugin(usersPlugin, { prefix: "/api" });
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### OpenAPI + Scalar Docs
|
|
109
|
-
|
|
110
|
-
Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) API reference UI โ all from a single `app.openapi()` call. Routes, validation, and docs stay in sync automatically.
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
import { prince } from "princejs";
|
|
114
|
-
import { z } from "zod";
|
|
115
|
-
|
|
116
|
-
const app = prince();
|
|
117
|
-
|
|
118
|
-
const api = app.openapi({ title: "My API", version: "1.0.0" }, "/docs", { theme: "moon" });
|
|
119
|
-
|
|
120
|
-
api.route("GET", "/users/:id", {
|
|
121
|
-
summary: "Get user by ID",
|
|
122
|
-
tags: ["users"],
|
|
123
|
-
schema: {
|
|
124
|
-
response: z.object({ id: z.string(), name: z.string() }),
|
|
125
|
-
},
|
|
126
|
-
}, (req) => ({ id: req.params!.id, name: "Alice" }));
|
|
127
|
-
|
|
128
|
-
api.route("POST", "/users", {
|
|
129
|
-
summary: "Create user",
|
|
130
|
-
tags: ["users"],
|
|
131
|
-
schema: {
|
|
132
|
-
body: z.object({ name: z.string().min(2), email: z.string().email() }),
|
|
133
|
-
response: z.object({ id: z.string(), name: z.string(), email: z.string() }),
|
|
134
|
-
},
|
|
135
|
-
}, (req) => ({ id: crypto.randomUUID(), ...req.parsedBody }));
|
|
136
|
-
|
|
137
|
-
app.listen(3000);
|
|
138
|
-
// โ GET /docs Scalar UI
|
|
139
|
-
// โ GET /docs.json Raw OpenAPI JSON
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**`api.route()` does three things at once:**
|
|
143
|
-
- Registers the route on PrinceJS (same as `app.get()` / `app.post()`)
|
|
144
|
-
- Auto-wires `validate(schema.body)` middleware โ no separate import needed
|
|
145
|
-
- Writes the full OpenAPI spec entry including path params, request body, query params, and response schema
|
|
146
|
-
|
|
147
|
-
| `schema` key | Runtime effect | Scalar docs |
|
|
148
|
-
|---|---|---|
|
|
149
|
-
| `body` | โ
Validates & strips via `validate()` | โ
requestBody model |
|
|
150
|
-
| `query` | โ docs only | โ
typed query params |
|
|
151
|
-
| `response` | โ docs only | โ
200 response model |
|
|
152
|
-
|
|
153
|
-
Routes registered with `app.get()` / `app.post()` directly never appear in the docs โ useful for internal health checks, webhooks, and admin endpoints.
|
|
154
|
-
|
|
155
|
-
**Available themes:** `default` ยท `moon` ยท `purple` ยท `solarized` ยท `bluePlanet` ยท `deepSpace` ยท `saturn` ยท `kepler` ยท `mars`
|
|
156
|
-
|
|
157
|
-
### Database (SQLite)
|
|
158
|
-
|
|
159
|
-
### End to End Type-Safety
|
|
160
|
-
|
|
161
|
-
PrinceJS supports contract-based type safety to sync your frontend and backend seamlessly. By defining an API contract, your client receives full TypeScript autocompletion and type-checking for routes, parameters, and responses.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
**Define Your Contract**
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
type ApiContract = {
|
|
168
|
-
"GET /users/:id": {
|
|
169
|
-
params: { id: string };
|
|
170
|
-
response: { id: string; name: string };
|
|
171
|
-
};
|
|
172
|
-
"POST /users": {
|
|
173
|
-
body: { name: string };
|
|
174
|
-
response: { id: string; ok: boolean };
|
|
175
|
-
};
|
|
176
|
-
};
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
**Initialize The Client**
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
import { createClient } from "princejs/client";
|
|
183
|
-
|
|
184
|
-
const client = createClient<ApiContract>("http://localhost:3000");
|
|
185
|
-
|
|
186
|
-
// Fully typed request and response
|
|
187
|
-
const user = await client.get("/users/:id", { params: { id: "42" } });
|
|
188
|
-
console.log(user.name); // Typed as string
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Deploy (Vercel, Workers, Deno)
|
|
194
|
-
|
|
195
|
-
Official adapters let you run the same Prince app on Vercel Edge, Cloudflare Workers, and Deno Deploy.
|
|
196
|
-
|
|
197
|
-
**Vercel (Edge)** โ `api/[[...route]].ts`:
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
import { prince } from "princejs";
|
|
201
|
-
import { toVercel } from "princejs/vercel";
|
|
202
|
-
|
|
203
|
-
const app = prince();
|
|
204
|
-
app.get("/", () => ({ message: "Hello from Vercel!" }));
|
|
205
|
-
|
|
206
|
-
export default toVercel(app);
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
**Cloudflare Workers** โ `src/index.ts`:
|
|
210
|
-
|
|
211
|
-
```ts
|
|
212
|
-
import { prince } from "princejs";
|
|
213
|
-
import { toWorkers } from "princejs/cloudflare";
|
|
214
|
-
|
|
215
|
-
const app = prince();
|
|
216
|
-
app.get("/", () => ({ message: "Hello from Workers!" }));
|
|
217
|
-
|
|
218
|
-
export default toWorkers(app);
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
**Deno Deploy** โ `main.ts`:
|
|
222
|
-
|
|
223
|
-
```ts
|
|
224
|
-
import { prince } from "princejs";
|
|
225
|
-
import { toDeno } from "princejs/deno";
|
|
226
|
-
|
|
227
|
-
const app = prince();
|
|
228
|
-
app.get("/", () => ({ message: "Hello from Deno!" }));
|
|
229
|
-
|
|
230
|
-
Deno.serve(toDeno(app));
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
## Performance With Oha (oha -c 100 -z 30s)
|
|
236
|
-
|
|
237
|
-
| Framework | Req/s | Total |
|
|
238
|
-
|-----------|----------------|--------|
|
|
239
|
-
| Elysia | 25,312 req/s | 759k |
|
|
240
|
-
| Hono | 22,124 req/s | 664k |
|
|
241
|
-
| PrinceJS | 21,748 req/s | 653k |
|
|
242
|
-
| Express | 9,325 req/s | 280k |
|
|
243
|
-
|
|
244
|
-
### Among the top three
|
|
245
|
-
|
|
246
|
-
---
|
|
247
|
-
|
|
248
|
-
## ๐ฏ Full Example
|
|
249
|
-
|
|
250
|
-
```ts
|
|
251
|
-
import { prince } from "princejs";
|
|
252
|
-
import { cors, logger, rateLimit, auth, apiKey, jwt, session, compress, serve } from "princejs/middleware";
|
|
253
|
-
import { validate } from "princejs/validation";
|
|
254
|
-
import { cache, upload, sse } from "princejs/helpers";
|
|
255
|
-
import { cron, openapi } from "princejs/scheduler";
|
|
256
|
-
import { Html, Head, Body, H1, P, render } from "princejs/jsx"
|
|
257
|
-
import { db } from "princejs/db";
|
|
258
|
-
import { z } from "zod";
|
|
259
|
-
|
|
260
|
-
const app = prince(true);
|
|
261
|
-
|
|
262
|
-
app.use(cors());
|
|
263
|
-
app.use(logger());
|
|
264
|
-
app.use(rateLimit({ max: 100, window: 60 }));
|
|
265
|
-
|
|
266
|
-
app.use(serve({ root: "./public" }));
|
|
267
|
-
|
|
268
|
-
app.use(validate(z.object({ name: z.string() })));
|
|
269
|
-
|
|
270
|
-
app.use(jwt(key));
|
|
271
|
-
app.use(session({ secret: "key" }));
|
|
272
|
-
app.use(compress());
|
|
273
|
-
|
|
274
|
-
const Page = () => Html(
|
|
275
|
-
Head("Test Page"),
|
|
276
|
-
Body(
|
|
277
|
-
H1("Hello World"),
|
|
278
|
-
P("This is a test")
|
|
279
|
-
)
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
// With props (optional)
|
|
283
|
-
const Card = (props: any) => Div(
|
|
284
|
-
{ className: "card", style: "padding: 1rem;" },
|
|
285
|
-
H1(props.title),
|
|
286
|
-
P(props.content)
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
// Without props
|
|
290
|
-
const Simple = () => Div(
|
|
291
|
-
H1("No Props Needed"),
|
|
292
|
-
P("Just pure content")
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
const requireAuth = async (req: any, next: any) => {
|
|
296
|
-
const token = req.headers.get("Authorization");
|
|
297
|
-
if (!token) return new Response("Unauthorized", { status: 401 });
|
|
298
|
-
req.user = { id: 1, name: "Alice" };
|
|
299
|
-
return next();
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
app.get("/protected", requireAuth, async (req) => {
|
|
303
|
-
return { user: req.user };
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
const users = db.sqlite("./db.sqlite", "CREATE TABLE users...");
|
|
307
|
-
|
|
308
|
-
app.ws("/chat", {
|
|
309
|
-
open: (ws) => ws.send("Welcome!"),
|
|
310
|
-
message: (ws, msg) => ws.send(`Echo: ${msg}`)
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
app.get("/protected", auth(), (req) => ({ user: req.user }));
|
|
315
|
-
app.get("/api", apiKey({ keys: ["key_123"] }), handler);
|
|
316
|
-
|
|
317
|
-
app.get("/", () => ({ message: "Welcome to PrinceJS" }));
|
|
318
|
-
|
|
319
|
-
app.get("/users/:id", (req) => ({ id: req.params.id }));
|
|
320
|
-
|
|
321
|
-
app.get("/jsx", () => render(Page()));
|
|
322
|
-
|
|
323
|
-
app.get("/data", cache(60)(() => ({ time: Date.now() })));
|
|
324
|
-
|
|
325
|
-
app.post("/upload", upload(), (req) => ({ files: Object.keys(req.files || {}) }));
|
|
326
|
-
|
|
327
|
-
app.get("/events", sse(), (req) => {
|
|
328
|
-
setInterval(() => req.sseSend({ time: Date.now() }), 1000);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
app.get("/count", (req) => ({ visits: req.session.visits++ || 1 }));
|
|
332
|
-
|
|
333
|
-
app.get("/users", () => users.query("SELECT * FROM users"));
|
|
334
|
-
|
|
335
|
-
cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
|
|
336
|
-
|
|
337
|
-
// OpenAPI + Scalar
|
|
338
|
-
const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");
|
|
339
|
-
api.route("GET", "/items", {
|
|
340
|
-
summary: "List items",
|
|
341
|
-
tags: ["items"],
|
|
342
|
-
schema: {
|
|
343
|
-
query: z.object({ q: z.string().optional() }),
|
|
344
|
-
response: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
345
|
-
},
|
|
346
|
-
}, () => [{ id: "1", name: "Widget" }]);
|
|
347
|
-
|
|
348
|
-
app.listen(3000);
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
---
|
|
352
|
-
|
|
353
|
-
## ๐ฆ Installation
|
|
354
|
-
|
|
355
|
-
```bash
|
|
356
|
-
npm install princejs
|
|
357
|
-
# or
|
|
358
|
-
bun add princejs
|
|
359
|
-
# or
|
|
360
|
-
yarn add princejs
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
---
|
|
364
|
-
|
|
365
|
-
## ๐ Documentation
|
|
366
|
-
|
|
367
|
-
Visit: **princejs.vercel.app**
|
|
368
|
-
|
|
369
|
-
---
|
|
370
|
-
|
|
371
|
-
## ๐ค Contributing
|
|
372
|
-
|
|
373
|
-
```bash
|
|
374
|
-
git clone https://github.com/MatthewTheCoder1218/princejs
|
|
375
|
-
cd princejs
|
|
376
|
-
bun install
|
|
377
|
-
bun test
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
---
|
|
381
|
-
|
|
382
|
-
## โญ Star This Repo
|
|
383
|
-
|
|
384
|
-
If PrinceJS helped you, star the repo!
|
|
385
|
-
|
|
386
|
-
GitHub: [https://github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
|
|
387
|
-
|
|
388
|
-
---
|
|
389
|
-
|
|
390
|
-
## ๐ Links
|
|
391
|
-
|
|
392
|
-
* npm: [https://www.npmjs.com/package/princejs](https://www.npmjs.com/package/princejs)
|
|
393
|
-
* GitHub: [https://github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
|
|
394
|
-
* Twitter: [https://twitter.com/Lil_Prince_1218](https://twitter.com/Lil_Prince_1218)
|
|
395
|
-
|
|
396
|
-
---
|
|
397
|
-
|
|
398
|
-
**PrinceJS: Small in size. Giant in capability. ๐**
|