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:

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:

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:

Every decision is visible. Every branch is explicit. No framework magic required.

The trade-off

Magic framework:

Explicit framework:

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:

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:

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:

Explicitness optimizes for reading:

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:

Every question requires context switching to framework docs or source code.

With explicitness:

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:

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:

  1. Read framework docs
  2. Understand decorators
  3. Learn middleware order
  4. Understand DI container
  5. Learn framework conventions
  6. Finally read application code

2 weeks to productivity.

New team member on Hectoday HTTP:

  1. Read application code
  2. See patterns (guards, handlers, responses)
  3. Copy existing routes
  4. 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:

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:

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:

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:

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:

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:

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:

Explicitness pays off over time.

Working in Teams

You have:

Self-documenting code reduces communication overhead.

Valuing maintainability

You prioritize:

Explicit code is maintainable code.

Need runtime independence

You want:

Web Standards enable portability.

Want security visibility

You need:

Explicit guards make security auditable.

Building production APIs

Your API needs:

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:

Choose magic. You’ll ship faster.

If you’re building something that:

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:

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.