Intercepting Filter

Posted by Dustin Boston .

The Intercepting Filter processes requests and responses before and after the main application logic. It works by defining a chain of filters that handle specific concerns. For example: logging, authentication, input validation, or data transformation. The Filter Manager executes each filter in sequence; This allows for modular and reusable processing steps.

References

Alur, D., Crupi, J., & Malks, D. (2003). Core JSEE patterns: Best practices and design strategies. Myrna Rivera.

Source Code Listing

code.ts

export class CustomRequest extends Request {
  public attributes: Record<string, any> = {};
}

export interface FilterChain {
  doFilter(request: CustomRequest): Promise<void>;
}

export interface Filter {
  doFilter(request: CustomRequest, chain: FilterChain): Promise<void>;
}

export class FilterManager {
  private filters: Filter[] = [];
  private filterChain: FilterChain;

  constructor(filterChain: FilterChain) {
    this.filterChain = filterChain;
  }

  addFilter(filter: Filter): void {
    this.filters.push(filter);
  }

  async doFilter(request: CustomRequest): Promise<void> {
    for (const filter of this.filters) {
      await filter.doFilter(request, this.filterChain);
    }
  }
}

export class SimpleFilterChain implements FilterChain {
  async doFilter(request: CustomRequest): Promise<void> {
    console.log("Attributes", request.attributes);
  }
}

export class JsonFilter implements Filter {
  async doFilter(request: CustomRequest, chain: FilterChain): Promise<void> {
    const contentType = request.headers.get("Content-Type");

    if (contentType && contentType.includes("application/json")) {
      const json = await request.json();
      request.attributes["json"] = json;
    }

    chain.doFilter(request);
  }
}

export class QueryFilter implements Filter {
  async doFilter(request: CustomRequest, chain: FilterChain): Promise<void> {
    const url = new URL(request.url);
    const queryParams = new URLSearchParams(url.search);

    request.attributes["query"] = queryParams;

    chain.doFilter(request);
  }
}

/**
 * @example Start the server
 * bun run server.ts
 *
 * @example
 * curl -X POST "http://localhost:3000/echo?foo=1&bar=2" -H "Content-Type: application/json" -d '{"key": "value"}'
 */
Bun.serve({
  routes: {
    "/*": async (req) => {
      // Wrap request in a custom object
      const customRequest = new CustomRequest(req);

      // Initialize filters and manager
      const filterManager = new FilterManager(new SimpleFilterChain());
      filterManager.addFilter(new QueryFilter());
      filterManager.addFilter(new JsonFilter());

      // Execute filters
      await filterManager.doFilter(customRequest);

      // Return a basic response
      return new Response("Filters executed successfully");
    },
  },
});