Middleware System
What is middleware?
Let's examine the diagram below:

In the above flow, a request is triggered from the end-user. This request is received and processed by a series of middleware functions. After all middleware functions are executed, a response object containing the webpage data is sent to the client.
Middleware functions execute during the lifecycle of a request to the server. Each middleware has access to the HTTP request and response for each route (or path) it's attached to. Middleware can be written to perform a variety of tasks. For example, a logging middleware might log all incoming requests to your application, or an authentication middleware might verify an incoming user's credentials.
Let's examine an example of a middleware function:
import { del } from "@evershop/postgres-query-builder";
import { pool } from "@evershop/evershop/lib/postgres";
export default async (request, response, next) => {
try {
const attributeIds = request.body.ids;
await del("attribute")
.where("attribute_id", "IN", attributeIds.split(","))
.execute(pool);
response.json({
data: {},
success: true,
});
} catch (e) {
response.json({
data: {},
message: e.message,
success: false,
});
}
};
EverShop Middleware Types
EverShop has four types of middleware functions:
1. Application-level Middleware
Application-level middleware functions are executed on all requests, including both admin and front store pages, even on 404 pages.
For example, a logging middleware that records information about all requests is an application-level middleware.
In a module, you can create an application-level middleware function by creating a file in the api/global or pages/global folder. For example, if you create a file named logMiddleware.ts in the api/global folder, this middleware will be executed on all API requests.
2. Admin-level Middleware
Admin-level middleware functions are executed on all requests to the admin panel.
For example, an authentication middleware that verifies admin credentials is an admin-level middleware.
In a module, you can create an admin-level middleware function by creating a file in the api/admin/all or pages/admin/all folder. For example, if you create a file named authMiddleware.ts in the pages/admin/all folder, this middleware will be executed on all pages in the admin panel.
3. FrontStore-level Middleware
Similar to admin-level middleware, frontStore-level middleware functions are executed on all requests to the front store.
For example, a customer authentication middleware that verifies customer credentials is a frontStore-level middleware.
To add a frontStore-level middleware, create a file in the api/frontStore/all or pages/frontStore/all folder. For example, if you create a file named authMiddleware.ts in the pages/frontStore/all folder, this middleware will be executed on all pages in the front store.
4. Route-level Middleware
Route-level middleware functions are executed only on specific routes.
For example, a middleware that loads product data will only be executed when a user visits the product view route.
Core Middleware That Runs Before Yours
Before any module or extension middleware is reached, EverShop mounts a fixed chain of Express-level middleware. In order:
- Debug logger — collects per-middleware timings for the
debuglog. - Static file serving —
publicStaticandthemePublicStatic. Requests they serve terminate here. - Rate limiter — per-client-IP request limiting (see below).
- Cookie parser
- Locale resolution — strips a storefront locale prefix into
request.localePathand wraps the rest of the request sotranslate()and resolvers see the locale. - Session — separate admin and storefront cookies; skipped for
/api/**, which is stateless. - URL rewrite lookup — resolves
url_rewriteintorequest.currentRoutewhen no standard route matched. - Dev middleware (development only) — webpack dev server and HMR.
Only after all of that does the router dispatch to your module middleware.
The Rate Limiter
A per-client-IP rate limiter is mounted globally, after static serving and before the session and locale lookups — so a flood is shed with a 429 before it consumes a database connection, and before any module middleware runs. You cannot bypass or reorder it from a module.
Three tiers are applied based on method and path (classified before route matching):
| Tier | Applies to | Limit |
|---|---|---|
page | Storefront and admin pages — anything not matching the tiers below | 300 requests / minute / IP |
api | /api and /api/** | 120 requests / minute / IP |
auth | POSTs to the credential endpoints: /customer/login, /admin/user/login, /api/customers, /api/customers/reset-password, /api/customers/password | 8 requests / 15 minutes / IP |
Exempt from all tiers: any path with a static-asset extension (.js, .css, images, fonts, …), /__webpack_hmr*, *.hot-update*, /backend/, /health and /healthz.
Responses carry the standard RateLimit-* headers so clients can back off, and a rejected request gets Retry-After plus a JSON error envelope on /api/** (plain text on pages).
The limiter is disabled entirely when NODE_ENV=test, so integration suites stay deterministic.
The limits are hardcoded, not operator-configurable — they are a capacity safety net, not a policy knob. They are per IP, so they only stop single-source floods; a distributed flood needs an edge WAF.
The limiter keys on request.ip, which behind a reverse proxy is only correct if Express trust proxy is set. EverShop sets it from the TRUST_PROXY_HOPS environment variable (default 1). If you run behind more than one proxy layer, set it to the real hop count — otherwise every request appears to come from the same address and one visitor can exhaust the limit for everyone.
Middleware Functions
A middleware function can access up to 4 arguments:
requestobject: The HTTP request object.responseobject: The HTTP response object.next: The function that calls the next middleware.error: Passed as the first argument, and only to error-handling middleware — those namederrorHandler(pages) orapiErrorHandler(API). Their signature is(error, request, response, next).
'Passive' Middleware Functions
Let's examine an example:
export default (request, response) => {
// Do something
};
Notice that this middleware function doesn't have the next argument. This type of middleware only handles its own tasks and doesn't call next(). The core of EverShop takes care of calling the next middleware in this case.
'Active' Middleware Functions
Let's examine another example:
export default (request, response, next) => {
// Do something
next();
};
This middleware function includes the next argument. In addition to handling its own tasks, it controls when to call next() based on its own logic. If you don't call next(), the request will be left hanging indefinitely.
Asynchronous Middleware Functions
Here's an example of an asynchronous middleware function:
export default async (request, response) => {
// Do something asynchronous
};
EverShop natively supports asynchronous middleware functions. You can create and export an async middleware function as shown above, and EverShop will handle the rest.
If you want subsequent middleware functions to wait for your middleware to complete, you can use the 'active' middleware function pattern.
Middleware and Routes
Before diving into the relationship between middleware and routes, we recommend reviewing the routing system documentation.
Let's examine the directory structure of a module:

EverShop uses a file-system based middleware approach. It relies on the directory structure to identify, load, and build middleware functions for each route.
In the above image:
- The middleware functions in the yellow box are 'Application-level' middleware. They are direct children of either the
apiorpagesdirectory. If you want a middleware function to execute on all incoming requests, this is where you should place it. - The middleware functions in the blue box are 'Site-level' middleware. They execute on all requests to the front store. They are located in a folder named 'all', which is a direct child of either the
apiorpagesdirectory. This same structure applies to 'Admin-level' middleware as well. - The middleware functions in the purple box are 'Route-level' middleware. They execute only when the route with ID
cmsPageViewis triggered.
Middleware Naming Rules and Dependency Management
When working with middleware, it's crucial to control the order of execution.
In other words, we need to ensure that our middleware functions execute in the expected order.
EverShop uses the file names of middleware for ordering and dependency management. The way you name your middleware function determines when it will be executed.
Middleware Naming Rules
Your middleware file should meet these two requirements:
- It must have '.ts' or '.js' as the file extension.
- It cannot contain whitespace or special characters (except '[', ']', and ',').
Middleware Ordering
Let's examine an example of a module structure:
├── components
├── pages
│ ├── admin
│ └── frontStore
│ └── productView
│ ├── a.ts
│ ├── [a]b.ts
│ ├── [a,b]c[e].ts
│ ├── e.ts
│ ├── [f]g.ts
└── └── route.json
In this example:
- The middleware
[a]b.tswill execute after the middlewarea.ts. This is controlled by including[a]at the beginning of[a]b.ts. This demonstrates the basics of middleware ordering based on file names. - The middleware
[a,b]c[e].tsrepresents a more complex case. This middleware will execute after botha.tsandb.tsAND before the middlewaree.ts. - The middleware
[f]g.tswill never execute because its dependency (f.ts) does not exist (assuming it doesn't exist in any module).
A middleware function will never execute if its dependency doesn't exist or has been removed.
Auto-Injected Dependencies
When EverShop loads middleware files, it automatically injects default dependencies if you haven't explicitly specified them. This ensures your middleware runs at the right point in the pipeline without requiring you to declare common dependencies.
For API Middleware
If your middleware is not context or apiErrorHandler, EverShop automatically adds:
after: ['escapeHtml', 'auth']— Your middleware runs after input sanitization and authentication.before: ['apiResponse']— Your middleware runs before the final API response is sent.
For Page Middleware
If your middleware is not context or errorHandler, EverShop automatically adds:
after: ['auth']— Your middleware runs after authentication.before: ['notFound']— Your middleware runs before the 404 handler.
Overriding Defaults
The two defaults are injected independently, per side. Declaring one side does not suppress the other.
[myDep]myMiddleware.jssetsafter: ['myDep']— but thebeforedefault (apiResponsefor API routes,notFoundfor pages) is still injected.myMiddleware[myDep].jssetsbefore: ['myDep']— theafterdefault (escapeHtml,auth) is still injected.- Only the two-sided form, e.g.
[context]bodyParser[auth].js, suppresses both.
TypeScript and File Extensions
EverShop source code is written in TypeScript (.ts files in the src/ folder). During the build process, TypeScript files are compiled to JavaScript (.js files in the dist/ folder). At runtime, EverShop loads the compiled .js files.
When developing, you write middleware as .ts files. The naming conventions for ordering (bracket syntax) apply to the final .js filename, but since the build preserves the base filename, you can use the bracket syntax directly in your .ts files.