Philosophy (revisited)
You’ve read the concepts. You’ve seen the examples. You’ve studied the reference.
Now let’s return to the question: Why is Hectoday HTTP designed this way?
Why there is no magic
Magic in frameworks comes in many forms:
- Middleware that auto-returns responses
- Decorators that auto-generate routes
- Exceptions that auto-map to status codes
- Conventions that auto-wire dependencies
Magic is convenient. It saves typing. It feels productive.
But magic has a cost.
Magic hides control flow
// In a framework with magic
@Route("/users/:id")
@RequireAuth() // Does this return 401? When?
@ValidateParams(UserIdSchema) // Does this return 400? When?
async getUser(id: string) {
const user = await db.users.get(id);
return user; // What if user is null? What happens?
}Reading this code, you can’t answer:
- When does the request end?
- What status codes can this return?
- What responses does the client see?
- Where do errors go?
To understand the code, you need to understand the framework’s magic.
Explicitness makes control flow visible
// In Hectoday HTTP
route.get("/users/:id", {
guards: [requireAuth], // Explicit: returns 401 if no auth
request: {
params: z.object({ id: z.string().uuid() }) // Explicit: validation
},
resolve: async (c) => {
// Explicit: check validation
if (!c.input.ok) {
return Response.json({ error: c.input.issues }, { status: 400 });
}
const user = await db.users.get(c.input.params.id);
// Explicit: check if user exists
if (!user) {
return Response.json({ error: "User not found" }, { status: 404 });
}
// Explicit: success response
return Response.json(user);
}
})Reading this code, you know:
- Request ends if
requireAuthdenies (401) - Request ends if validation fails (400)
- Request ends if user not found (404)
- Request ends with user data (200)
Every decision is visible. Every branch is explicit. No framework magic required.
The trade-off
Magic framework:
- ✓ Less code to write
- ✓ Faster initial development
- ✗ Hidden control flow
- ✗ Framework knowledge required
- ✗ Debugging is harder
Explicit framework:
- ✗ More code to write
- ✗ Slower initial development
- ✓ Visible control flow
- ✓ Code is self-documenting
- ✓ Debugging is straightforward
Hectoday HTTP chooses visibility over convenience.
Not because convenience is bad. But because in production systems that last years, maintained by multiple people, visibility is more valuable than convenience.
Magic breaks down at scale
Small project (1 developer, 3 months):
// Magic works fine
@Route("/api/data")
@Auth()
getData() {
return this.dataService.get();
}Magic is great here. One person understands it. Code is simple.
Large project (10 developers, 3 years):
// What does this actually do?
@Route("/api/v2/organizations/:orgId/projects/:projectId/reports")
@Auth()
@RequireSubscription("premium")
@RateLimit(100, "1h")
@CacheFor("5m")
@AuditLog()
@Validated(ReportSchema)
async getReport(orgId: string, projectId: string, params: ReportParams) {
// 300 lines of business logic
}Magic breaks down. Six different decorators. Each with its own rules. Each potentially returning different responses. The actual control flow is completely hidden.
New developer questions:
- “Which decorator runs first?”
- “What happens if Auth fails?”
- “Does RateLimit run if Auth fails?”
- “What status codes can this return?”
- “Where do I add logging?”
Answers require reading framework docs, not the code.
Explicit version:
route.get("/api/v2/organizations/:orgId/projects/:projectId/reports", {
guards: [
requireAuth,
requireSubscription("premium"),
rateLimit(100, 60 * 60 * 1000),
auditLog("report_access")
],
request: {
params: z.object({
orgId: z.string().uuid(),
projectId: z.string().uuid()
}),
query: reportSchema
},
resolve: async (c) => {
if (!c.input.ok) {
return Response.json({ error: c.input.issues }, { status: 400 });
}
// 300 lines of business logic
// Every return statement is visible
// Every status code is explicit
}
})All behavior is visible:
- Guards run in order: auth → subscription → rate limit → audit
- Each guard can deny (explicit Response)
- Validation can fail (explicit 400)
- Handler logic is explicit
New developer can read this code and understand it without reading framework docs.
Magic optimizes for writing, not reading
You write code once. You read it hundreds of times.
Magic optimizes for writing:
- Fewer lines typed
- Less boilerplate
- “DRY” (Don’t Repeat Yourself)
Explicitness optimizes for reading:
- Control flow is visible
- Decisions are local
- No hidden behavior
Code is read 10x more than it’s written. Hectoday HTTP optimizes for reading.
The real cost of magic
The cost of magic isn’t lines of code. It’s cognitive load.
With magic:
- “What does this decorator do?”
- “Where is this coming from?”
- “Why did this return 401?”
- “How do I debug this?”
Every question requires context switching to framework docs or source code.
With explicitness:
- Everything is in the code you’re reading
- Questions are answered locally
- Debugging is tracing execution
Less context switching = faster understanding = faster development.
Why explicitness scales
Explicit code has properties that become more valuable as projects grow.
Property 1: code is self-documenting
// This code documents itself
route.delete("/admin/users/:id", {
guards: [
requireAuth, // 1. Must be authenticated
requireAdmin, // 2. Must be admin
requireEmailVerified, // 3. Email must be verified
requireNotSelf // 4. Can't delete self
],
resolve: async (c) => {
const id = c.raw.params.id;
// Check if user exists
const user = await db.users.get(id);
if (!user) {
return Response.json({ error: "User not found" }, { status: 404 });
}
// Check if user has active sessions
const sessions = await db.sessions.getByUserId(id);
if (sessions.length > 0) {
return Response.json(
{ error: "Cannot delete user with active sessions" },
{ status: 409 }
);
}
// Soft delete
await db.users.softDelete(id);
return new Response(null, { status: 204 });
}
})Reading this tells you:
- Who can call this endpoint (admins with verified emails, not self)
- What can go wrong (not found, active sessions)
- What happens on success (soft delete, 204)
No external docs needed. The code is the documentation.
Property 2: changes are localized
To change behavior, you change the code directly:
Want to add a new check?
// Add a guard
guards: [
requireAuth,
requireAdmin,
requireEmailVerified,
requireNotSelf,
requireNoActiveOrders // ← New guard
],Want to change an error message?
// Change the return statement
if (!user) {
return Response.json(
{
error: "User not found",
hint: "Check that the user ID is correct" // ← Changed
},
{ status: 404 }
);
}Want to add logging?
resolve: async (c) => {
console.log("Deleting user:", c.raw.params.id); // ← Added
const id = c.raw.params.id;
// ...
}All changes are local. No framework config. No global middleware. Just edit the handler.
Property 3: refactoring is safe
When code is explicit, refactoring is straightforward:
Extract a helper:
// Before
if (!user) {
return Response.json({ error: "User not found" }, { status: 404 });
}
// After
if (!user) {
return notFound("User not found");
}
function notFound(message: string) {
return Response.json({ error: message }, { status: 404 });
}Extract a guard:
// Before
resolve: async (c) => {
const sessions = await db.sessions.getByUserId(id);
if (sessions.length > 0) {
return Response.json({ error: "Active sessions" }, { status: 409 });
}
// ...
}
// After
guards: [
requireAuth,
requireAdmin,
requireNoActiveSessions // ← Extracted
],
resolve: async (c) => {
// Sessions already checked by guard
// ...
}TypeScript guides refactoring. If it compiles, it probably works.
Property 4: testing is straightforward
Explicit code is easy to test (as we saw in Chapter 13):
Deno.test("DELETE /users/:id requires all permissions", async () => {
const app = setup({ handlers: [deleteUserRoute] });
// Test 1: No auth → 401
const res1 = await app.fetch(
new Request("http://localhost/admin/users/123", { method: "DELETE" })
);
assertEquals(res1.status, 401);
// Test 2: Not admin → 403
const res2 = await app.fetch(
new Request("http://localhost/admin/users/123", {
method: "DELETE",
headers: { "Authorization": "Bearer user-token" }
})
);
assertEquals(res2.status, 403);
// Test 3: Delete self → 400
const res3 = await app.fetch(
new Request("http://localhost/admin/users/admin-123", {
method: "DELETE",
headers: { "Authorization": "Bearer admin-token" }
})
);
assertEquals(res3.status, 400);
// Test 4: Success → 204
const res4 = await app.fetch(
new Request("http://localhost/admin/users/other-user", {
method: "DELETE",
headers: { "Authorization": "Bearer admin-token" }
})
);
assertEquals(res4.status, 204);
});Every branch is testable. Just create requests and check responses.
Property 5: onboarding is faster
New team member on magic framework:
- Read framework docs
- Understand decorators
- Learn middleware order
- Understand DI container
- Learn framework conventions
- Finally read application code
2 weeks to productivity.
New team member on Hectoday HTTP:
- Read application code
- See patterns (guards, handlers, responses)
- Copy existing routes
- Make changes
2 days to productivity.
Because the code is self-documenting, learning happens by reading code.
Property 6: debugging is tracing execution
With magic, debugging requires understanding framework internals.
With explicitness, debugging is tracing execution:
route.post("/users", {
guards: [requireAuth, requireAdmin], // Add console.log here
resolve: async (c) => {
console.log("Handler running"); // Or here
if (!c.input.ok) {
console.log("Validation failed:", c.input.issues); // Or here
return Response.json({ error: c.input.issues }, { status: 400 });
}
console.log("Creating user:", c.input.body); // Or here
const user = await db.users.create(c.input.body);
console.log("User created:", user.id); // Or here
return Response.json(user, { status: 201 });
}
})Every step is visible. Add logging anywhere. Trace the exact path.
When hectoday HTTP Is the wrong tool
Honesty time: Hectoday HTTP is not for everyone.
Here’s when you should use something else.
When prototyping
If you’re:
- Building a proof of concept
- Validating an idea quickly
- Experimenting with different approaches
- Throwing away code in a week
Use something with more magic. Explicitness slows you down when you’re exploring.
Good alternatives: Express, Hono, Elysia (for speed)
When building trivial APIs
If your API is:
- 3-5 simple CRUD endpoints
- No complex validation
- No authorization logic
- Will never grow beyond this
Use something simpler. Hectoday HTTP’s structure is overkill.
Good alternatives: Deno’s Deno.serve() directly, or any lightweight router
When you want batteries included
If you want:
- Built-in ORM
- Built-in auth system
- Built-in session management
- Built-in admin panel
- Full-stack framework
Use a full-stack framework. Hectoday HTTP is just HTTP handling.
Good alternatives: Remix, Next.js, Fresh (for full-stack)
When your team prefers magic
If your team:
- Loves decorators
- Prefers “convention over configuration”
- Wants minimal boilerplate
- Doesn’t mind framework-specific patterns
Use what your team prefers. Team productivity matters more than framework choice.
Good alternatives: NestJS, Fastify with decorators
When you need maximum performance
If you need:
- Absolute lowest latency (every microsecond counts)
- Maximum throughput (millions of requests/second)
- Metal-close performance
Use something more optimized. Hectoday HTTP prioritizes clarity over micro-optimizations.
Good alternatives: Rust/Axum, Go/Gin, Bun with zero-abstraction handlers
When you’re building microservices at scale
If you’re:
- Building 100+ services
- Need centralized policy enforcement
- Want consistent API gateway patterns
- Need service mesh integration
Use a framework designed for microservices. Hectoday HTTP is for individual services, not orchestration.
Good alternatives: Use Hectoday HTTP for individual services, but add Istio/Envoy/Kong for orchestration
When hectoday HTTP Is the Right Tool
Hectoday HTTP shines when:
Building APIs that last
Your API will:
- Live for years
- Be maintained by multiple people
- Grow beyond initial scope
- Need to be understood by new team members
Explicitness pays off over time.
Working in Teams
You have:
- Multiple developers
- Different experience levels
- Code review processes
- Need for predictable patterns
Self-documenting code reduces communication overhead.
Valuing maintainability
You prioritize:
- Code readability over brevity
- Debugging ease over development speed
- Long-term understanding over short-term convenience
Explicit code is maintainable code.
Need runtime independence
You want:
- To deploy on multiple runtimes
- To switch runtimes without rewriting
- To avoid vendor lock-in
Web Standards enable portability.
Want security visibility
You need:
- To audit security controls
- To see exactly what checks run
- To verify authorization logic
- To pass security reviews
Explicit guards make security auditable.
Building production APIs
Your API needs:
- Reliable behavior
- Clear error handling
- Traceable request flow
- Production-ready patterns
Explicit control flow is production-ready.
The core trade-off
Every framework makes a trade-off:
Magic frameworks: Fast to write, slow to understand
Explicit frameworks: Slow to write, fast to understand
The question is: which matters more for your project?
If you’re building something that:
- Will be thrown away soon
- Only you will maintain
- Is simple and won’t grow
Choose magic. You’ll ship faster.
If you’re building something that:
- Will last years
- Multiple people will maintain
- Will grow in complexity
Choose explicitness. You’ll maintain faster.
The philosophy in one sentence
Hectoday HTTP describes what happened. You decide what it means.
The framework never makes HTTP decisions. It computes facts about requests. Guards make allow/deny decisions. Handlers commit responses.
Everything else is your job.
And when debugging at 2am, when code breaks in production, when a new developer joins the team, when a security audit happens, when requirements change…
You’ll be glad the code is explicit.
Closing thoughts
If you’ve read this far, you understand Hectoday HTTP’s philosophy.
You might not agree with it. That’s okay. Different projects need different tools. Different teams have different values.
But if you value:
- Explicit control flow
- Self-documenting code
- Visible security
- Runtime independence
- Long-term maintainability
Try Hectoday HTTP.
Write a route. See how it feels. Notice that you can read it six months later and understand exactly what it does.
That’s the point.
Additional resources
Build something. See if explicitness works for you.
If it does, welcome. If it doesn’t, that’s fine too. Use what works.
The best framework is the one that helps your team ship quality software.
For us, that’s Hectoday HTTP.