Middleware Architecture
Overview
The Middleware Architecture module is responsible for enabling a flexible and extensible mechanism to enhance and customize the behavior of core SWR hooks. It allows the composition of multiple middleware functions that can intercept, modify, or augment the data fetching lifecycle, cache management, and state updates in a declarative and reusable manner.
This architecture solves the problem of maintaining a clean and modular core while providing advanced features (such as devtools integration, data preloading, subscriptions, or optimistic updates) without cluttering the main hook logic. Middleware can be composed and layered, allowing developers to easily add, remove, or customize functionality in a predictable way.
Core Concepts
Middleware Composition
Middleware functions wrap around the core SWR hook or another middleware-enhanced hook, creating a pipeline through which hook invocations flow. Each middleware has access to the hook's arguments, internal state, and can inject additional hooks or side-effects.
The key idea is to treat middleware as higher-order functions that take a hook and return a new hook with enhanced capabilities.
Built-in Middlewares
The project defines a set of built-in middleware implementations bundled as presets, which provide essential features such as:
Devtools Integration: Middleware to enable synchronization with developer tools for real-time inspection and debugging.
Preloading: Middleware that supports preloading data to reduce waterfall loading effects and improve perceived performance.
These built-in middleware are composed and exported together to be automatically applied or selectively included.
How Middleware Works
Middleware in this system is implemented as a function that takes an SWR hook (useSWR) and returns a new SWR hook wrapping the original. This wrapping hook internally manages the middleware chain by extending the use array in the hook's configuration.
Middleware Application via withMiddleware
The utility function withMiddleware is the primary mechanism to create a middleware-enhanced hook. It normalizes the hook arguments and appends the new middleware into the configuration's use array, effectively layering the middleware.
Simplified Illustration from withMiddleware.ts
export const withMiddleware = (
useSWR: SWRHook,
middleware: Middleware
): SWRHook => {
return <Data = any, Error = any>(...args) => {
const [key, fn, config] = normalize(args)
const uses = (config.use || []).concat(middleware)
return useSWR<Data, Error>(key, fn, { ...config, use: uses })
}
}
The function receives a base
useSWRhook and amiddlewarefunction.It returns a new hook that:
Normalizes its arguments into key, fetcher, and config.
Concatenates the new middleware to any existing middleware in the config.
Calls the original
useSWRwith the updated config containing the combined middleware list.
This design allows middleware to be layered dynamically and composed in a declarative manner.
Interaction with Other Modules
Middleware interacts closely with the core SWR hook implementation and other utility modules:
Core SWR Hook: Middleware decorates or wraps the core hook to extend functionality without modifying the core directly.
Middleware Presets: Bundles of middleware (like
devtoolsandpreload) are composed and exported together frommiddleware-preset.tsfor convenient use and integration.Custom Middleware: Developers can implement their own middleware to inject custom behaviors or side effects into the hook lifecycle.
The middleware chain is respected and executed in sequence, with each middleware able to operate on the SWR hook's arguments and state.
Built-in Middleware Preset
The file middleware-preset.ts exports a predefined array of middleware combining devtools and preloading capabilities:
import { use as devtoolsUse } from './devtools'
import { middleware as preload } from './preload'
export const BUILT_IN_MIDDLEWARE = devtoolsUse.concat(preload)
devtoolsUseis an array of middleware functions related to developer tools integration.preloadis a middleware function that enables preloading features.Both are concatenated to form a single preset middleware array.
This preset can be applied to SWR hooks to automatically include these commonly used enhancements.
Design Patterns and Unique Approaches
Higher-Order Hook Wrapping: Middleware follows a higher-order function pattern, wrapping hooks to augment their behavior transparently.
Composable Middleware Chain: Middleware functions are composed as arrays and concatenated, enabling flexible and dynamic extension.
Declarative Middleware Usage: Middleware can be added via configuration (
usearray) for declarative and manageable enhancement.Normalization of Arguments: To support various argument signatures, middleware uses normalization utilities to standardize inputs before processing.
Separation of Concerns: Middleware isolates feature-specific logic from the core hook, promoting modularity and maintainability.
Mermaid Diagram: Middleware Composition Flow
sequenceDiagram
participant C as Component
participant MH as Middleware-Enhanced Hook
participant MW as Middleware Chain
participant SWR as Core useSWR Hook
C->>MH: Calls hook with key, fetcher, config
MH->>MW: Adds middleware to config.use array
MW->>SWR: Invokes core useSWR with updated config
SWR-->>MW: Returns SWR response (data, error, mutate, etc.)
MW-->>MH: Middleware chain processes response
MH-->>C: Returns final enhanced SWR response
This sequence illustrates how the component calls a hook wrapped by middleware, which composes middleware functions and ultimately invokes the core useSWR hook. The middleware chain can intercept, modify, or extend the response before it reaches the component.
Summary
The Middleware Architecture in this project provides a clean, extensible, and composable pattern to enhance SWR hooks. Through higher-order wrapping and declarative composition, middleware enables modular addition of features like devtools integration, data preloading, and custom behaviors without complicating the core logic. The utility withMiddleware facilitates easy middleware application by normalizing arguments and managing middleware arrays, while built-in middleware presets bundle common enhancements for streamlined use. This design supports flexible feature expansion and maintains separation of concerns across the data fetching lifecycle.