npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

princejs

v2.2.2

Published

An easy and fast backend framework that is among the top three — by a 13yo developer, for developers.

Readme

👑 PrinceJS

Ultra-clean, modern & minimal Bun web framework.
Built by a 13-year-old Nigerian developer. Among the top three in performance.

npm version GitHub stars npm downloads license

Website · npm · GitHub · Twitter


⚡ Performance

Benchmarked with oha -c 100 -z 30s on Windows 10:

| Framework | Avg Req/s | Peak Req/s | |-----------|----------:|-----------:| | Elysia | 27,606 | 27,834 | | PrinceJS | 17,985 | 18,507 | | Hono | 17,914 | 18,826 | | Fastify | 15,519 | 16,434 | | Express | 13,138 | 13,458 |

PrinceJS is 2.3× faster than Express, matches Hono head-to-head, and sits at approximately 5kB gzipped — loads in approximately 100ms on a slow 3G connection.


🚀 Quick Start

bun add princejs
# or
npm install princejs
import { prince } from "princejs";
import { cors, logger } from "princejs/middleware";

const app = prince();

app.use(cors());
app.use(logger());

app.get("/", () => ({ message: "Hello PrinceJS!" }));
app.get("/users/:id", (req) => ({ id: req.params?.id }));

app.listen(3000);

🧰 Features

| Feature | Import | |---------|--------| | Routing, Route Grouping, WebSockets, OpenAPI, Plugins, Lifecycle Hooks, Cookies, IP | princejs | | CORS, Logger, JWT, JWKS, Auth, Rate Limit, Validate, Compress, Session, API Key, Secure Headers, Timeout, Request ID, IP Restriction, Static Files | princejs/middleware | | File Uploads, SSE, Streaming, In-memory Cache | princejs/helpers | | Cron Scheduler | princejs/scheduler | | JSX / SSR | princejs/jsx | | SQLite Database | princejs/db | | End-to-End Type Safety | princejs/client | | Vercel Edge adapter | princejs/vercel | | Cloudflare Workers adapter | princejs/cloudflare | | Deno Deploy adapter | princejs/deno | | Node.js / Express adapter | princejs/node |


🍪 Cookies & 🌐 IP Detection

Reading Cookies

Cookies are automatically parsed and available on every request:

import { prince } from "princejs";

const app = prince();

app.get("/profile", (req) => ({
  sessionId: req.cookies?.sessionId,
  theme: req.cookies?.theme,
  allCookies: req.cookies, // Record<string, string>
}));

Setting Cookies

Use the response builder for full cookie control:

app.get("/login", (req) =>
  app.response()
    .status(200)
    .json({ ok: true })
    .cookie("sessionId", "abc123", {
      maxAge: 3600,       // 1 hour
      path: "/",
      httpOnly: true,     // not accessible from JS
      secure: true,       // HTTPS only
      sameSite: "Strict", // CSRF protection
    })
);

// Chain multiple cookies
app.response()
  .json({ ok: true })
  .cookie("session", "xyz")
  .cookie("theme", "dark")
  .cookie("lang", "en");

Client IP Detection

app.get("/api/data", (req) => ({
  clientIp: req.ip,
  data: [],
}));

Supported headers (in priority order):

  • X-Forwarded-For — load balancers, proxies (first IP in list)
  • X-Real-IP — Nginx, Apache reverse proxy
  • CF-Connecting-IP — Cloudflare
  • X-Client-IP — other proxy services
  • Fallback — 127.0.0.1
// IP-based rate limiting
app.use((req, next) => {
  const count = ipTracker.getCount(req.ip) || 0;
  if (count > 100) return new Response("Too many requests", { status: 429 });
  ipTracker.increment(req.ip);
  return next();
});

// IP allowlist
app.post("/admin", (req) => {
  if (!ALLOWED_IPS.includes(req.ip!)) {
    return new Response("Forbidden", { status: 403 });
  }
  return { authorized: true };
});

🗂️ Route Grouping

Group routes under a shared prefix with optional shared middleware. Zero overhead at request time — purely a registration convenience.

import { prince } from "princejs";

const app = prince();

// Basic grouping
app.group("/api", (r) => {
  r.get("/users",     () => ({ users: [] }));
  r.post("/users",    (req) => ({ created: req.parsedBody }));
  r.get("/users/:id", (req) => ({ id: req.params?.id }));
});
// → GET  /api/users
// → POST /api/users
// → GET  /api/users/:id

// With shared middleware — applies to every route in the group
import { auth } from "princejs/middleware";

app.group("/admin", auth(), (r) => {
  r.get("/stats",   () => ({ stats: {} }));
  r.delete("/users/:id", (req) => ({ deleted: req.params?.id }));
});

// Chainable
app
  .group("/v1", (r) => { r.get("/ping", () => ({ v: 1 })); })
  .group("/v2", (r) => { r.get("/ping", () => ({ v: 2 })); });

app.listen(3000);

🛡️ Secure Headers

One call sets all the security headers your production app needs:

import { secureHeaders } from "princejs/middleware";

app.use(secureHeaders());
// Sets: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection,
//       Strict-Transport-Security, Referrer-Policy

// Custom options
app.use(secureHeaders({
  xFrameOptions: "DENY",
  contentSecurityPolicy: "default-src 'self'",
  permissionsPolicy: "camera=(), microphone=()",
  strictTransportSecurity: "max-age=63072000; includeSubDomains; preload",
}));

⏱️ Request Timeout

Kill hanging requests before they pile up:

import { timeout } from "princejs/middleware";

app.use(timeout(5000));          // 5 second global timeout → 408
app.use(timeout(3000, "Slow!")); // custom message

// Per-route timeout
app.get("/heavy", timeout(10000), (req) => heavyOperation());

🏷️ Request ID

Attach a unique ID to every request for distributed tracing and log correlation:

import { requestId } from "princejs/middleware";

app.use(requestId());
// → sets req.id and X-Request-ID response header

// Custom header name
app.use(requestId({ header: "X-Trace-ID" }));

// Custom generator
app.use(requestId({ generator: () => `req-${Date.now()}` }));

app.get("/", (req) => ({ requestId: req.id }));

🚫 IP Restriction

Allow or block specific IPs:

import { ipRestriction } from "princejs/middleware";

// Only allow these IPs
app.use(ipRestriction({ allowList: ["192.168.1.1", "10.0.0.1"] }));

// Block these IPs
app.use(ipRestriction({ denyList: ["1.2.3.4"] }));

📁 Static Files

Serve a directory of static files. Falls through to your routes if the file doesn't exist:

import { serveStatic } from "princejs/middleware";

app.use(serveStatic("./public"));
// → GET /logo.png        serves ./public/logo.png
// → GET /               serves ./public/index.html
// → GET /api/users      falls through to your route handler

🌊 Streaming

Stream chunked responses for AI/LLM output, large payloads, or anything that generates data over time:

import { stream } from "princejs/helpers";

// Async generator — cleanest for AI token streaming
app.get("/ai", stream(async function*(req) {
  yield "Hello ";
  await delay(100);
  yield "from ";
  yield "PrinceJS!";
}));

// Async callback
app.get("/data", stream(async (req) => {
  req.streamSend("chunk 1");
  await fetchMoreData();
  req.streamSend("chunk 2");
}));

// Custom content type for binary or JSON streams
app.get("/events", stream(async function*(req) {
  for (const item of items) {
    yield JSON.stringify(item) + "\n";
  }
}, { contentType: "application/x-ndjson" }));

🔑 JWKS / Third-Party Auth

Verify JWTs from Auth0, Clerk, Supabase, or any JWKS endpoint — no symmetric key needed:

import { jwks } from "princejs/middleware";

// Auth0
app.use(jwks("https://your-domain.auth0.com/.well-known/jwks.json"));

// Clerk
app.use(jwks("https://your-clerk-domain.clerk.accounts.dev/.well-known/jwks.json"));

// Supabase
app.use(jwks("https://your-project.supabase.co/auth/v1/.well-known/jwks.json"));

// req.user is set after verification, same as jwt()
app.get("/protected", auth(), (req) => ({ user: req.user }));

📖 OpenAPI + Scalar Docs ✨

Auto-generate an OpenAPI 3.0 spec and serve a beautiful Scalar UI — all from a single app.openapi() call.

import { prince } from "princejs";
import { z } from "zod";

const app = prince();

const api = app.openapi({ title: "My API", version: "1.0.0" }, "/docs", { theme: "moon" });

api.route("GET", "/users/:id", {
  summary: "Get user by ID",
  tags: ["users"],
  schema: {
    response: z.object({ id: z.string(), name: z.string() }),
  },
}, (req) => ({ id: req.params!.id, name: "Alice" }));

api.route("POST", "/users", {
  summary: "Create user",
  tags: ["users"],
  schema: {
    body:     z.object({ name: z.string().min(2), email: z.string().email() }),
    response: z.object({ id: z.string(), name: z.string(), email: z.string() }),
  },
}, (req) => ({ id: crypto.randomUUID(), ...req.parsedBody }));

app.listen(3000);
// → GET /docs       Scalar UI
// → GET /docs.json  Raw OpenAPI JSON

api.route() does three things at once:

  • ✅ Registers the route on PrinceJS
  • ✅ Auto-wires body validation — no separate middleware needed
  • ✅ Writes the full OpenAPI spec entry

| schema key | Runtime effect | Scalar docs | |---|---|---| | body | ✅ Validates & rejects bad requests | ✅ requestBody model | | query | — | ✅ Typed query params | | response | — | ✅ 200 response model |

Routes on app.get() / app.post() stay private — they never appear in the docs.

Themes: default · moon · purple · solarized · bluePlanet · deepSpace · saturn · kepler · mars


🔌 Plugin System

import { prince, type PrincePlugin } from "princejs";

const usersPlugin: PrincePlugin<{ prefix?: string }> = (app, opts) => {
  const base = opts?.prefix ?? "";

  app.use((req, next) => {
    (req as any).fromPlugin = true;
    return next();
  });

  app.get(`${base}/users`, (req) => ({
    ok: true,
    fromPlugin: (req as any).fromPlugin,
  }));
};

const app = prince();
app.plugin(usersPlugin, { prefix: "/api" });
app.listen(3000);

🎣 Lifecycle Hooks

import { prince } from "princejs";

const app = prince();

app.onRequest((req) => {
  (req as any).startTime = Date.now();
});

app.onBeforeHandle((req, path, method) => {
  console.log(`🔍 ${method} ${path}`);
});

app.onAfterHandle((req, res, path, method) => {
  const ms = Date.now() - (req as any).startTime;
  console.log(`✅ ${method} ${path} ${res.status} (${ms}ms)`);
});

app.onError((err, req, path, method) => {
  console.error(`❌ ${method} ${path}:`, err.message);
});

app.get("/users", () => ({ users: [] }));
app.listen(3000);

Execution order:

  1. onRequest — runs before routing, good for setup
  2. onBeforeHandle — just before the handler
  3. Handler executes
  4. onAfterHandle — after success (skipped on error)
  5. onError — only when handler throws

🔒 End-to-End Type Safety

import { createClient, type PrinceApiContract } from "princejs/client";

type ApiContract = {
  "GET /users/:id": {
    params: { id: string };
    response: { id: string; name: string };
  };
  "POST /users": {
    body: { name: string };
    response: { id: string; ok: boolean };
  };
};

const client = createClient<ApiContract>("http://localhost:3000");

const user = await client.get("/users/:id", { params: { id: "42" } });
console.log(user.name); // typed as string ✅

const created = await client.post("/users", { body: { name: "Alice" } });
console.log(created.id); // typed as string ✅

🌍 Deploy Adapters

Vercel Edgeapi/[[...route]].ts

import { toVercel } from "princejs/vercel";
export default toVercel(app);

Cloudflare Workerssrc/index.ts

import { toWorkers } from "princejs/cloudflare";
export default toWorkers(app);

Deno Deploymain.ts

import { toDeno } from "princejs/deno";
Deno.serve(toDeno(app));

Node.jsserver.ts

import { createServer } from "http";
import { toNode, toExpress } from "princejs/node";
import express from "express";

const app = prince();
app.get("/", () => ({ message: "Hello!" }));

// Native Node http
createServer(toNode(app)).listen(3000);

// Or drop into Express
const expressApp = express();
expressApp.all("*", toExpress(app));
expressApp.listen(3000);

🎯 Full Example

import { prince } from "princejs";
import {
  cors,
  logger,
  rateLimit,
  auth,
  apiKey,
  jwt,
  signJWT,
  session,
  compress,
  validate,
  secureHeaders,
  timeout,
  requestId,
} from "princejs/middleware";
import { cache, upload, sse, stream } from "princejs/helpers";
import { cron } from "princejs/scheduler";
import { Html, Head, Body, H1, P, render } from "princejs/jsx";
import { db } from "princejs/db";
import { z } from "zod";

const SECRET = new TextEncoder().encode("your-secret");
const app = prince();

// ── Lifecycle hooks ───────────────────────────────────────
app.onRequest((req) => { (req as any).t = Date.now(); });
app.onAfterHandle((req, res, path, method) => {
  console.log(`✅ ${method} ${path} ${res.status} (${Date.now() - (req as any).t}ms)`);
});
app.onError((err, req, path, method) => {
  console.error(`❌ ${method} ${path}:`, err.message);
});

// ── Global middleware ─────────────────────────────────────
app.use(secureHeaders());
app.use(requestId());
app.use(timeout(10000));
app.use(cors());
app.use(logger());
app.use(rateLimit(100, 60));
app.use(jwt(SECRET));
app.use(session({ secret: "session-secret" }));
app.use(compress());

// ── JSX SSR ───────────────────────────────────────────────
const Page = () => Html(Head("Home"), Body(H1("Hello World"), P("Welcome!")));
app.get("/", () => render(Page()));

// ── Cookies & IP ──────────────────────────────────────────
app.post("/login", (req) =>
  app.response()
    .json({ ok: true, ip: req.ip })
    .cookie("sessionId", "user_123", {
      httpOnly: true, secure: true, sameSite: "Strict", maxAge: 86400,
    })
);
app.get("/profile", (req) => ({
  sessionId: req.cookies?.sessionId,
  clientIp: req.ip,
}));

// ── Database ──────────────────────────────────────────────
const users = db.sqlite("./app.sqlite", `
  CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, name TEXT NOT NULL)
`);
app.get("/users", () => users.query("SELECT * FROM users"));

// ── WebSockets ────────────────────────────────────────────
app.ws("/chat", {
  open:    (ws) => ws.send("Welcome!"),
  message: (ws, msg) => ws.send(`Echo: ${msg}`),
  close:   (ws) => console.log("disconnected"),
});

// ── Auth & API keys ───────────────────────────────────────
app.get("/protected", auth(), (req) => ({ user: req.user }));
app.get("/api", apiKey({ keys: ["key_123"] }), () => ({ ok: true }));

// ── Helpers ───────────────────────────────────────────────
app.get("/cached",  cache(60)(() => ({ time: Date.now() })));
app.post("/upload", upload());
app.get("/events",  sse(), (req) => {
  let i = 0;
  const id = setInterval(() => {
    req.sseSend({ count: i++ });
    if (i >= 10) clearInterval(id);
  }, 1000);
});

// ── Validation ────────────────────────────────────────────
app.post(
  "/items",
  validate(z.object({ name: z.string().min(1), price: z.number().positive() })),
  (req) => ({ created: req.parsedBody })
);

// ── Cron ──────────────────────────────────────────────────
cron("* * * * *", () => console.log("💓 heartbeat"));

// ── OpenAPI + Scalar ──────────────────────────────────────
const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");

api.route("GET", "/items", {
  summary: "List items",
  tags: ["items"],
  schema: {
    query:    z.object({ q: z.string().optional() }),
    response: z.array(z.object({ id: z.string(), name: z.string() })),
  },
}, () => [{ id: "1", name: "Widget" }]);

api.route("POST", "/items", {
  summary: "Create item",
  tags: ["items"],
  schema: {
    body:     z.object({ name: z.string().min(1), price: z.number().positive() }),
    response: z.object({ id: z.string(), name: z.string() }),
  },
}, (req) => ({ id: crypto.randomUUID(), name: req.parsedBody.name }));

app.listen(3000);

📦 Installation

bun add princejs
# or
npm install princejs

🤝 Contributing

git clone https://github.com/MatthewTheCoder1218/princejs
cd princejs
bun install
bun test

🔗 Links


PrinceJS: ~5kB. Hono-speed. Everything included. 👑

Built with ❤️ in Nigeria