Skip to content

Architecture: Deepen Router to own middleware and eliminate route-matching leaks across middleware #138

Description

@jonbaldie

Candidate Summary

  • Recommendation Strength: Worth exploring
  • Modules Involved: src/router.ts, src/middleware.ts, src/handler.ts
  • Domain Context: HTTP transport layer for Queue and Item Lifecycle
  • Architecture Vocabulary: module, interface, depth, seam, adapter, leverage, locality

Problem & Evidence

Currently, Router in src/router.ts is a shallow module that only registers paths and methods, and executes them against a request URL.

Because Router does not own middleware or route execution pipelines, src/handler.ts wraps the entire router.handle method in a global chain:

const handlerWithAuth = withAuth(apiToken)(router.handle);
const handlerWithRateLimit = withRateLimit(rateLimiter)(handlerWithAuth);

Because the middleware wraps the entire router externally, any unauthenticated or un-rate-limited route (specifically /health) must be known and checked by EVERY middleware independently.

In src/middleware.ts:

const HEALTH_PATTERN = new URLPattern({ pathname: "/health{/}?" });

export function withAuth(apiToken: string): Middleware {
...
    if (HEALTH_PATTERN.exec(request.url)) {
        return next(request, info);
    }
...
export function withRateLimit(limiter: RateLimiter): Middleware {
...
    if (HEALTH_PATTERN.exec(request.url)) {
        return next(request, info);
    }

And in src/handler.ts:

router.get("/health{/}?", () => { ... });

Friction points:

  1. Duplicated route pattern knowledge: Both withAuth and withRateLimit independently parse the URL and duplicate the /health{/}? pattern. In issue Health endpoint returns 401 for trailing-slash variant /health/ (public probe marked unhealthy) #67, fixing trailing slash support required editing both src/middleware.ts and src/handler.ts.
  2. Violation of middleware single responsibility: Middleware modules designed to validate Bearer tokens or calculate IP rate limits are tightly coupled to the application's URL routing table. Adding any future public endpoint (e.g. /metrics) requires modifying all middleware files.
  3. Inverted dispatch on 404: Unauthenticated requests to non-existent paths return 401 Unauthorized before the router ever runs, hiding 404 Not Found behind authentication.

Test Evidence (/deintrovert-tests)

In tests/handler_test.ts, middleware tests must construct full HTTP request URLs that include /health or non-health endpoints to verify bypass behavior, coupling middleware tests to specific route paths rather than testing auth or rate-limiting invariants directly.

The Deletion Test

Deleting HEALTH_PATTERN from src/middleware.ts removes path-matching complexity from middleware entirely. Middlewares become pure transforms: withAuth only inspects headers; withRateLimit only inspects IP and request timestamps.

Proposed Change

Deepen Router so that routes or route groups can declare middleware:

router.get("/health{/}?", healthHandler); // unauthenticated, unthrottled
router.group([withRateLimit(limiter), withAuth(apiToken)], (r) => {
    r.get("/queues", listQueuesHandler);
    r.post("/enqueue/:queue", enqueueHandler);
    r.get("/dequeue/:queue", dequeueHandler);
    r.get("/peek/:queue", peekHandler);
    r.get("/length/:queue", lengthHandler);
});

Alternatively, have Router match the route first, returning the handler and its associated middleware pipeline.

Benefits

  • Leverage: New endpoints declare their security policy in one place (at route registration) rather than requiring edits across multiple middleware files.
  • Locality: Routing rules and path matching concentrate entirely in src/router.ts and src/handler.ts. Middleware concentrates strictly on auth or rate limiting.
  • Testability: Middleware can be tested in isolation using dummy requests without needing to construct specific URL pathnames matching /health.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentFully specified and ready for an implementation agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions