@@ -28,7 +28,7 @@ Benchmarked with `oha -c 100 -z 30s` on Windows 10:
2828| Fastify | 15,519 | 16,434 |
2929| Express | 13,138 | 13,458 |
3030
31- > PrinceJS is ** 2.3× faster than Express** , matches Hono head-to-head, and sits at just ** 6.3kB gzipped** — loads in ~ 122ms on a slow 3G connection.
31+ > PrinceJS is ** 2.3× faster than Express** , matches Hono head-to-head, and sits at just ** 5.1kB gzipped** — loads in ~ 101ms on a slow 3G connection.
3232
3333---
3434
@@ -61,11 +61,9 @@ app.listen(3000);
6161
6262| Feature | Import |
6363| ---------| --------|
64- | Routing, WebSockets, OpenAPI, Plugins, Lifecycle Hooks, Cookies, IP | ` princejs ` |
65- | ** Route Grouping** | ` princejs ` |
66- | CORS, Logger, JWT, Auth, Rate Limit, Validate, Compress, Session, API Key | ` princejs/middleware ` |
67- | ** Secure Headers, Timeout, Request ID, IP Restriction, Static Files, JWKS** | ` princejs/middleware ` |
68- | File Uploads, SSE, In-memory Cache, ** Streaming** | ` princejs/helpers ` |
64+ | Routing, Route Grouping, WebSockets, OpenAPI, Plugins, Lifecycle Hooks, Cookies, IP | ` princejs ` |
65+ | CORS, Logger, JWT, JWKS, Auth, Rate Limit, Validate, Compress, Session, API Key, Secure Headers, Timeout, Request ID, IP Restriction, Static Files | ` princejs/middleware ` |
66+ | File Uploads, SSE, Streaming, In-memory Cache | ` princejs/helpers ` |
6967| Cron Scheduler | ` princejs/scheduler ` |
7068| JSX / SSR | ` princejs/jsx ` |
7169| SQLite Database | ` princejs/db ` |
@@ -157,48 +155,56 @@ app.post("/admin", (req) => {
157155
158156---
159157
160- ---
161158
162- ## 📁 Route Grouping
159+ ## 🗂️ Route Grouping
163160
164- Namespace routes under a shared prefix with optional shared middleware. Zero overhead at request time — just registers prefixed routes in the trie .
161+ Group routes under a shared prefix with optional shared middleware. Zero overhead at request time — purely a registration convenience .
165162
166163``` ts
164+ import { prince } from " princejs" ;
165+
166+ const app = prince ();
167+
167168// Basic grouping
168169app .group (" /api" , (r ) => {
169- r .get (" /users" , () => ({ users: [] })); // → GET /api/users
170- r .post (" /users" , (req ) => req .parsedBody ); // → POST /api/users
171- r .get (" /users/:id" , (req ) => ({ id: req .params ?.id })); // → GET /api/users/:id
170+ r .get (" /users" , () => ({ users: [] }));
171+ r .post (" /users" , (req ) => ({ created: req .parsedBody }));
172+ r .get (" /users/:id" , (req ) => ({ id: req .params ?.id }));
172173});
174+ // → GET /api/users
175+ // → POST /api/users
176+ // → GET /api/users/:id
173177
174178// With shared middleware — applies to every route in the group
175179import { auth } from " princejs/middleware" ;
176180
177181app .group (" /admin" , auth (), (r ) => {
178- r .get (" /stats" , () => ({ ok: true })); // → GET /admin/stats
179- r .delete (" /user " , () => ({ deleted: true })); // → DELETE /admin/user
182+ r .get (" /stats" , () => ({ stats: {} }));
183+ r .delete (" /users/:id " , (req ) => ({ deleted: req . params ?. id }));
180184});
181185
182186// Chainable
183187app
184188 .group (" /v1" , (r ) => { r .get (" /ping" , () => ({ v: 1 })); })
185189 .group (" /v2" , (r ) => { r .get (" /ping" , () => ({ v: 2 })); });
190+
191+ app .listen (3000 );
186192```
187193
188194---
189195
190- ## 🔐 Security Middleware
196+ ## 🛡️ Secure Headers
191197
192- ### Secure Headers
193-
194- One call sets X-Frame-Options, HSTS, X-Content-Type-Options, X-XSS-Protection, and Referrer-Policy:
198+ One call sets all the security headers your production app needs:
195199
196200``` ts
197201import { secureHeaders } from " princejs/middleware" ;
198202
199203app .use (secureHeaders ());
204+ // Sets: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection,
205+ // Strict-Transport-Security, Referrer-Policy
200206
201- // Custom overrides
207+ // Custom options
202208app .use (secureHeaders ({
203209 xFrameOptions: " DENY" ,
204210 contentSecurityPolicy: " default-src 'self'" ,
@@ -207,18 +213,25 @@ app.use(secureHeaders({
207213}));
208214```
209215
210- ### Request Timeout
216+ ---
217+
218+ ## ⏱️ Request Timeout
211219
212220Kill hanging requests before they pile up:
213221
214222``` ts
215223import { timeout } from " princejs/middleware" ;
216224
217- app .use (timeout (5000 )); // 408 after 5s
218- app .use (timeout (3000 , " Gateway Timeout" )); // custom message
225+ app .use (timeout (5000 )); // 5 second global timeout → 408
226+ app .use (timeout (3000 , " Slow!" )); // custom message
227+
228+ // Per-route timeout
229+ app .get (" /heavy" , timeout (10000 ), (req ) => heavyOperation ());
219230```
220231
221- ### Request ID
232+ ---
233+
234+ ## 🏷️ Request ID
222235
223236Attach a unique ID to every request for distributed tracing and log correlation:
224237
@@ -228,96 +241,102 @@ import { requestId } from "princejs/middleware";
228241app .use (requestId ());
229242// → sets req.id and X-Request-ID response header
230243
231- // Custom header or generator
232- app .use (requestId ({
233- header: " X-Trace-ID " ,
234- generator : () => ` req-${ Date . now ()} ` ,
235- }));
244+ // Custom header name
245+ app .use (requestId ({ header: " X-Trace-ID " }));
246+
247+ // Custom generator
248+ app . use ( requestId ({ generator : () => ` req-${ Date . now ()} ` }));
236249
237250app .get (" /" , (req ) => ({ requestId: req .id }));
238251```
239252
240- ### IP Restriction
253+ ---
254+
255+ ## 🚫 IP Restriction
241256
242- Allow or deny specific IPs:
257+ Allow or block specific IPs:
243258
244259``` ts
245260import { ipRestriction } from " princejs/middleware" ;
246261
247262// Only allow these IPs
248263app .use (ipRestriction ({ allowList: [" 192.168.1.1" , " 10.0.0.1" ] }));
249264
250- // Block specific IPs
265+ // Block these IPs
251266app .use (ipRestriction ({ denyList: [" 1.2.3.4" ] }));
252267```
253268
254- ### JWKS — Auth0, Clerk, Supabase
255-
256- Verify JWTs against a remote JWKS endpoint — no symmetric key needed:
257-
258- ``` ts
259- import { jwks } from " princejs/middleware" ;
260-
261- // Auth0
262- app .use (jwks (" https://YOUR_DOMAIN.auth0.com/.well-known/jwks.json" ));
263-
264- // Clerk
265- app .use (jwks (" https://YOUR_CLERK_DOMAIN/.well-known/jwks.json" ));
266-
267- // Keys are cached automatically — only fetched on rotation
268- app .get (" /protected" , auth (), (req ) => ({ user: req .user }));
269- ```
270-
271269---
272270
273- ## 📂 Static Files
271+ ## 📁 Static Files
274272
275273Serve a directory of static files. Falls through to your routes if the file doesn't exist:
276274
277275``` ts
278276import { serveStatic } from " princejs/middleware" ;
279277
280278app .use (serveStatic (" ./public" ));
281-
282- // Your API routes still work normally
283- app . get ( " / api/users" , () => ({ users: [] }));
279+ // → GET /logo.png serves ./public/logo.png
280+ // → GET / serves ./public/index.html
281+ // → GET / api/users falls through to your route handler
284282```
285283
286284---
287285
288286## 🌊 Streaming
289287
290- Stream responses chunk by chunk — perfect for AI/LLM token output :
288+ Stream chunked responses for AI/LLM output, large payloads, or anything that generates data over time :
291289
292290``` ts
293291import { stream } from " princejs/helpers" ;
294292
295- // Async generator ( cleanest for AI output)
293+ // Async generator — cleanest for AI token streaming
296294app .get (" /ai" , stream (async function * (req ) {
297295 yield " Hello " ;
296+ await delay (100 );
298297 yield " from " ;
299298 yield " PrinceJS!" ;
300299}));
301300
302- // Callback style
303- app .get (" /stream " , stream ((req ) => {
301+ // Async callback
302+ app .get (" /data " , stream (async (req ) => {
304303 req .streamSend (" chunk 1" );
304+ await fetchMoreData ();
305305 req .streamSend (" chunk 2" );
306306}));
307307
308- // Async callback
309- app .get (" /slow-stream" , stream (async (req ) => {
310- req .streamSend (" Starting..." );
311- await fetch (" https://api.openai.com/v1/chat/completions" , { /* ... */ })
312- .then (res => res .body ?.pipeTo (new WritableStream ({
313- write(chunk ) { req .streamSend (chunk ); }
314- })));
315- }));
308+ // Custom content type for binary or JSON streams
309+ app .get (" /events" , stream (async function * (req ) {
310+ for (const item of items ) {
311+ yield JSON .stringify (item ) + " \n " ;
312+ }
313+ }, { contentType: " application/x-ndjson" }));
314+ ```
315+
316+ ---
317+
318+ ## 🔑 JWKS / Third-Party Auth
319+
320+ Verify JWTs from Auth0, Clerk, Supabase, or any JWKS endpoint — no symmetric key needed:
321+
322+ ``` ts
323+ import { jwks } from " princejs/middleware" ;
316324
317- // Custom content type
318- app .get (" /binary" , stream (() => { /* ... */ }, { contentType: " application/octet-stream" }));
325+ // Auth0
326+ app .use (jwks (" https://your-domain.auth0.com/.well-known/jwks.json" ));
327+
328+ // Clerk
329+ app .use (jwks (" https://your-clerk-domain.clerk.accounts.dev/.well-known/jwks.json" ));
330+
331+ // Supabase
332+ app .use (jwks (" https://your-project.supabase.co/auth/v1/.well-known/jwks.json" ));
333+
334+ // req.user is set after verification, same as jwt()
335+ app .get (" /protected" , auth (), (req ) => ({ user: req .user }));
319336```
320337
338+ ---
339+
321340## 📖 OpenAPI + Scalar Docs ✨
322341
323342Auto-generate an OpenAPI 3.0 spec and serve a beautiful [ Scalar] ( https://scalar.com ) UI — all from a single ` app.openapi() ` call.
@@ -600,17 +619,6 @@ app.post(
600619 (req ) => ({ created: req .parsedBody })
601620);
602621
603- // ── Route groups ─────────────────────────────────────────
604- app .group (" /v1" , (r ) => {
605- r .get (" /status" , () => ({ version: 1 , ok: true }));
606- });
607-
608- // ── Streaming ─────────────────────────────────────────────
609- app .get (" /ai" , stream (async function * () {
610- yield " Hello " ;
611- yield " World!" ;
612- }));
613-
614622// ── Cron ──────────────────────────────────────────────────
615623cron (" * * * * *" , () => console .log (" 💓 heartbeat" ));
616624
@@ -672,7 +680,7 @@ bun test
672680
673681<div align =" center " >
674682
675- ** PrinceJS: 6.3kB . Hono-speed. Everything included. 👑**
683+ ** PrinceJS: 5.1kB . Hono-speed. Everything included. 👑**
676684
677685* Built with ❤️ in Nigeria*
678686
0 commit comments