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.
Middleware Functions
A middleware function can access up to 4 arguments:
request
object: The HTTP request object.response
object: The HTTP response object.next
: The function that calls the next middleware.
'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
api
orpages
directory. 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
api
orpages
directory. 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
cmsPageView
is 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.ts
will 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].ts
represents a more complex case. This middleware will execute after botha.ts
andb.ts
AND before the middlewaree.ts
. - The middleware
[f]g.ts
will 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.