There's a trap that kills most developers before they ever get good.
You finish a tutorial. You understand it. You feel ready. Then you open a blank file and freeze — because the tutorial told you what to type, but it didn't teach you how to think.
So you watch another tutorial. Then another. Then another.
That's tutorial hell. I almost got stuck in it at 13. Instead I built PrinceJS — one of the top three fastest Bun web frameworks in the world.
The Tutorial Hell Trap
I'm from Abuja, Nigeria. I've been coding for a couple of years, mostly through YouTube videos and online courses.
The problem with tutorials is that they're engineered to make you feel productive. You follow along, the code works, and you get the dopamine hit of seeing something run. But when the tutorial ends and you try to build something yourself, you realise you can't.
I watched videos about building to-do apps, weather apps, CRUD apps. People kept saying that building apps like these wouldn't get you anywhere as a developer — that you needed to build something real. I agreed. But I didn't know what "something real" meant. So I watched more tutorials.
Then one day I just got bored.
Not motivated. Not inspired. Just completely bored of watching other people write code. So I closed YouTube and asked myself one question: what do I actually use every day as a developer?
A web framework.
Three Days on a 2015 Windows Laptop
I had no tutorial. No roadmap. Just Bun's source code, some CS reading about trie data structures, and a blank file.
Day one: I built the routing layer. Most frameworks use hash maps for URL routing — O(1) average, but hash collisions degrade performance under heavy concurrent load. I used a compressed trie instead. O(k) where k equals the number of URL path segments. Zero collisions. Fully predictable under any load.
Day two: I had a working server. I ran my first benchmark.
19,036 requests per second on Bun.
I screamed. Genuinely.
Day three: Optimisation. Removed one unnecessary function call from the hot path. Added response header caching. Set a rule I've kept ever since: the bundle stays under 5kb. No runtime dependencies.
Final benchmark: 21,748 requests per second — third fastest Bun framework in the world, benchmarked with oha -c 100 -z 30s on Windows 10.
And because of the 4.8kb gzipped bundle: 97 milliseconds on Slow 3G.
I'm from Nigeria. Slow 3G is real here. I built for it.
What PrinceJS Is Today
Three days turned into a v2 release. PrinceJS now ships a full production feature set — still in 4.8kb gzipped:
import { prince } from "princejs";
import { cors, logger, rateLimit, auth, jwt, session, compress } from "princejs/middleware";
import { cache, upload, sse } from "princejs/helpers";
import { cron } from "princejs/scheduler";
import { db } from "princejs/db";
const app = prince();
app.use(cors());
app.use(logger());
app.use(rateLimit({ max: 100, window: 60 }));
app.use(compress());
// Routes with params
app.get("/users/:id", (req) => ({ id: req.params.id }));
// WebSockets
app.ws("/chat", {
open: (ws) => ws.send("Connected!"),
message: (ws, msg) => ws.send(`Echo: ${msg}`),
});
// Server-Sent Events
app.get("/events", sse(), (req) => {
setInterval(() => req.sseSend({ time: Date.now() }), 1000);
});
// Cached route
app.get("/data", cache(60)(() => ({ time: Date.now() })));
// SQLite
const users = db.sqlite("./db.sqlite", "CREATE TABLE IF NOT EXISTS users (id TEXT)");
app.get("/users", () => users.query("SELECT * FROM users"));
// Cron job
cron("*/1 * * * *", () => console.log("heartbeat"));
app.listen(3000);
Every feature in that example ships inside the 4.8kb bundle. No separate installs. No bloating.
Auto-Generated OpenAPI Docs
One of the features I'm most proud of in v2: define a route once and get runtime validation, type safety, and Scalar UI documentation automatically:
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("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() }),
},
}, (req) => ({ id: crypto.randomUUID(), ...req.parsedBody }));
app.listen(3000);
// GET /docs → beautiful Scalar UI, auto-updated
End-to-End Type Safety
Define your API contract once, get full TypeScript autocompletion on the client automatically:
type ApiContract = {
"GET /users/:id": {
params: { id: string };
response: { id: string; name: string };
};
};
import { createClient } from "princejs/client";
const client = createClient<ApiContract>("http://localhost:3000");
const user = await client.get("/users/:id", { params: { id: "42" } });
console.log(user.name); // typed as string ✅
The Real Numbers
| Metric | Value |
|---|---|
| Requests/sec | 21,748 (oha -c 100 -z 30s, Windows 10) |
| Global rank | #3 fastest Bun framework |
| vs Express | 2.3x faster |
| vs Hono | 1.7% slower |
| Bundle size | 4.8kb gzipped |
| Slow 3G load | 97ms |
| Runtime dependencies | 0 |
| npm total downloads | 5.2k and growing |
| Built in | 3 days, age 13, Abuja Nigeria |
What I Learned
Boredom is a better teacher than motivation. I built PrinceJS because I had nothing to watch — not because I felt inspired. The blank file was the tutorial.
Read source code, not tutorials. In three days of reading Bun's source code and trie papers I learned more than I had in months of following along with videos.
Honesty about tradeoffs builds trust. PrinceJS is 1.7% slower than Hono on req/s. I publish that number openly in the README. Developers respect honesty more than marketing.
Build for the users you know. I'm from Nigeria. I optimised for Slow 3G because I know what it feels like. The 97ms result is a product of building for my reality, not for a benchmark chart.
Try It
bun add princejs
Website: princejs.vercel.app
npm: npmjs.com/package/princejs
GitHub: github.com/MatthewTheCoder1218/princejs
X: @Lil_Prince_1218
If you're stuck in tutorial hell right now — close the video. Open a blank file. Build something you actually use. You don't need permission. You're already ready.
Top comments (0)