docs: revise extend models (features) (#10429)
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Create Links between Brand and Product Records`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note title="Example Chapter">
|
||||
|
||||
This chapter covers how to create a link between the records of the `Brand` and `Product` data models as a step of the ["Extend Models" chapter](../page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## What is the Remote Link?
|
||||
|
||||
The remote link is a class with utility methods to manage links between data models' records.
|
||||
|
||||
It’s registered in the Medusa container under the `ContainerRegistrationKeys.REMOTE_LINK` (`remoteLink`) registration name.
|
||||
|
||||
### Example: Create Link with Remote Link
|
||||
|
||||
For example, consider the following step:
|
||||
|
||||
export const stepHighlights = [
|
||||
["19", "resolve", "Resolve the remote link."],
|
||||
["23", "create", "Create a link between two records."]
|
||||
]
|
||||
|
||||
```ts highlights={stepHighlights} collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
Modules,
|
||||
ContainerRegistrationKeys,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { BRAND_MODULE } from "../../modules/brand"
|
||||
|
||||
type LinkProductToBrandStepInput = {
|
||||
productId: string
|
||||
brandId: string
|
||||
}
|
||||
|
||||
export const linkProductToBrandStep = createStep(
|
||||
"link-product-to-brand",
|
||||
async ({ productId, brandId }: LinkProductToBrandStepInput, { container }) => {
|
||||
const remoteLink = container.resolve(
|
||||
ContainerRegistrationKeys.REMOTE_LINK
|
||||
)
|
||||
|
||||
remoteLink.create({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: productId,
|
||||
},
|
||||
[BRAND_MODULE]: {
|
||||
brand_id: brandId,
|
||||
},
|
||||
})
|
||||
|
||||
return new StepResponse(undefined, {
|
||||
productId,
|
||||
brandId,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In this step, you resolve the remote link, then use its `create` method to create a link between product and brand records.
|
||||
|
||||
The `create` method accepts as a parameter an object whose properties are the names of each module, and the value is an object.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Use the `Modules` enum imported from `@medusajs/framework/utils` to for the commerce module's names.
|
||||
|
||||
</Note>
|
||||
|
||||
The value object has a property, which is the name of the data model (as specified in `model.define`'s first parameter) followed by `_id`, and its value is the ID of the record to link.
|
||||
|
||||
### Dismiss Link in Compensation
|
||||
|
||||
The above step can have the following compensation function that dismisses the link between the records:
|
||||
|
||||
export const compensationHighlights = [
|
||||
["4", "resolve", "Resolve the remote link."],
|
||||
["8", "dismiss", "Create a link between two records."]
|
||||
]
|
||||
|
||||
```ts highlights={compensationHighlights}
|
||||
export const linkProductToBrandStep = createStep(
|
||||
// ...
|
||||
async ({ productId, brandId }, { container }) => {
|
||||
const remoteLink = container.resolve(
|
||||
ContainerRegistrationKeys.REMOTE_LINK
|
||||
)
|
||||
|
||||
remoteLink.dismiss({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: productId,
|
||||
},
|
||||
[BRAND_MODULE]: {
|
||||
brand_id: brandId,
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The `dismiss` method removes the link to dismiss between two records. Its parameter is the same as that of the `create` method.
|
||||
|
||||
---
|
||||
|
||||
## Next Step: Extend Create Product API Route
|
||||
|
||||
In the next step, you'll extend the Create Product API route to allow passing a brand ID, and link a product to a brand.
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Define Link Between a Brand and a Product`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note title="Example Chapter">
|
||||
|
||||
This chapter covers how to define a link between the `Brand` and `Product`data models as a step of the ["Extend Models" chapter](../page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## 1. Define the Link Between Product and Brand
|
||||
|
||||
<Prerequisites
|
||||
items={[
|
||||
{
|
||||
text: "Brand Module having a Brand data model",
|
||||
link: "/learn/customization/custom-features/module"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
Links are defined in a TypeScript or JavaScript file under the `src/links` directory. The file defines and exports the link using the `defineLink` function imported from `@medusajs/framework/utils`.
|
||||
|
||||
So, create the file `src/links/product-brand.ts` with the following content:
|
||||
|
||||
export const highlights = [
|
||||
["7", "linkable", "Special `linkable` property that holds the linkable data models of `ProductModule`."],
|
||||
["10", "linkable", "Special `linkable` property that holds the linkable data models of `BrandModule`."],
|
||||
]
|
||||
|
||||
```ts title="src/links/product-brand.ts" highlights={highlights}
|
||||
import BrandModule from "../modules/brand"
|
||||
import ProductModule from "@medusajs/medusa/product"
|
||||
import { defineLink } from "@medusajs/framework/utils"
|
||||
|
||||
export default defineLink(
|
||||
{
|
||||
linkable: ProductModule.linkable.product,
|
||||
isList: true,
|
||||
},
|
||||
BrandModule.linkable.brand
|
||||
)
|
||||
```
|
||||
|
||||
The `defineLink` function accepts two parameters, each specifying the link configurations of each data model.
|
||||
|
||||
Modules have a special `linkable` property that holds the data models' link configurations.
|
||||
|
||||
`defineLink` accepts for each parameter either:
|
||||
|
||||
- The data model's link configuration;
|
||||
- Or an object that has two properties:
|
||||
- `linkable`: the link configuration of the data model.
|
||||
- `isList`: Whether many records of the data model can be linked to the other model.
|
||||
|
||||
So, in the above code snippet, you define a link between the `Product` and `Brand` data models. Since `isList` is enabled on the product's side, a brand can be associated with multiple products.
|
||||
|
||||
---
|
||||
|
||||
## 2. Sync the Link to the Database
|
||||
|
||||
To reflect your link in the database, run the `db:sync-links` command:
|
||||
|
||||
```bash
|
||||
npx medusa db:sync-links
|
||||
```
|
||||
|
||||
This creates a table for the link in the database. The table stores the IDs of linked brand and product records.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
You can also use the `db:migrate` command, which both runs the migrations and syncs the links.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Next: Link Brand and Product Records
|
||||
|
||||
In the next chapter, you'll learn how to associate brand and product records by creating a link between them.
|
||||
@@ -0,0 +1,228 @@
|
||||
import { Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Extend Create Product API Route`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note title="Example Chapter">
|
||||
|
||||
This chapter covers how to extend the Create Product API route to link a product to a brand as a step of the ["Extend Models" chapter](../page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Additional Data in API Routes
|
||||
|
||||
Some API routes, including the [Create Product API route](!api!/admin#products_postproducts), accept an `additional_data` request body parameter.
|
||||
|
||||
It's useful when you want to pass custom data, such as the brand ID, then perform an action based on this data, such as link the brand to the product.
|
||||
|
||||
---
|
||||
|
||||
## 1. Allow Passing the Brand ID in Additional Data
|
||||
|
||||
<Prerequisites
|
||||
items={[
|
||||
{
|
||||
text: "Brand Module",
|
||||
link: "/learn/customization/custom-features/module"
|
||||
},
|
||||
{
|
||||
text: "Defined link between the Brand and Product data models.",
|
||||
link: "/learn/customization/extend-features/define-link"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
Before passing custom properties in the `additional_data` parameter, you add the property to `additional_data`'s validation rules.
|
||||
|
||||
Create the file `src/api/middlewares.ts`, which is a special file that defines middlewares or validation rules of custom properties passed in the `additional_data` parameter:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import { z } from "zod"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
matcher: "/admin/products",
|
||||
method: ["POST"],
|
||||
additionalDataValidator: {
|
||||
brand_id: z.string().optional(),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
You use [Zod](https://zod.dev/) to add a validation rule to the `additional_data` parameter indicating that it can include a `brand_id` property of type string.
|
||||
|
||||
### defineMiddleware Parameters
|
||||
|
||||
The `defineMiddlewares` function accepts an object having a `routes` property. Its 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. It must be compatible with [path-to-regexp](https://github.com/pillarjs/path-to-regexp).
|
||||
- `method`: An array of HTTP method to apply the middleware or additional data validation to. If not supplied, it's applied to all HTTP methods.
|
||||
- `additionalDataValidator`: An object of key-value pairs defining the validation rules for custom properties using [Zod](https://zod.dev/).
|
||||
|
||||
---
|
||||
|
||||
## 2. Link Brand to Product using Workflow Hook
|
||||
|
||||
A workflow hook is a point in a workflow where you can inject a step to perform a custom functionality. This is useful to perform custom action in an API route's workflow.
|
||||
|
||||
The [createProductsWorkflow](!resources!/references/medusa-workflows/createProductsWorkflow) used in the Create Product API route has a `productsCreated` hook that runs after the product is created.
|
||||
|
||||
So, to consume the `productsCreated` hook, create the file `src/workflows/hooks/created-product.ts` with the following content:
|
||||
|
||||
export const hookHighlights = [
|
||||
["7", "productsCreated", "Access the hook in the `hooks` property."],
|
||||
["9", "", "Only proceed if the brand ID is passed in the additional data."],
|
||||
["18", "retrieveBrand", "Try to retrieve the brand to ensure it exists."],
|
||||
["27", "links", "Define an array to store the links in."],
|
||||
["31", "push", "Add a link to be created."],
|
||||
["41", "create", "Create the links."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hooks/created-product.ts" highlights={hookHighlights}
|
||||
import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
|
||||
import { StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules, ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
||||
import { BRAND_MODULE } from "../../modules/brand"
|
||||
import BrandModuleService from "../../modules/brand/service"
|
||||
|
||||
createProductsWorkflow.hooks.productsCreated(
|
||||
(async ({ products, additional_data }, { container }) => {
|
||||
if (!additional_data?.brand_id) {
|
||||
return new StepResponse([], [])
|
||||
}
|
||||
|
||||
// check that brand exists
|
||||
const brandModuleService: BrandModuleService = container.resolve(
|
||||
BRAND_MODULE
|
||||
)
|
||||
// if the brand doesn't exist, an error is thrown.
|
||||
await brandModuleService.retrieveBrand(additional_data.brand_id as string)
|
||||
|
||||
const remoteLink = container.resolve(
|
||||
ContainerRegistrationKeys.REMOTE_LINK
|
||||
)
|
||||
const logger = container.resolve(
|
||||
ContainerRegistrationKeys.LOGGER
|
||||
)
|
||||
|
||||
const links = []
|
||||
|
||||
// link products to brands
|
||||
for (const product of products) {
|
||||
links.push({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: product.id,
|
||||
},
|
||||
[BRAND_MODULE]: {
|
||||
brand_id: additional_data.brand_id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await remoteLink.create(links)
|
||||
|
||||
logger.info("Linked brand to products")
|
||||
|
||||
return new StepResponse(links, links)
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
Workflows have a special `hooks` property to access its hooks and consume them. Each hook, such as `productCreated`, accept a step function as a parameter.
|
||||
|
||||
In the step, if a brand ID is passed in `additional_data` and the brand exists, you create a link between each product and the brand.
|
||||
|
||||
### Dismiss Links in Compensation
|
||||
|
||||
You can pass as a second parameter of the hook a compensation function that undoes what the step did.
|
||||
|
||||
Add the following compensation function as a second parameter:
|
||||
|
||||
```ts title="src/workflows/hooks/created-product.ts"
|
||||
createProductsWorkflow.hooks.productsCreated(
|
||||
// ...
|
||||
(async (links, { container }) => {
|
||||
if (!links.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const remoteLink = container.resolve(
|
||||
ContainerRegistrationKeys.REMOTE_LINK
|
||||
)
|
||||
|
||||
await remoteLink.dismiss(links)
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
In the compensation function, you dismiss the links created by the step using the `dismiss` method of the remote link.
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
To test it out, first, retrieve the authentication token of your admin user by sending a `POST` request to `/auth/user/emailpass`:
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:9000/auth/user/emailpass' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "admin@medusa-test.com",
|
||||
"password": "supersecret"
|
||||
}'
|
||||
```
|
||||
|
||||
Make sure to replace the email and password with your user's credentials.
|
||||
|
||||
Then, send a `POST` request to `/admin/products` to create a product, and pass in the `additional_data` parameter a brand's ID:
|
||||
|
||||
```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_id": "01J7AX9ES4X113HKY6C681KDZ2J"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Make sure to replace the `{token}` in the Authorization header with the token received from the previous request.
|
||||
|
||||
</Note>
|
||||
|
||||
In the request body, you pass in the `additional_data` parameter a `brand_id`.
|
||||
|
||||
The request creates a product and returns it.
|
||||
|
||||
In the Medusa application's logs, you'll find the message `Linked brand to products`, indicating that the workflow hook handler ran and linked the brand to the products.
|
||||
|
||||
---
|
||||
|
||||
## Workflows and API Routes References
|
||||
|
||||
Medusa exposes hooks in many of its workflows that you can consume to add custom logic.
|
||||
|
||||
The [Store](!api!/store) and [Admin](!api!/admin) API references indicate what workflows are used in each API routes. By clicking on the workflow, you access the [workflow's reference](!resources!/medusa-workflows-reference) where you can see the hooks available in the workflow.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps: Query Linked Records
|
||||
|
||||
In the next chapter, you'll learn how to query the brand linked to a product.
|
||||
@@ -0,0 +1,25 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Extend Core Commerce Features`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In the upcoming chapters, you'll learn about the concepts and tools to extend Medusa's core commerce features.
|
||||
|
||||
In other commerce platforms, you extend core features and models through hacky workarounds that can introduce unexpected issues and side effects across the platform. It also makes your application difficult to maintain and upgrade in the long run.
|
||||
|
||||
Medusa's framework and orchestration tools mitigate these issues while supporting all your customization needs:
|
||||
|
||||
- [Module Links](../../advanced-development/module-links/page.mdx): Link data models of different modules without building direct dependencies, ensuring that the Medusa application integrates your modules without side effects.
|
||||
- [Workflow Hooks](../../advanced-development/workflows/workflow-hooks/page.mdx): inject custom functionalities into a workflow at predefined points, called hooks. This allows you to perform custom actions as a part of a core workflow without hacky workarounds.
|
||||
- [Additional Data in API Routes](../../advanced-development/api-routes/additional-data/page.mdx): Configure core API routes to accept request parameters relevant to your customizations. These parameters are passed to the underlying workflow's hooks, where you can manage your custom data as part of an existing flow.
|
||||
|
||||
---
|
||||
|
||||
## Next Chapters: Link Brands to Products Example
|
||||
|
||||
The next chapters explain how to use the tools mentioned above with step-by-step guides. You'll continue with the [brands example from the previous chapters](../custom-features/page.mdx) to:
|
||||
|
||||
- Link brands from the custom [Brand Module](../custom-features/module/page.mdx) to products from Medusa's [Product Module](!resources!/commerce-modules/product).
|
||||
- Extend the core product-creation workflow and the API route that uses it to allow setting the brand of a newly created product.
|
||||
- Retrieve a product's associated brand's details.
|
||||
@@ -0,0 +1,183 @@
|
||||
import { Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Retrieve Brand linked to Product using Query`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note title="Example Chapter">
|
||||
|
||||
This chapter covers how to retrieve the brand linked to a product using Query as a step of the ["Extend Models" chapter](../page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## What is Query?
|
||||
|
||||
Query is a utility that retrieves data across modules and their links. It’s registered in the Medusa container under the `ContainerRegistrationKeys.QUERY` (`query`) registration name.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Brand of Product API Route
|
||||
|
||||
<Prerequisites
|
||||
items={[
|
||||
{
|
||||
text: "Brand Module",
|
||||
link: "/learn/customization/custom-features/module"
|
||||
},
|
||||
{
|
||||
text: "Defined link between the Brand and Product data models.",
|
||||
link: "/learn/customization/extend-features/define-link"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
You'll create an API route that retrieves the brand of a product. You'll use this in a later chapter.
|
||||
|
||||
Create the file `src/api/admin/products/[id]/brand/route.ts` with the following content:
|
||||
|
||||
export const highlights = [
|
||||
["13", "resolve", "Resolve Query from the Medusa Container."],
|
||||
["17", "graph", "Run a query to retrieve a product by its ID and its brand."],
|
||||
["18", "entity", "The name of the model to query."],
|
||||
["19", "fields", "The fields and relations to retrieve."],
|
||||
["20", "filters", "The filters to apply on the retrieved data."]
|
||||
]
|
||||
|
||||
```ts title="src/api/admin/products/[id]/brand/route.ts" highlights={highlights} collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
} from "@medusajs/framework/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const query = req.scope.resolve(
|
||||
ContainerRegistrationKeys.QUERY
|
||||
)
|
||||
|
||||
const { data: [product] } = await query.graph({
|
||||
entity: "product",
|
||||
fields: ["brand.*"],
|
||||
filters: {
|
||||
id: req.params.id,
|
||||
},
|
||||
})
|
||||
|
||||
res.json({ brand: product.brand })
|
||||
}
|
||||
```
|
||||
|
||||
In this example, you retrieve a product by its ID with its brand, and return the brand in the response.
|
||||
|
||||
### query.graph Parameters
|
||||
|
||||
The `graph` method of Query runs a query to retrieve data. It accepts an object having the following properties:
|
||||
|
||||
- `entity`: The data model's name as specified in the first parameter of `model.define`.
|
||||
- `fields`: An array of properties and relations to retrieve. You can pass:
|
||||
- A property's name, such as `id`.
|
||||
- A relation or linked model's name, such as `brand`. You suffix the name with `.*` to retrieve all its properties.
|
||||
- `filters`: An object of filters to apply on the retrieved data model's properties.
|
||||
|
||||
<Note>
|
||||
|
||||
Filters currently don't work on models of another module, such as `brand` in this example.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Test it Out
|
||||
|
||||
To test the API route out, first, retrieve the authentication token of your admin user by sending a `POST` request to `/auth/user/emailpass`:
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:9000/auth/user/emailpass' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "admin@medusa-test.com",
|
||||
"password": "supersecret"
|
||||
}'
|
||||
```
|
||||
|
||||
Make sure to replace the email and password with your user's credentials.
|
||||
|
||||
Then, send a `GET` request to `/admin/products/:id/brand`:
|
||||
|
||||
```bash
|
||||
curl 'http://localhost:9000/admin/products/prod_123/brand' \
|
||||
-H 'Authorization: Bearer {token}'
|
||||
```
|
||||
|
||||
This returns the product's brand if it has one. For example:
|
||||
|
||||
```json title="Example Response"
|
||||
{
|
||||
"brand": {
|
||||
"id": "123",
|
||||
"name": "Acme",
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Products of a Brand
|
||||
|
||||
An example of retrieving the products of a brand:
|
||||
|
||||
export const brandProductsHighlights = [
|
||||
["7", `"products.*"`, "Use the plural name of `product` since a brand has multiple products."]
|
||||
]
|
||||
|
||||
```ts highlights={brandProductsHighlights}
|
||||
const query = req.scope.resolve(
|
||||
ContainerRegistrationKeys.QUERY
|
||||
)
|
||||
|
||||
const { data: [brand] } = await query.graph({
|
||||
entity: "brand",
|
||||
fields: ["products.*"],
|
||||
filters: {
|
||||
id: req.params.id,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
In this case, since a brand has multiple products, you specify the plural name of the `Product` data model (`products`) in `fields`.
|
||||
|
||||
The retrieved `brand` now has a `products` field, which is an array of products linked to it:
|
||||
|
||||
```json title="Example Response"
|
||||
{
|
||||
"brand": {
|
||||
"products": [
|
||||
// ...
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
By following the examples of the previous chapters, you:
|
||||
|
||||
- Defined a link between the Brand and Product modules's data models, as if you're extending the `Product` model to add a brand.
|
||||
- Created a link between brand and product records.
|
||||
- Queried the brand linked to a product, and vice versa.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
In the next chapters, you'll learn how to customize the Medusa Admin to show brands.
|
||||
Reference in New Issue
Block a user