3.6.4. Middlewares
In this chapter, you’ll learn about middlewares and how to create them.
What is a Middleware?#
A middleware is a function executed when a request is sent to an API Route. It's executed before the route handler function.
Middlewares are used to guard API routes, parse request content types other than application/json
, manipulate request data, and more.
Middleware Types#
There are two types of middlewares:
Type | Description | Example |
---|---|---|
Global Middleware | A middleware that applies to all routes matching a specified pattern. |
|
Route Middleware | A middleware that applies to routes matching a specified pattern and HTTP method(s). | A middleware that applies to all |
These middlewares generally have the same definition and usage, but they differ in the routes they apply to. You'll learn how to create both types in the following sections.
How to Create a Middleware?#
Middlewares of all types are defined in the special file src/api/middlewares.ts
. Use the defineMiddlewares
function from the Medusa Framework to define the middlewares, and export its value.
For example:
The defineMiddlewares
function accepts a middleware configurations object that has the property routes
. routes
's value is an array of middleware route objects, each having the following properties:
matcher
: a string or regular expression indicating the API route path to apply the middleware on. The regular expression must be compatible with path-to-regexp.middlewares
: An array of global and route middleware functions.method
: (optional) By default, a middleware is applied on all HTTP methods for a route. You can specify one or more HTTP methods to apply the middleware to in this option, making it a route middleware.
Test the Middleware#
To test the middleware:
- Start the application:
- Send a request to any API route starting with
/custom
. If you specified an HTTP method in themethod
property, make sure to use that method. - See the following message in the terminal:
When to Use Middlewares#
Middlewares are useful for:
- Protecting API routes to ensure that only authenticated users can access them.
- Validating request query and body parameters.
- Parsing request content types other than
application/json
. - Applying CORS configurations to custom API routes.
Middleware Function Parameters#
The middleware function accepts three parameters:
- A request object of type
MedusaRequest
. - A response object of type
MedusaResponse
. - A function of type
MedusaNextFunction
that executes the next middleware in the stack.
next
function in the middleware. Otherwise, other middlewares and the API route handler won’t execute.For example:
1import { 2 MedusaNextFunction, 3 MedusaRequest, 4 MedusaResponse, 5 defineMiddlewares,6} from "@medusajs/framework/http"7 8export default defineMiddlewares({9 routes: [10 {11 matcher: "/custom*",12 middlewares: [13 (14 req: MedusaRequest, 15 res: MedusaResponse, 16 next: MedusaNextFunction17 ) => {18 console.log("Received a request!", req.body)19 20 next()21 },22 ],23 },24 ],25})
This middleware logs the request body to the terminal, then calls the next
function to execute the next middleware in the stack.
Middleware for Routes with Path Parameters#
To indicate a path parameter in a middleware's matcher
pattern, use the format :{param-name}
.
For example:
This applies a middleware to the routes defined in the file src/api/custom/[id]/route.ts
.
Request URLs with Trailing Backslashes#
A middleware whose matcher
pattern doesn't end with a backslash won't be applied for requests to URLs with a trailing backslash.
For example, consider you have the following middleware:
6} from "@medusajs/framework/http"7 8export default defineMiddlewares({9 routes: [10 {11 matcher: "/custom",12 middlewares: [13 (14 req: MedusaRequest, 15 res: MedusaResponse, 16 next: MedusaNextFunction17 ) => {18 console.log("Received a request!")19 20 next()21 },22 ],23 },24 ],25})
If you send a request to http://localhost:9000/custom
, the middleware will run.
However, if you send a request to http://localhost:9000/custom/
, the middleware won't run.
In general, avoid adding trailing backslashes when sending requests to API routes.
How Are Middlewares Ordered and Applied?#
Middleware and Routes Execution Order#
The Medusa application registers middlewares and API route handlers in the following order, stacking them on top of each other:
- Global middlewares in the following order:
- Global middleware defined in the Medusa's core.
- Global middleware defined in the plugins (in the order the plugins are registered in).
- Global middleware you define in the application.
- Route middlewares in the following order:
- Route middleware defined in the Medusa's core.
- Route middleware defined in the plugins (in the order the plugins are registered in).
- Route middleware you define in the application.
- API routes in the following order:
- API routes defined in the Medusa's core.
- API routes defined in the plugins (in the order the plugins are registered in).
- API routes you define in the application.
Then, when a request is sent to an API route, the stack is executed in order: global middlewares are executed first, then the route middlewares, and finally the route handlers.
For example, consider you have the following middlewares:
1export default defineMiddlewares({2 routes: [3 {4 matcher: "/custom",5 middlewares: [6 (req, res, next) => {7 console.log("Global middleware")8 next()9 },10 ],11 },12 {13 matcher: "/custom",14 method: ["GET"],15 middlewares: [16 (req, res, next) => {17 console.log("Route middleware")18 next()19 },20 ],21 },22 ],23})
When you send a request to /custom
route, the following messages are logged in the terminal:
The global middleware runs first, then the route middleware, and finally the route handler, assuming that it logs the message Hello from custom!
.
Middlewares Sorting#
On top of the previous ordering, Medusa sorts global and route middlewares based on their matcher pattern in the following order:
- Wildcard matchers. For example,
/custom*
. - Regex matchers. For example,
/custom/(products|collections)
. - Static matchers without parameters. For example,
/custom
. - Static matchers with parameters. For example,
/custom/:id
.
For example, if you have the following middlewares:
1export default defineMiddlewares({2 routes: [3 {4 matcher: "/custom/:id",5 middlewares: [/* ... */],6 },7 {8 matcher: "/custom",9 middlewares: [/* ... */],10 },11 {12 matcher: "/custom*",13 method: ["GET"],14 middlewares: [/* ... */],15 },16 {17 matcher: "/custom/:id",18 method: ["GET"],19 middlewares: [/* ... */],20 },21 ],22})
The global middlewares are sorted into the following order before they're registered:
- Global middleware
/custom
. - Global middleware
/custom/:id
.
And the route middlewares are sorted into the following order before they're registered:
- Route middleware
/custom*
. - Route middleware
/custom/:id
.
Then, the middlwares are registered in the order mentioned earlier, with global middlewares first, then the route middlewares.
Overriding Middlewares#
A middleware can not override an existing middleware. Instead, middlewares are added to the end of the middleware stack.
For example, if you define a custom validation middleware, such as validateAndTransformBody
, on an existing route, then both the original and the custom validation middleware will run.
Similarly, if you add an authenticate middleware to an existing route, both the original and the custom authentication middleware will run. So, you can't override the original authentication middleware.
Alternative Solution to Overriding Middlewares#
If you need to change the middlewares applied to a route, you can create a custom API route that executes the same functionality as the original route, but with the middlewares you want.
Learn more in the Override API Routes chapter.