Core Concepts: Middleware

Middleware functions are a fundamental part of building applications with Jetpath. They allow you to execute code before your route handler runs and after it completes, enabling you to implement cross-cutting concerns like logging, authentication, data validation, error handling, and response formatting in a clean, reusable way.


What is Middleware?

Think of middleware as functions that sit in the middle of the request-response cycle. They can:


Defining Middleware

In Jetpath, middleware is typically defined by exporting a special function named MIDDLEWARE_ from a .jet.ts file.

import { type JetMiddleware, type JetContext } from "jetpath";

// Define expected types for plugins if used in middleware // Example: Assuming AuthPlugin and LoggerPlugin types exist type AppPlugins = [typeof AuthPluginInstance, typeof LoggerPluginInstance]; // Define expected type for application state attached to ctx.app type AppState = { user?: { id: string; role: string } };

export const MIDDLEWARE_: JetMiddleware<AppState, AppPlugins> = (ctx: JetContext<AppState, AppPlugins>) => { // ======================== // === PRE-HANDLER CODE === // ======================== // This code runs BEFORE the route handler executes. console.log(--&gt; Request Start: <span class="hljs-subst">${ctx.request.method}</span> <span class="hljs-subst">${ctx.request.url}</span>); const startTime = Date.now(); ctx.set("X-Request-ID", crypto.randomUUID()); // Example: Add request ID header

// Example: Authentication Check (can call plugins) // const isPublic = ctx.request.url.startsWith('/public'); // if (!isPublic) { // const authResult = ctx.plugins.verifyAuth(ctx); // Assuming verifyAuth plugin exists // if (!authResult.authenticated) { // ctx.code = 401; // ctx.throw("Authentication Required"); // Throwing ends cycle here, goes to post-handler error part // } // ctx.app.user = authResult.user; // Attach user to context app state // }

// === RETURN POST-HANDLER FUNCTION === return (ctx: JetContext<AppState, AppPlugins>, err?: Error) => { // ========================= // === POST-HANDLER CODE === // ========================= // This code runs AFTER the route handler finishes OR if an error occurs.

<span class="hljs-keyword">const</span> duration = <span class="hljs-title class_">Date</span>.<span class="hljs-title function_">now</span>() - startTime;
ctx.<span class="hljs-title function_">set</span>(<span class="hljs-string">&quot;X-Response-Time&quot;</span>, <span class="hljs-string">`<span class="hljs-subst">${duration}</span>ms`</span>); <span class="hljs-comment">// Example: Add response time header</span>

<span class="hljs-comment">// --- Error Handling ---</span>
<span class="hljs-keyword">if</span> (err) {
  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">`!!! Request Error: <span class="hljs-subst">${err.message}</span>`</span>, err.<span class="hljs-property">stack</span>);
  <span class="hljs-comment">// Set status code if not already set by ctx.throw or validation</span>
  ctx.<span class="hljs-property">code</span> = ctx.<span class="hljs-property">code</span> &gt;= <span class="hljs-number">400</span> ? ctx.<span class="hljs-property">code</span> : <span class="hljs-number">500</span>;

  <span class="hljs-comment">// Log the error (e.g., using a logger plugin)</span>
  <span class="hljs-comment">// ctx.plugins.logger?.error({ error: err.message, stack: err.stack, code: ctx.code });</span>

  <span class="hljs-comment">// Send a standardized error response</span>
  ctx.<span class="hljs-title function_">send</span>({
    <span class="hljs-attr">error</span>: {
      <span class="hljs-attr">message</span>: ctx.<span class="hljs-property">code</span> &lt; <span class="hljs-number">500</span> ? err.<span class="hljs-property">message</span> : <span class="hljs-string">&quot;Internal Server Error&quot;</span>,
      <span class="hljs-attr">requestId</span>: ctx.<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;X-Request-ID&quot;</span>),
      <span class="hljs-attr">code</span>: ctx.<span class="hljs-property">code</span>,
    },
  });
  <span class="hljs-keyword">return</span>; <span class="hljs-comment">// Stop further processing after sending error response</span>
}

<span class="hljs-comment">// --- 404 Handling ---</span>
<span class="hljs-comment">// Check if a response was already sent by the handler</span>
<span class="hljs-comment">// (Note: Exact check might depend on underlying response object state)</span>
<span class="hljs-keyword">const</span> responseSent = ctx.<span class="hljs-property">response</span>.<span class="hljs-property">status</span> !== <span class="hljs-number">404</span> || ctx.<span class="hljs-property">response</span>.<span class="hljs-property">body</span> != <span class="hljs-literal">null</span>;
<span class="hljs-keyword">if</span> (!responseSent) {
  ctx.<span class="hljs-property">code</span> = <span class="hljs-number">404</span>;
  ctx.<span class="hljs-title function_">send</span>({
    <span class="hljs-attr">error</span>: {
      <span class="hljs-attr">message</span>: <span class="hljs-string">&quot;Not Found&quot;</span>,
      <span class="hljs-attr">requestId</span>: ctx.<span class="hljs-title function_">get</span>(<span class="hljs-string">&quot;X-Request-ID&quot;</span>),
      <span class="hljs-attr">code</span>: <span class="hljs-number">404</span>,
    }
  });
  <span class="hljs-keyword">return</span>; <span class="hljs-comment">// Stop further processing</span>
}

<span class="hljs-comment">// --- Successful Response Logging ---</span>
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`&lt;-- Request End: <span class="hljs-subst">${ctx.request.method}</span> <span class="hljs-subst">${ctx.request.url}</span> <span class="hljs-subst">${ctx.code}</span> <span class="hljs-subst">${duration}</span>ms`</span>);
<span class="hljs-comment">// Log success (e.g., using a logger plugin)</span>
<span class="hljs-comment">// ctx.plugins.logger?.info({ status: ctx.code, duration });</span>

}; };

[Based on middleware structure in tests/app.jet.ts]

Key Points about the Structure:


Execution Flow & Order

  1. Jetpath matches the incoming request to a route handler.
  2. The pre-handler section of the most relevant MIDDLEWARE_ function executes. (See Scoping below).
  3. (If not ended early) The route handler function executes.
  4. The post-handler section (the returned function) of the MIDDLEWARE_ function executes, receiving the context and any error that occurred.

If multiple middleware functions apply (e.g., global and folder-level - see Scoping), the pre-handler sections execute from general to specific, and the post-handler sections execute in the reverse order (specific to general).


Scoping Middleware

(Note: The provided example tests/app.jet.ts only explicitly shows a global middleware defined in the main app file. The following describes common patterns for scoping in file-based frameworks, which Jetpath might implement or could adopt.)

Middleware can potentially be scoped to apply globally or only to specific parts of your API:


Common Use Cases

Middleware is ideal for handling:


Error Handling in Middleware

The post-handler function (return (ctx, err) => { ... }) is the primary place to centralize error handling.


Best Practices


Next Steps