BRAIDGROUP
RESEARCH & DEV
53. Framework Docs

Middleware

Middleware in Junction is a linear pipeline — an ordered array of functions that run before route dispatch. Each middleware receives the Request object and can either short-circuit by returning a Response (e.g., an auth error), or return null to pass control to the next middleware in the chain.

Middleware Signature

Every middleware is a function with this signature:

fn my_middleware(req: Request) -> Response {
    // Return null to continue to the next middleware or route handler
    // Return a Response to short-circuit the pipeline
    return null;
}

Registering Middleware

use_middleware(app, logging_middleware);
use_middleware(app, compression_middleware());
use_middleware(app, session_middleware(app));

Middleware runs in registration order. Use insert_middleware(app, index, middleware) to insert at a specific position.

Built-In Middleware

Logging / Tracing

logging_middleware() records a start timestamp on the request. After the response is generated, a structured log line is emitted containing method, path, status, duration, remote address, user agent, request ID, and trace ID. Log output goes to stderr.

junction.use_middleware(app, junction.logging_middleware());

Compression (gzip / brotli / deflate)

compression_middleware() inspects the Accept-Encoding request header and sets X-Compression on the request. The router applies the chosen encoding to the response body. Images and videos are skipped automatically.

junction.use_middleware(app, junction.compression_middleware());

CORS

Configurable cross-origin resource sharing with preflight handling. See CORS section for full configuration.

let cors_cfg = cors.config_from_object({
    "allowed_origins": ["https://myapp.com", "https://*.subdomain.com"],
    "allowed_methods": ["GET", "POST", "PUT", "DELETE", "PATCH"],
    "allow_credentials": true,
    "max_age": 86400
});
junction.use_middleware(app, cors.cors_middleware(cors_cfg));

CSRF Protection

csrf_middleware(app) validates X-CSRF-Token header or _csrf_token form field against the session store for all non-idempotent methods (POST, PUT, PATCH, DELETE). See Security page for details.

junction.use_middleware(app, junction.csrf_middleware(app));

Helmet Security Headers

Security headers are applied automatically by apply_security_headers() to every response. A configurable variant is available via security_headers_middleware(config).

Rate Limiting

rate_limit_middleware(app, config) implements a sliding-window counter per IP-route pair. Configurable limit and window.

junction.use_middleware(app, junction.rate_limit_middleware(app, {
    "limit": 100,
    "window_ms": 60000
}));

Sessions

session_middleware(app) resolves or creates a session ID from the session_id cookie and attaches session data to req.auth_context["session_data"]. See Sessions page.

Static File Serving

static_middleware(root_dir, opts) serves static files from a directory. Supports SPA fallback, directory listing, MIME detection, and ETag-based caching. Place this middleware early in the pipeline so it short-circuits before the router.

let static_files = junction.static_middleware("./public", {
    "serve_index": true,
    "cache_max_age": 3600
});
junction.use_middleware(app, static_files);

Timeout

timeout_middleware(ms) enforces a per-request deadline by checking a monotonic millisecond timestamp.

junction.use_middleware(app, junction.timeout_middleware(30000));

Request and Response Objects

The Request struct provides:

  • req.method — HTTP method string (GET, POST, etc.)
  • req.path — URL path
  • req.query — Parsed query parameters as object
  • req.params — Path parameters from the router
  • req.headers — Raw headers object
  • req.body — Raw body string
  • req.json — Parsed JSON body (auto-populated for application/json)
  • req.cookies — Parsed cookies
  • req.auth_context — Auth data set by middleware (session, permissions, etc.)
  • req.trace_context — Trace ID for distributed tracing
  • req.uploaded_files — Multipart file uploads
  • req.remote_addr — Client IP address
  • req.request_id — Unique request identifier

The Response struct provides:

  • res.status — HTTP status code
  • res.headers — Response headers object
  • res.body — Response body string
  • res.cookies — Cookies to set via Set-Cookie
  • res.etag — ETag for conditional requests
  • res.last_modified — Last-Modified header value
  • res.stream — Stream handle for chunked responses

Response Helpers

// JSON response with auto-ETag
junction.json({"key": "value"}, 200);

// Plain text
junction.text("Hello", 200);

// HTML
junction.html("<h1>Hello</h1>", 200);

// XML
junction.xml("<doc><item/></doc>", 200);

// Binary bytes
junction.bytes(raw_data, "image/png", 200);

// File response with Content-Type, ETag, Last-Modified
junction.file_response("./public/logo.png", "image/png");

// Stream (chunked transfer)
junction.stream_response(stream_handle, "text/event-stream", 200);

// Redirects
junction.redirect("/new-location");
junction.redirect_permanent("/permanent-move");

// 204 No Content
junction.no_content();

// Error with RFC 9457 problem+json format
junction.error("NOT_FOUND", "Resource not found", 404);

Content Negotiation

Use negotiate_content_type(req, available) to select a response format based on the Accept header. accepts_json(req) is a convenience shorthand.

fn handler(req: Request) -&gt; Response {
    let negotiated = junction.negotiate_content_type(req,
        ["application/json", "text/html", "text/plain"]);
    if (negotiated == "application/json") {
        return junction.json({"msg": "hello"}, 200);
    }
    return junction.text("hello", 200);
}

Conditional Requests

check_conditional(req, last_modified, etag) returns a 304 Not Modified response when the client has a fresh copy. Use check_etag(req, etag) and check_last_modified(req, last_modified) for individual checks.

fn handler(req: Request) -&gt; Response {
    let not_modified = junction.check_conditional(req, "2024-01-01", "abc123");
    if (not_modified != null) { return not_modified; }
    return junction.json({"data": "fresh"}, 200);
}

Cookie Helpers

let res = junction.json({"ok": true}, 200);
junction.set_cookie(res, "token", "abc123", {
    "path": "/",
    "http_only": true,
    "secure": true,
    "same_site": "Lax",
    "max_age": 3600
});
junction.delete_cookie(res, "token", {"path": "/"});

Custom Middleware Example

junction.use_middleware(app, fn(req: Request) -&gt; Response {
    req.request_id = "req_" + std.crypto.random_hex(8);
    req.trace_context = {"trace_id": "trc_" + std.crypto.random_hex(8)};
    return null;
});

junction.use_middleware(app, fn(req: Request) -> Response {
    let start = std.time.now_millis();
    // The middleware cannot intercept the response directly;
    // use X-Start-Time pattern from logging_middleware instead
    req.headers["X-Start-Time"] = std.time.now_iso8601();
    return null;
});