docs: add routing page (#9550)
- Add a new homepage to `book` project for the routing page - Move all main doc pages to be under `/v2/learn` (and added redirects + fixed links across docs) - Other: add admin components to resources dropdown + fixes to search on mobile. Closes DX-955 Preview: https://docs-v2-git-docs-router-page-medusajs.vercel.app/v2
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
import { Details } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Pass Additional Data to Medusa's API Route`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn how to pass additional data in requests to Medusa's API Route.
|
||||
|
||||
## Why Pass Additional Data?
|
||||
|
||||
Some of Medusa's API Routes accept an `additional_data` parameter whose type is an object. The API Route passes the `additional_data` to the workflow, which in turn passes it to its hooks.
|
||||
|
||||
This is useful when you have a link from your custom module to a commerce module, and you want to perform an additional action when a request is sent to an existing API route.
|
||||
|
||||
For example, the [Create Product API Route](!api!/admin#products_postproducts) accepts an `additional_data` parameter. If you have a data model linked to it, you consume the `productsCreated` hook to create a record of the data model using the custom data and link it to the product.
|
||||
|
||||
### API Routes Accepting Additional Data
|
||||
|
||||
<Details summaryContent="API Routes List">
|
||||
|
||||
- Campaigns
|
||||
- [Create Campaign](!api!/admin#campaigns_postcampaigns)
|
||||
- [Update Campaign](!api!/admin#campaigns_postcampaignsid)
|
||||
- Cart
|
||||
- [Create Cart](!api!/store#carts_postcarts)
|
||||
- [Update Cart](!api!/store#carts_postcartsid)
|
||||
- Customers
|
||||
- [Create Customer](!api!/admin#customers_postcustomers)
|
||||
- [Update Customer](!api!/admin#customers_postcustomersid)
|
||||
- [Create Address](!api!/admin#customers_postcustomersidaddresses)
|
||||
- [Update Address](!api!/admin#customers_postcustomersidaddressesaddress_id)
|
||||
- Draft Orders
|
||||
- [Create Draft Order](!api!/admin#draft-orders_postdraftorders)
|
||||
- Orders
|
||||
- [Complete Orders](!api!/admin#orders_postordersidcomplete)
|
||||
- [Cancel Order's Fulfillment](!api!/admin#orders_postordersidfulfillmentsfulfillment_idcancel)
|
||||
- [Create Shipment](!api!/admin#orders_postordersidfulfillmentsfulfillment_idshipments)
|
||||
- [Create Fulfillment](!api!/admin#orders_postordersidfulfillments)
|
||||
- Products
|
||||
- [Create Product](!api!/admin#products_postproducts)
|
||||
- [Update Product](!api!/admin#products_postproductsid)
|
||||
- [Create Product Variant](!api!/admin#products_postproductsidvariants)
|
||||
- [Update Product Variant](!api!/admin#products_postproductsidvariantsvariant_id)
|
||||
- [Create Product Option](!api!/admin#products_postproductsidoptions)
|
||||
- [Update Product Option](!api!/admin#products_postproductsidoptionsoption_id)
|
||||
- Promotions
|
||||
- [Create Promotion](!api!/admin#promotions_postpromotions)
|
||||
- [Update Promotion](!api!/admin#promotions_postpromotionsid)
|
||||
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
## How to Pass Additional Data
|
||||
|
||||
### 1. Specify Validation of Additional Data
|
||||
|
||||
Before passing custom data in the `additional_data` object parameter, you must specify validation rules for the allowed properties in the object.
|
||||
|
||||
To do that, use the middleware route object defined in `src/api/middlewares.ts`.
|
||||
|
||||
For example, create the file `src/api/middlewares.ts` with the following content:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import { z } from "zod"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
method: "POST",
|
||||
matcher: "/admin/products",
|
||||
additionalDataValidator: {
|
||||
brand: z.string().optional(),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
The middleware route object accepts an optional parameter `additionalDataValidator` whose value is an object of key-value pairs. The keys indicate the name of accepted properties in the `additional_data` parameter, and the value is [Zod](https://zod.dev/) validation rules of the property.
|
||||
|
||||
In this example, you indicate that the `additional_data` parameter accepts a `brand` property whose value is an optional string.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to [Zod's documentation](https://zod.dev) for all available validation rules.
|
||||
|
||||
</Note>
|
||||
|
||||
### 2. Pass the Additional Data in a Request
|
||||
|
||||
You can now pass a `brand` property in the `additional_data` parameter of a request to the Create Product API Route.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:9000/admin/products' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer {token}' \
|
||||
--data '{
|
||||
"title": "Product 1",
|
||||
"options": [
|
||||
{
|
||||
"title": "Default option",
|
||||
"values": ["Default option value"]
|
||||
}
|
||||
],
|
||||
"additional_data": {
|
||||
"brand": "Acme"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Make sure to replace the `{token}` in the authorization header with an admin user's authentication token.
|
||||
|
||||
</Note>
|
||||
|
||||
In this request, you pass in the `additional_data` parameter a `brand` property and set its value to `Acme`.
|
||||
|
||||
The `additional_data` is then passed to hooks in the `createProductsWorkflow` used by the API route.
|
||||
|
||||
---
|
||||
|
||||
## Use Additional Data in a Hook
|
||||
|
||||
<Note>
|
||||
|
||||
Learn about workflow hooks in [this guide](../../workflows/workflow-hooks/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
Step functions consuming the workflow hook can access the `additional_data` in the first parameter.
|
||||
|
||||
For example, consider you want to store the data passed in `additional_data` in the product's `metadata` property.
|
||||
|
||||
To do that, create the file `src/workflows/hooks/product-created.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/hooks/product-created.ts"
|
||||
import { StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
createProductsWorkflow.hooks.productsCreated(
|
||||
async ({ products, additional_data }, { container }) => {
|
||||
if (!additional_data.brand) {
|
||||
return
|
||||
}
|
||||
|
||||
const productModuleService = container.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
await productModuleService.upsertProducts(
|
||||
products.map((product) => ({
|
||||
...product,
|
||||
metadata: {
|
||||
...product.metadata,
|
||||
brand: additional_data.brand,
|
||||
},
|
||||
}))
|
||||
)
|
||||
|
||||
return new StepResponse(products, {
|
||||
products,
|
||||
additional_data,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
This consumes the `productsCreated` hook, which runs after the products are created.
|
||||
|
||||
If `brand` is passed in `additional_data`, you resolve the Product Module's main service and use its `upsertProducts` method to update the products, adding the brand to the `metadata` property.
|
||||
|
||||
### Compensation Function
|
||||
|
||||
Hooks also accept a compensation function as a second parameter to undo the actions made by the step function.
|
||||
|
||||
For example, pass the following second parameter to the `productsCreated` hook:
|
||||
|
||||
```ts title="src/workflows/hooks/product-created.ts"
|
||||
createProductsWorkflow.hooks.productsCreated(
|
||||
async ({ products, additional_data }, { container }) => {
|
||||
// ...
|
||||
},
|
||||
async ({ products, additional_data }, { container }) => {
|
||||
if (!additional_data.brand) {
|
||||
return
|
||||
}
|
||||
|
||||
const productModuleService = container.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
await productModuleService.upsertProducts(
|
||||
products
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
This updates the product to their original state before adding the brand to their `metadata` property.
|
||||
@@ -0,0 +1,120 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Handling CORS in API Routes`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about the CORS middleware and how to configure it for custom API routes.
|
||||
|
||||
## CORS Overview
|
||||
|
||||
Cross-Origin Resource Sharing (CORS) allows only configured origins to access your API Routes.
|
||||
|
||||
For example, if you allow only origins starting with `http://localhost:7001` to access your Admin API Routes, other origins accessing those routes get a CORS error.
|
||||
|
||||
### CORS Configurations
|
||||
|
||||
The `storeCors` and `adminCors` properties of Medusa's `http` configuration set the allowed origins for routes starting with `/store` and `/admin` respectively.
|
||||
|
||||
These configurations accept a URL pattern to identify allowed origins.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.ts"
|
||||
module.exports = defineConfig({
|
||||
projectConfig: {
|
||||
http: {
|
||||
storeCors: "http://localhost:8000",
|
||||
adminCors: "http://localhost:7001",
|
||||
// ...
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This allows the `http://localhost:7001` origin to access the Admin API Routes, and the `http://localhost:8000` origin to access Store API Routes.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn more about the CORS configurations in [this resource guide](!resources!/references/medusa-config#http).
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## CORS in Store and Admin Routes
|
||||
|
||||
To disable the CORS middleware for a route, export a `CORS` variable in the route file with its value set to `false`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts" highlights={[["15"]]}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export const GET = (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "[GET] Hello world!",
|
||||
})
|
||||
}
|
||||
|
||||
export const CORS = false
|
||||
```
|
||||
|
||||
This disables the CORS middleware on API Routes at the path `/store/custom`.
|
||||
|
||||
---
|
||||
|
||||
## CORS in Custom Routes
|
||||
|
||||
If you create a route that doesn’t start with `/store` or `/admin`, you must apply the CORS middleware manually. Otherwise, all requests to your API route lead to a CORS error.
|
||||
|
||||
You can do that in the exported middlewares configurations in `src/api/middlewares.ts`.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [["25", "parseCorsOrigins", "A utility function that parses the CORS configurations in `medusa-config.ts`"]]
|
||||
|
||||
```ts title="src/api/middlewares.ts" highlights={highlights} collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { ConfigModule } from "@medusajs/framework/types"
|
||||
import { parseCorsOrigins } from "@medusajs/framework/utils"
|
||||
import cors from "cors"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
matcher: "/custom*",
|
||||
middlewares: [
|
||||
(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) => {
|
||||
const configModule: ConfigModule =
|
||||
req.scope.resolve("configModule")
|
||||
|
||||
return cors({
|
||||
origin: parseCorsOrigins(
|
||||
configModule.projectConfig.http.storeCors
|
||||
),
|
||||
credentials: true,
|
||||
})(req, res, next)
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
This retrieves the configurations exported from `medusa-config.ts` and applies the `storeCors` to routes starting with `/custom`.
|
||||
@@ -0,0 +1,284 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Throwing and Handling Errors`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you'll learn how to throw errors in your Medusa application, how it affects an API route's response, and how to change the default error handler of your Medusa application.
|
||||
|
||||
## Throw MedusaError
|
||||
|
||||
When throwing an error in your API routes, middlewares, workflows, or any customization, throw a `MedusaError`, which is imported from `@medusajs/framework/utils`.
|
||||
|
||||
The Medusa application's API route error handler then wraps your thrown error in a uniform object and returns it in the response.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { MedusaError } from "@medusajs/framework/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
if (!req.query.q) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"The `q` query parameter is required."
|
||||
)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The `MedusaError` class accepts in its constructor two parameters:
|
||||
|
||||
1. The first is the error's type. `MedusaError` has a static property `Types` that you can use. `Types` is an enum whose possible values are explained in the next section.
|
||||
2. The second is the message to show in the error response.
|
||||
|
||||
### Error Object in Response
|
||||
|
||||
The error object returned in the response has two properties:
|
||||
|
||||
- `type`: The error's type.
|
||||
- `message`: The error message, if available.
|
||||
- `code`: A common snake-case code. Its values can be:
|
||||
- `invalid_request_error` for the `DUPLICATE_ERROR` type.
|
||||
- `api_error`: for the `DB_ERROR` type.
|
||||
- `invalid_state_error` for `CONFLICT` error type.
|
||||
- `unknown_error` for any unidentified error type.
|
||||
- For other error types, this property won't be available unless you provide a code as a third parameter to the `MedusaError` constructor.
|
||||
|
||||
### MedusaError Types
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Type</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell className="w-1/5">Status Code</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`DB_ERROR`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Indicates a database error.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`500`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`DUPLICATE_ERROR`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Indicates a duplicate of a record already exists. For example, when trying to create a customer whose email is registered by another customer.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`422`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`INVALID_ARGUMENT` and `UNEXPECTED_STATE`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Indicates an error that occurred due to incorrect arguments or other unexpected state.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`500`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`INVALID_DATA`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Indicates a validation error.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`400`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`UNAUTHORIZED`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Indicates that a user is not authorized to perform an action or access a route.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`401`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`NOT_FOUND`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Indicates that the requested resource, such as a route or a record, isn't found.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`404`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`NOT_ALLOWED`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Indicates that an operation isn't allowed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`400`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`CONFLICT`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Indicates that a request conflicts with another previous or ongoing request. The error message in this case is ignored for a default message.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`409`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`PAYMENT_AUTHORIZATION_ERROR`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Indicates an error has occurred while authorizing a payment.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`422`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
Other error types
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Any other error type results in an `unknown_error` code and message.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`500`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
---
|
||||
|
||||
## Override Error Handler
|
||||
|
||||
The `defineMiddlewares` function used to apply middlewares on routes accepts an `errorHandler` in its object parameter. Use it to override the default error handler for API routes.
|
||||
|
||||
<Note>
|
||||
|
||||
This error handler will also be used for errors thrown in Medusa's API routes and resources.
|
||||
|
||||
</Note>
|
||||
|
||||
For example, create `src/api/middlewares.ts` with the following:
|
||||
|
||||
```ts title="src/api/middlewares.ts" collapsibleLines="1-8" expandMoreLabel="Show Imports"
|
||||
import {
|
||||
defineMiddlewares,
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { MedusaError } from "@medusajs/framework/utils"
|
||||
|
||||
export default defineMiddlewares({
|
||||
errorHandler: (
|
||||
error: MedusaError | any,
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) => {
|
||||
res.status(400).json({
|
||||
error: "Something happened.",
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The `errorHandler` property's value is a function that accepts four parameters:
|
||||
|
||||
1. The error thrown. Its type can be `MedusaError` or any other thrown error type.
|
||||
2. A request object of type `MedusaRequest`.
|
||||
3. A response object of type `MedusaResponse`.
|
||||
4. A function of type MedusaNextFunction that executes the next middleware in the stack.
|
||||
|
||||
This example overrides Medusa's default error handler with a handler that always returns a `400` status code with the same message.
|
||||
@@ -0,0 +1,45 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} HTTP Methods`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn about how to add new API routes for each HTTP method.
|
||||
|
||||
## HTTP Method Handler
|
||||
|
||||
An API route is created for every HTTP method you export a handler function for in a route file.
|
||||
|
||||
Allowed HTTP methods are: `GET`, `POST`, `DELETE`, `PUT`, `PATCH`, `OPTIONS`, and `HEAD`.
|
||||
|
||||
For example, create the file `src/api/hello-world/route.ts` with the following content:
|
||||
|
||||
```ts title="src/api/hello-world/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "[GET] Hello world!",
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "[POST] Hello world!",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
This adds two API Routes:
|
||||
|
||||
- A `GET` route at `http://localhost:9000/hello-world`.
|
||||
- A `POST` route at `http://localhost:9000/hello-world`.
|
||||
@@ -0,0 +1,217 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Middlewares`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
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.
|
||||
|
||||
Middlwares are used to guard API routes, parse request content types other than `application/json`, manipulate request data, and more.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
As Medusa's server is based on Express, you can use any [Express middleware](https://expressjs.com/en/resources/middleware.html).
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Middleware?
|
||||
|
||||
Middlewares are defined in the special file `src/api/middlewares.ts`. Use the `defineMiddlewares` function imported from `@medusajs/medusa` to define the middlewares, and export its value.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
matcher: "/custom*",
|
||||
middlewares: [
|
||||
(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) => {
|
||||
console.log("Received a request!")
|
||||
|
||||
next()
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
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](https://github.com/pillarjs/path-to-regexp).
|
||||
- `middlewares`: An array of middleware functions.
|
||||
|
||||
In the example above, you define a middleware that logs the message `Received a request!` whenever a request is sent to an API route path starting with `/custom`.
|
||||
|
||||
---
|
||||
|
||||
## Test the Middleware
|
||||
|
||||
To test the middleware:
|
||||
|
||||
1. Start the application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
2. Send a request to any API route starting with `/custom`.
|
||||
3. See the following message in the terminal:
|
||||
|
||||
```bash
|
||||
Received a request!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Use Middlewares
|
||||
|
||||
<Note type="success" title="Use middlewares when">
|
||||
|
||||
- You want to protect API routes by a custom condition.
|
||||
- You're modifying the request body.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Middleware Function Parameters
|
||||
|
||||
The middleware function accepts three parameters:
|
||||
|
||||
1. A request object of type `MedusaRequest`.
|
||||
2. A response object of type `MedusaResponse`.
|
||||
3. A function of type `MedusaNextFunction` that executes the next middleware in the stack.
|
||||
|
||||
<Note title="Important">
|
||||
|
||||
You must call the `next` function in the middleware. Otherwise, other middlewares and the API route handler won’t execute.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Middleware for Routes with Path Parameters
|
||||
|
||||
To indicate a path parameter in a middleware's `matcher` pattern, use the format `:{param-name}`.
|
||||
|
||||
For example:
|
||||
|
||||
export const pathParamHighlights = [["11", ":id", "Indicates that the API route accepts an `id` path parameter."]]
|
||||
|
||||
```ts title="src/api/middlewares.ts" collapsibleLines="1-7" expandMoreLabel="Show Imports" highlights={pathParamHighlights}
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
matcher: "/custom/:id",
|
||||
middlewares: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
This applies a middleware to the routes defined in the file `src/api/custom/[id]/route.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Restrict HTTP Methods
|
||||
|
||||
Restrict which HTTP methods the middleware is applied to using the `method` property of the middleware route object.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [["12", "method", "Apply the middleware only on `POST` requests"]]
|
||||
|
||||
```ts title="src/api/middlewares.ts" highlights={highlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
matcher: "/custom*",
|
||||
method: ["POST", "PUT"],
|
||||
middlewares: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
`method`'s value is one or more HTTP methods to apply the middleware to.
|
||||
|
||||
This example applies the middleware only when a `POST` or `PUT` request is sent to an API route path starting with `/custom`.
|
||||
|
||||
---
|
||||
|
||||
## 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:
|
||||
|
||||
```ts collapsibleLines="1-7" expandMoreLabel="Show Imports"
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import type {
|
||||
MedusaNextFunction,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
matcher: "/custom",
|
||||
middlewares: [
|
||||
(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: MedusaNextFunction
|
||||
) => {
|
||||
console.log("Received a request!")
|
||||
|
||||
next()
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,14 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} API Routes Advanced Guides`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In the next chapters, you'll focus more on API routes to learn about topics such as:
|
||||
|
||||
- Creating API routes for different HTTP methods.
|
||||
- Accepting parameters in your API routes.
|
||||
- Formatting response data and headers.
|
||||
- Applying middlewares on API routes.
|
||||
- Validating request body parameters.
|
||||
- Protecting API routes by requiring user authentication.
|
||||
@@ -0,0 +1,155 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} API Route Parameters`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about path, query, and request body parameters.
|
||||
|
||||
## Path Parameters
|
||||
|
||||
To create an API route that accepts a path parameter, create a directory within the route file's path whose name is of the format `[param]`.
|
||||
|
||||
For example, to create an API Route at the path `/hello-world/:id`, where `:id` is a path parameter, create the file `src/api/hello-world/[id]/route.ts` with the following content:
|
||||
|
||||
export const singlePathHighlights = [
|
||||
["11", "req.params.id", "Access the path parameter `id`"]
|
||||
]
|
||||
|
||||
```ts title="src/api/hello-world/[id]/route.ts" highlights={singlePathHighlights}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: `[GET] Hello ${req.params.id}!`,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The `MedusaRequest` object has a `params` property. `params` holds the path parameters in key-value pairs.
|
||||
|
||||
### Multiple Path Parameters
|
||||
|
||||
To create an API route that accepts multiple path parameters, create within the file's path multiple directories whose names are of the format `[param]`.
|
||||
|
||||
For example, to create an API route at `/hello-world/:id/name/:name`, create the file `src/api/hello-world/[id]/name/[name]/route.ts` with the following content:
|
||||
|
||||
export const multiplePathHighlights = [
|
||||
["12", "req.params.id", "Access the path parameter `id`"],
|
||||
["13", "req.params.name", "Access the path parameter `name`"]
|
||||
]
|
||||
|
||||
```ts title="src/api/hello-world/[id]/name/[name]/route.ts" highlights={multiplePathHighlights}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: `[GET] Hello ${
|
||||
req.params.id
|
||||
} - ${req.params.name}!`,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
You access the `id` and `name` path parameters using the `req.params` property.
|
||||
|
||||
---
|
||||
|
||||
## Query Parameters
|
||||
|
||||
You can access all query parameters in the `query` property of the `MedusaRequest` object. `query` is an object of key-value pairs, where the key is a query parameter's name, and the value is its value.
|
||||
|
||||
For example:
|
||||
|
||||
export const queryHighlights = [
|
||||
["11", "req.query.name", "Access the query parameter `name`"],
|
||||
]
|
||||
|
||||
```ts title="src/api/hello-world/route.ts" highlights={queryHighlights}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: `Hello ${req.query.name}`,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The value of `req.query.name` is the value passed in `?name=John`, for example.
|
||||
|
||||
---
|
||||
|
||||
## Request Body Parameters
|
||||
|
||||
The Medusa application parses the body of any request having its `Content-Type` header set to `application/json`. The request body parameters are set in the `MedusaRequest`'s `body` property.
|
||||
|
||||
For example:
|
||||
|
||||
export const bodyHighlights = [
|
||||
["11", "HelloWorldReq", "Specify the type of the request body parameters."],
|
||||
["15", "req.body.name", "Access the request body parameter `name`"],
|
||||
]
|
||||
|
||||
```ts title="src/api/hello-world/route.ts" highlights={bodyHighlights}
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
type HelloWorldReq = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest<HelloWorldReq>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: `[POST] Hello ${req.body.name}!`,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
In this example, you use the `name` request body parameter to create the message in the returned response.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The `MedusaRequest` type accepts a type argument that indicates the type of the request body. This is useful for auto-completion and to avoid typing errors.
|
||||
|
||||
</Note>
|
||||
|
||||
To test it out, send the following request to your Medusa application:
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:9000/hello-world' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "John"
|
||||
}'
|
||||
```
|
||||
|
||||
This returns the following JSON object:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "[POST] Hello John!"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,182 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Protected Routes`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to create protected routes.
|
||||
|
||||
## What is a Protected Route?
|
||||
|
||||
A protected route is a route that requires requests to be user-authenticated before performing the route's functionality. Otherwise, the request fails, and the user is prevented access.
|
||||
|
||||
---
|
||||
|
||||
## Default Protected Routes
|
||||
|
||||
Medusa applies an authentication guard on routes starting with `/admin`, including custom API routes.
|
||||
|
||||
Requests to `/admin` must be user-authenticated to access the route.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Refer to the API Reference for [Admin](!api!/admin#authentication) and [Store](!api!/store#authentication) authentication methods.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Protect Custom API Routes
|
||||
|
||||
To protect custom API Routes to only allow authenticated customer or admin users, use the `authenticate` middleware imported from `@medusajs/medusa`.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
[
|
||||
"10",
|
||||
"authenticate",
|
||||
"Only authenticated admin users can access routes starting with `/custom/admin`",
|
||||
],
|
||||
[
|
||||
"14",
|
||||
"authenticate",
|
||||
"Only authenticated customers can access routes starting with `/custom/customers`",
|
||||
],
|
||||
]
|
||||
|
||||
```ts title="src/api/middlewares.ts" highlights={highlights}
|
||||
import {
|
||||
defineMiddlewares,
|
||||
authenticate,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
matcher: "/custom/admin*",
|
||||
middlewares: [authenticate("user", ["session", "bearer", "api-key"])],
|
||||
},
|
||||
{
|
||||
matcher: "/custom/customer*",
|
||||
middlewares: [authenticate("customer", ["session", "bearer"])],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
The `authenticate` middleware function accepts three parameters:
|
||||
|
||||
1. The type of user authenticating. Use `user` for authenticating admin users, and `customer` for authenticating customers. You can also pass `*` to allow all types of users.
|
||||
2. An array of the types of authentication methods allowed. Both `user` and `customer` scopes support `session` and `bearer`. The `admin` scope also supports the `api-key` authentication method.
|
||||
3. An optional object of configurations accepting the following property:
|
||||
- `allowUnauthenticated`: (default: `false`) A boolean indicating whether authentication is required. For example, you may have an API route where you want to access the logged-in customer if available, but guest customers can still access it too.
|
||||
|
||||
---
|
||||
|
||||
## Authentication Opt-Out
|
||||
|
||||
To disable the authentication guard on custom routes under the `/admin` path prefix, export an `AUTHENTICATE` variable in the route file with its value set to `false`.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/admin/custom/route.ts" highlights={[["15"]]}
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "Hello",
|
||||
})
|
||||
}
|
||||
|
||||
export const AUTHENTICATE = false
|
||||
```
|
||||
|
||||
Now, any request sent to the `/admin/custom` API route is allowed, regardless if the admin user is authenticated.
|
||||
|
||||
---
|
||||
|
||||
## Authenticated Request Type
|
||||
|
||||
To access the authentication details in an API route, such as the logged-in user's ID, set the type of the first request parameter to `AuthenticatedMedusaRequest`. It extends `MedusaRequest`.
|
||||
|
||||
The `auth_context.actor_id` property of `AuthenticatedMedusaRequest` holds the ID of the authenticated user or customer. If there isn't any authenticated user or customer, `auth_context` is `undefined`.
|
||||
|
||||
<Note>
|
||||
|
||||
If you opt-out of authentication in a route as mentioned in the [previous section](#authentication-opt-out), you can't access the authenticated user or customer anymore. Use the [authenticate middleware](#protect-custom-api-routes) instead.
|
||||
|
||||
</Note>
|
||||
|
||||
### Retrieve Logged-In Customer's Details
|
||||
|
||||
You can access the logged-in customer’s ID in all API routes starting with `/store` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts" highlights={[["19", "req.auth_context.actor_id", "Access the logged-in customer's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { ICustomerModuleService } from "@medusajs/framework/types"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
if (req.auth_context?.actor_id) {
|
||||
// retrieve customer
|
||||
const customerModuleService: ICustomerModuleService = req.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
const customer = await customerModuleService.retrieveCustomer(
|
||||
req.auth_context.actor_id
|
||||
)
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In this example, you resolve the Customer Module's main service, then use it to retrieve the logged-in customer, if available.
|
||||
|
||||
### Retrieve Logged-In Admin User's Details
|
||||
|
||||
You can access the logged-in admin user’s ID in all API Routes starting with `/admin` using the `auth_context.actor_id` property of the `AuthenticatedMedusaRequest` object.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/admin/custom/route.ts" highlights={[["17", "req.auth_context.actor_id", "Access the logged-in admin user's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { IUserModuleService } from "@medusajs/framework/types"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const userModuleService: IUserModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
|
||||
const user = await userModuleService.retrieveUser(
|
||||
req.auth_context.actor_id
|
||||
)
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In the route handler, you resolve the User Module's main service, then use it to retrieve the logged-in admin user.
|
||||
@@ -0,0 +1,121 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} API Route Response`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn how to send a response in your API route.
|
||||
|
||||
## Send a JSON Response
|
||||
|
||||
To send a JSON response, use the `json` method of the `MedusaResponse` object passed as the second parameter of your API route handler.
|
||||
|
||||
For example:
|
||||
|
||||
export const jsonHighlights = [
|
||||
["7", "json", "Return a JSON object."]
|
||||
]
|
||||
|
||||
```ts title="src/api/custom/route.ts" highlights={jsonHighlights}
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "Hello, World!",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
This API route returns the following JSON object:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello, World!"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Set Response Status Code
|
||||
|
||||
By default, setting the JSON data using the `json` method returns a response with a `200` status code.
|
||||
|
||||
To change the status code, use the `status` method of the `MedusaResponse` object.
|
||||
|
||||
For example:
|
||||
|
||||
export const statusHighlight = [
|
||||
["7", "status", "Set the response code to `201`."]
|
||||
]
|
||||
|
||||
```ts title="src/api/custom/route.ts" highlights={statusHighlight}
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.status(201).json({
|
||||
message: "Hello, World!",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The response of this API route has the status code `201`.
|
||||
|
||||
---
|
||||
|
||||
## Change Response Content Type
|
||||
|
||||
To return response data other than a JSON object, use the `writeHead` method of the `MedusaResponse` object. It allows you to set the response headers, including the content type.
|
||||
|
||||
For example, to create an API route that returns an event stream:
|
||||
|
||||
export const streamHighlights = [
|
||||
["7", "writeHead", "Set the response's headers."],
|
||||
["7", "200", "Set the status code."],
|
||||
["8", `"Content-Type"`, "Set the response's content type."],
|
||||
["13", "interval", "Simulate stream data using an interval"],
|
||||
["14", "write", "Write stream data."],
|
||||
["17", "on", "Stop the stream when the request is terminated."]
|
||||
]
|
||||
|
||||
```ts highlights={streamHighlights}
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
})
|
||||
|
||||
const interval = setInterval(() => {
|
||||
res.write("Streaming data...\n")
|
||||
}, 3000)
|
||||
|
||||
req.on("end", () => {
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The `writeHead` method accepts two parameters:
|
||||
|
||||
1. The first one is the response's status code.
|
||||
2. The second is an object of key-value pairs to set the headers of the response.
|
||||
|
||||
This API route opens a stream by setting the `Content-Type` in the header to `text/event-stream`. It then simulates a stream by creating an interval that writes the stream data every three seconds.
|
||||
|
||||
---
|
||||
|
||||
## Do More with Responses
|
||||
|
||||
The `MedusaResponse` type is based on [Express's Response](https://expressjs.com/en/api.html#res). Refer to their API reference for other uses of responses.
|
||||
@@ -0,0 +1,135 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Request Body Parameter Validation`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn how to validate request body parameters in your custom API route.
|
||||
|
||||
## Example Scenario
|
||||
|
||||
Consider you're creating a `POST` API route at `/custom`. It accepts two paramters `a` and `b` that are required numbers, and returns their sum.
|
||||
|
||||
The next steps explain how to add validation to this API route, as an example.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create Zod Schema
|
||||
|
||||
Medusa uses [Zod](https://zod.dev/) to validate the body parameters of an incoming request.
|
||||
|
||||
To use Zod to validate your custom schemas, create a `validators.ts` file in any `src/api` subfolder. This file holds Zod schemas for each of your API routes.
|
||||
|
||||
For example, create the file `src/api/custom/validators.ts` with the following content:
|
||||
|
||||
```ts title="src/api/custom/validators.ts"
|
||||
import { z } from "zod"
|
||||
|
||||
export const PostStoreCustomSchema = z.object({
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
})
|
||||
```
|
||||
|
||||
The `PostStoreCustomSchema` variable is a Zod schema that indicates the request body is valid if:
|
||||
|
||||
1. It's an object.
|
||||
2. It has a property `a` that is a required number.
|
||||
3. It has a property `b` that is a required number.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Add Validation Middleware
|
||||
|
||||
To use this schema for validating the body parameters of requests to `/custom`, use the `validateAndTransformBody` middleware provided by `@medusajs/framework/utils`. It accepts the Zod schema as a parameter.
|
||||
|
||||
For example, create the file `src/api/middlewares.ts` with the following content:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import {
|
||||
validateAndTransformBody,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { PostStoreCustomSchema } from "./custom/validators"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
matcher: "/custom",
|
||||
method: "POST",
|
||||
middlewares: [
|
||||
validateAndTransformBody(PostStoreCustomSchema),
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
This applies the `validateAndTransformBody` middleware on `POST` requests to `/custom`. It uses the `PostStoreCustomSchema` as the validation schema.
|
||||
|
||||
### How the Validation Works
|
||||
|
||||
If a request's body parameters don't pass the validation, the `validateAndTransformBody` middleware throws an error indicating the validation errors.
|
||||
|
||||
If a request's body parameters are validated successfully, the middleware sets the validated body parameters in the `validatedBody` property of `MedusaRequest`.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Use Validated Body in API Route
|
||||
|
||||
In your API route, consume the validated body using the `validatedBody` property of `MedusaRequest`.
|
||||
|
||||
For example, create the file `src/api/custom/route.ts` with the following content:
|
||||
|
||||
export const routeHighlights = [
|
||||
["5", "PostStoreCustomSchemaType", "Infer the request body type from the schema to pass it as a type parameter to `MedusaRequest`."],
|
||||
["14", "", "Access the body parameters using `validatedBody`."]
|
||||
]
|
||||
|
||||
```ts title="src/api/custom/route.ts" highlights={routeHighlights}
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { z } from "zod"
|
||||
import { PostStoreCustomSchema } from "./validators"
|
||||
|
||||
type PostStoreCustomSchemaType = z.infer<
|
||||
typeof PostStoreCustomSchema
|
||||
>
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest<PostStoreCustomSchemaType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
sum: req.validatedBody.a + req.validatedBody.b,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
In the API route, you use the `validatedBody` property of `MedusaRequest` to access the values of the `a` and `b` properties.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
To pass the request body's type as a type parameter to `MedusaRequest`, use Zod's `infer` type that accepts the type of a schema as a parameter.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
To test out the validation, send a `POST` request to `/custom`. You can try sending incorrect request body parameters.
|
||||
|
||||
For example, if you omit the `a` parameter, you'll receive a `400` response code with the following response data:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "invalid_data",
|
||||
"message": "Invalid request: Field 'a' is required"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Learn More About Validation Schemas
|
||||
|
||||
To see different examples and learn more about creating a validation schema, refer to [Zod's documentation](https://zod.dev).
|
||||
Reference in New Issue
Block a user