From ab4358de7dcf06c0ea2989be8794c58ce103d7d5 Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Mon, 16 Jun 2025 10:25:45 +0300 Subject: [PATCH] docs: document index module (#12594) Closes DX-1638 --- .../module-links/index-module/page.mdx | 489 ++++++++++++++++++ www/apps/book/generated/edit-dates.mjs | 4 +- www/apps/book/generated/sidebar.mjs | 22 +- www/apps/book/public/llms-full.txt | 478 ++++++++++++++++- www/apps/book/sidebar.mjs | 5 + 5 files changed, 983 insertions(+), 15 deletions(-) create mode 100644 www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx diff --git a/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx b/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx new file mode 100644 index 0000000000..7d29cb5c42 --- /dev/null +++ b/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx @@ -0,0 +1,489 @@ +export const metadata = { + title: `${pageNumber} Index Module`, +} + +# {metadata.title} + +In this chapter, you'll learn about the Index Module and how you can use it. + + + +The Index Module is experimental and still in development, so it is subject to change. Consider whether your application can tolerate minor issues before using it in production. + + + +## What is the Index Module? + +The Index Module is a tool to perform highly performant queries across modules, for example, to filter linked modules. + +While modules share the same database by default, Medusa [isolates modules](../../modules/isolation/page.mdx) to allow using external data sources or different database types. + +So, when you retrieve data across modules using Query, Medusa aggregates the data coming from diffeent modules to create the end result. This approach limits your ability to filter data by linked modules. For example, you can't filter products (created in the Product Module) by their brand (created in the Brand Module). + +The Index Module solves this problem by ingesting data into a central data store on application startup. The data store has a relational structure that enables efficiently filtering data ingested from different modules (and their data stores). So, when you retrieve data with the Index Module, you're retrieving it from the Index' data store, not the original data source. + +![Diagram showcasing how data is retrieved from the Index Module's data store](https://res.cloudinary.com/dza7lstvk/image/upload/v1747988533/Medusa%20Book/index-module_epurmt.jpg) + +### Ingested Data Models + +Currently, only the following core data models are ingested into Index when you install it: + +- `Product` +- `ProductVariant` +- `Price` +- `SalesChannel` + +Consequently, you can only index custom data models if they are linked to an ingested data model. You'll learn more about this in the [Ingest Custom Data Models](#how-to-ingest-custom-data-models) section. + +Future versions may add more data models to the list. + +--- + +## How to Install the Index Module + +To install the Index Module, run the following command in your Medusa project to install its package: + +```bash npm2yarn +npm install @medusajs/index +``` + +Then, add the Index Module to your Medusa configuration in `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + // ... + { + resolve: "@medusajs/index", + }, + ], +}) +``` + +Finally, run the migrations to create the necessary tables for the Index Module in your database: + +```bash +npx medusa db:migrate +``` + +The index module only ingests data when you start your Medusa server. So, to ingest the [currently supported data models](#ingested-data-models), start the Medusa application: + +```bash npm2yarn +npm run dev +``` + +The ingestion process may take a while if your product catalog is large. You'll see the following messages in the logs: + +```bash +info: [Index engine] Checking for index changes +info: [Index engine] Found 7 index changes that are either pending or processing +info: [Index engine] syncing entity 'ProductVariant' +info: [Index engine] syncing entity 'ProductVariant' done (+38.73ms) +info: [Index engine] syncing entity 'Product' +info: [Index engine] syncing entity 'Product' done (+18.21ms) +info: [Index engine] syncing entity 'LinkProductVariantPriceSet' +info: [Index engine] syncing entity 'LinkProductVariantPriceSet' done (+33.87ms) +info: [Index engine] syncing entity 'Price' +info: [Index engine] syncing entity 'Price' done (+22.79ms) +info: [Index engine] syncing entity 'PriceSet' +info: [Index engine] syncing entity 'PriceSet' done (+10.72ms) +info: [Index engine] syncing entity 'LinkProductSalesChannel' +info: [Index engine] syncing entity 'LinkProductSalesChannel' done (+11.45ms) +info: [Index engine] syncing entity 'SalesChannel' +info: [Index engine] syncing entity 'SalesChannel' done (+7.00ms) +``` + +### Enable Index Module Feature Flag + +Since the Index Module is still experimental, the `/store/products` and `/admin/products` API routes will use the Index Module to retrieve products only if the Index Module's feature flag is enabled. By enabling the feature flag, you can filter products by their linked data models in these API routes. + +To enable the Index Module's feature flag, add the following line to your `.env` file: + +```env +MEDUSA_FF_INDEX_ENGINE=true +``` + +If you send a request to the `/store/products` or `/admin/products` API routes, you'll receive the following response: + +```json +{ + "products": [ + // ... + ], + "count": 2, + "estimate_count": 2, + "offset": 0, + "limit": 50 +} +``` + +Notice the `estimate_count` property, which is the estimated total number of products in the database. You'll learn more about it in the [Pagination](#apply-pagination-with-the-index-module) section. + +--- + +## How to Use the Index Module + +The Index Module adds a new `index` method to [Query](../query/page.mdx) and it has the same API as the `graph` method. + +For example, to filter products by a sales channel ID: + +export const basicHighlights = [ + ["17", "id", "Filter products by their linked sales channel ID."] +] + +```ts title="src/api/custom/products/route.ts" highlights={basicHighlights} +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const query = req.scope.resolve("query") + + const { data: products } = await query.index({ + entity: "product", + fields: ["*", "sales_channels.*"], + filters: { + sales_channels: { + id: "sc_123" + } + } + }) + + res.json({ products }) +} +``` + +This will return all products that are linked to the sales channel with the ID `sc_123`. + +The `index` method accepts an object with the same properties as the `graph` method's parameter: + +- `entity`: The data model's name, as specified in the first parameter of the `model.define` method used for the data model's definition. +- `fields`: An array of the data model’s properties, relations, and linked data models to retrieve in the result. +- `filters`: An object with the filters to apply on the data model's properties, relations, and linked data models that are ingested. + +--- + +## How to Ingest Custom Data Models + +Aside from the [core data models](#ingested-data-models), you can also ingest your own custom data models into the Index Module. You can do so by defining a link between your custom data model and one of the core data models, and setting the `filterable` property in the link definition. + + + +Read-only links are not supported by the Index Module. + + + +For example, assuming you have a Brand Module with a Brand data model (as explained in the [Customizations](../../../customization/custom-features/module/page.mdx)), you can ingest it into the Index Module using the `filterable` property in its link definition to the Product data model: + +export const filterableHighlights = [ + ["12", "filterable", "Inject Brand by setting its filterable properties."] +] + +```ts title="src/links/product-brand.ts" highlights={filterableHighlights} +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, + }, + { + linkable: BrandModule.linkable.brand, + filterable: ["id", "name"], + }, +) +``` + +The `filterable` property is an array of property names in the data model that can be filtered using the `index` method. When the `filterable` property is set, the Index Module will ingest into its data store the custom data model. + +But first, you must run the migrations to sync the link, then start the Medusa application: + +```bash npm2yarn +npx medusa db:migrate +npm run dev +``` + +You'll then see the following message in the logs: + +```bash +info: [Index engine] syncing entity 'LinkProductProductBrandBrand' +info: [Index engine] syncing entity 'LinkProductProductBrandBrand' done (+3.64ms) +info: [Index engine] syncing entity 'Brand' +info: [Index engine] syncing entity 'Brand' done (+0.99ms) +``` + +You can now filter products by their brand, and vice versa. For example: + +```ts title="src/api/custom/products/route.ts" +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const query = req.scope.resolve("query") + + const { data: products } = await query.index({ + entity: "product", + fields: ["*", "brand.*"], + filters: { + brand: { + name: "Acme" + } + } + }) + + res.json({ products }) +} +``` + +This will return all products that are linked to the brand with the name `Acme`. For example: + +```json title="Example Response" +{ + "products": [ + { + "id": "prod_123", + "brand": { + "id": "brand_123", + "name": "Acme" + }, + // ... + } + ] +} +``` + +--- + +## Apply Pagination with the Index Module + +Similar to Query's `graph` method, the Index Module accepts a `pagination` object to paginate the results. + +For example, to paginate the products and retrieve `10` products per page: + +export const paginationHighlights = [ + ["14", "metadata", "Pagination details."], + ["23", "pagination", "Apply pagination to the retrieved records."] +] + +```ts title="src/api/custom/products/route.ts" highlights={paginationHighlights} +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const query = req.scope.resolve("query") + + const { + data: products, + metadata + } = await query.index({ + entity: "product", + fields: ["*", "brand.*"], + filters: { + brand: { + name: "Acme" + } + }, + pagination: { + take: 10, + skip: 0, + } + }) + + res.json({ products, ...metadata }) +} +``` + +The `pagination` object accepts the following properties: + +- `take`: The number of items to retrieve per page. +- `skip`: The number of items to skip before retrieving the items. + +When the `pagination` property is set, the `index` method will also return a `metadata` property. `metadata` is an object with the following properties: + +- `skip`: The number of items skipped. +- `take`: The number of items retrieved. +- `estimate_count`: The estimated total number of items in the database matching the query. This value is retrieved from the PostgreSQL query planner rather than using a `COUNT` query, so it may not be accurate for smaller data sets. + +For example, this is the response returned by the above API route: + +```json title="Example Response" +{ + "products": [ + // ... + ], + "skip": 0, + "take": 10, + "estimate_count": 100 +} +``` + +--- + +## index Method Usage Examples + +The following sections show examples of how to use the `index` method in different scenarios. + +### Retrieve Linked Data Models + +Retrieve the records of a linked data model by passing in fields the data model's name suffixed with `.*`. + +For example: + +```ts title="src/api/custom/products/route.ts" highlights={[["3"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["*", "brand.*"], +}) +``` + +This will return all products with their linked brand data model. + +### Use Advanced Filters + +When setting filters on properties, you can use advanced filters like `$ne` and `$gt`. These are the same advanced filters accepted by the [listing methods generated by the Service Factory](!resources!/service-factory-reference/tips/filtering). + +For example, to only retrieve products linked to a brand: + +```ts title="src/api/custom/products/route.ts" highlights={[["9"]]} +const { + data: products, +} = await query.index({ + entity: "product", + fields: ["*", "brand.*"], + filters: { + brand: { + id: { + $ne: null + } + } + }, +}) +``` + +You use the `$ne` operator to filter products that are linked to a brand. + +Another example is to retrieve products whose brand name starts with `Acme`: + +```ts title="src/api/custom/products/route.ts" highlights={[["9"]]} +const { + data: products, +} = await query.index({ + entity: "product", + fields: ["*", "brand.*"], + filters: { + brand: { + name: { + $like: "Acme%" + } + } + }, +}) +``` + +This will return all products whose brand name starts with `Acme`. + +### Use Request Query Configuations + +API routes using the `graph` method can configure default [query configurations](../query/page.mdx#request-query-configurations), such as which fields to retrieve, while also allowing clients to override them using query parameters. + +The `index` method supports the same configurations. For example, if you add the request query configuration as explained in the [Query documentation](../query/page.mdx#request-query-configurations), you can use those configurations in the `index` method: + +```ts title="src/api/custom/products/route.ts" highlights={[["17"]]} +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const query = req.scope.resolve("query") + + const { + data: products, + metadata + } = await query.index({ + entity: "product", + ...req.queryConfig, + filters: { + brand: { + name: "Acme" + } + }, + }) + + res.json({ products, ...metadata }) +} +``` + +You pass the `req.queryConfig` object to the `index` method, which will contain the fields and pagination properties to use in the query. + +### Use Index Module in Workflows + +In a workflow's step, you can resolve `query` and use its `index` method to retrieve data using the Index Module. + +For example: + +export const workflowHighlights = [ + ["11", "query", "Resolve query in a workflow step."], + ["13", "index", "Use the `index` method to retrieve data."], +] + +```ts title="src/workflows/custom-workflow.ts" highlights={workflowHighlights} +import { + createStep, + createWorkflow, + StepResponse, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" + +const retrieveBrandsStep = createStep( + "retrieve-brands", + async ({}, { container }) => { + const query = container.resolve("query") + + const { data: brands } = await query.index({ + entity: "brand", + fields: ["*", "products.*"], + filters: { + products: { + id: { + $ne: null, + } + } + } + }) + + return new StepResponse(brands) + } +) + +export const retrieveBrandsWorkflow = createWorkflow( + "retrieve-brands", + () => { + const retrieveBrands = retrieveBrandsStep() + + return new WorkflowResponse(retrieveBrands) + } +) +``` + +This will retrieve all brands that are linked to at least one product. diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index fcc5c058b8..cca7b39c58 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -120,5 +120,7 @@ export const generatedEditDates = { "app/learn/fundamentals/framework/page.mdx": "2025-04-25T14:26:25.000Z", "app/learn/fundamentals/api-routes/retrieve-custom-links/page.mdx": "2025-04-25T14:26:25.000Z", "app/learn/fundamentals/workflows/errors/page.mdx": "2025-04-25T14:26:25.000Z", - "app/learn/fundamentals/api-routes/override/page.mdx": "2025-05-09T08:01:24.493Z" + "app/learn/fundamentals/api-routes/override/page.mdx": "2025-05-09T08:01:24.493Z", + "app/learn/fundamentals/module-links/index/page.mdx": "2025-05-23T07:57:58.958Z", + "app/learn/fundamentals/module-links/index-module/page.mdx": "2025-05-23T08:36:13.009Z" } \ No newline at end of file diff --git a/www/apps/book/generated/sidebar.mjs b/www/apps/book/generated/sidebar.mjs index 63108bfcf1..c54006601b 100644 --- a/www/apps/book/generated/sidebar.mjs +++ b/www/apps/book/generated/sidebar.mjs @@ -411,6 +411,16 @@ export const generatedSidebars = [ "chapterTitle": "3.4.3. Query", "number": "3.4.3." }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/learn/fundamentals/module-links/index-module", + "title": "Index Module", + "children": [], + "chapterTitle": "3.4.4. Index Module", + "number": "3.4.4." + }, { "loaded": true, "isPathHref": true, @@ -418,8 +428,8 @@ export const generatedSidebars = [ "path": "/learn/fundamentals/module-links/custom-columns", "title": "Add Custom Columns", "children": [], - "chapterTitle": "3.4.4. Add Custom Columns", - "number": "3.4.4." + "chapterTitle": "3.4.5. Add Custom Columns", + "number": "3.4.5." }, { "loaded": true, @@ -428,8 +438,8 @@ export const generatedSidebars = [ "path": "/learn/fundamentals/module-links/read-only", "title": "Read-Only Links", "children": [], - "chapterTitle": "3.4.5. Read-Only Links", - "number": "3.4.5." + "chapterTitle": "3.4.6. Read-Only Links", + "number": "3.4.6." }, { "loaded": true, @@ -438,8 +448,8 @@ export const generatedSidebars = [ "path": "/learn/fundamentals/module-links/query-context", "title": "Query Context", "children": [], - "chapterTitle": "3.4.6. Query Context", - "number": "3.4.6." + "chapterTitle": "3.4.7. Query Context", + "number": "3.4.7." } ], "chapterTitle": "3.4. Module Links", diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index aa7a3c4ea9..2c619126b9 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -4658,11 +4658,11 @@ In this document, you'll learn the general steps to deploy your Medusa applicati Find how-to guides for specific platforms in [this documentation](https://docs.medusajs.com/resources/deployment/index.html.md). -Want Medusa to manage and maintain your infrastructure? [Sign up and learn more about Medusa Cloud](https://medusajs.com/pricing) +Want Medusa to manage and maintain your infrastructure? [Sign up and learn more about Cloud](https://medusajs.com/pricing) -Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance. +Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Cloud hosts your server, Admin dashboard, database, and Redis instance. -With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub: +With Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub: - Push to deploy. - Multiple testing environments. @@ -5004,16 +5004,16 @@ Learn more about worker modes and how to configure them in the [Worker Mode chap ### How to Deploy Medusa? -Medusa Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Medusa Cloud hosts your server, Admin dashboard, database, and Redis instance. +Cloud is our managed services offering that makes deploying and operating Medusa applications possible without having to worry about configuring, scaling, and maintaining infrastructure. Cloud hosts your server, Admin dashboard, database, and Redis instance. -With Medusa Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub: +With Cloud, you maintain full customization control as you deploy your own modules and customizations directly from GitHub: - Push to deploy. - Multiple testing environments. - Preview environments for new PRs. - Test on production-like data. -[Sign up and learn more about Medusa Cloud](https://medusajs.com/pricing) +[Sign up and learn more about Cloud](https://medusajs.com/pricing) To self-host Medusa, the [next chapter](https://docs.medusajs.com/learn/deployment/general/index.html.md) explains the general steps to deploy your Medusa application. Refer to [this reference](https://docs.medusajs.com/resources/deployment/index.html.md) to find how-to deployment guides for general and specific hosting providers. @@ -10963,6 +10963,467 @@ export default defineLink( ``` +# Index Module + +In this chapter, you'll learn about the Index Module and how you can use it. + +The Index Module is experimental and still in development, so it is subject to change. Consider whether your application can tolerate minor issues before using it in production. + +## What is the Index Module? + +The Index Module is a tool to perform highly performant queries across modules, for example, to filter linked modules. + +While modules share the same database by default, Medusa [isolates modules](https://docs.medusajs.com/learn/fundamentals/modules/isolation/index.html.md) to allow using external data sources or different database types. + +So, when you retrieve data across modules using Query, Medusa aggregates the data coming from diffeent modules to create the end result. This approach limits your ability to filter data by linked modules. For example, you can't filter products (created in the Product Module) by their brand (created in the Brand Module). + +The Index Module solves this problem by ingesting data into a central data store on application startup. The data store has a relational structure that enables efficiently filtering data ingested from different modules (and their data stores). So, when you retrieve data with the Index Module, you're retrieving it from the Index' data store, not the original data source. + +![Diagram showcasing how data is retrieved from the Index Module's data store](https://res.cloudinary.com/dza7lstvk/image/upload/v1747988533/Medusa%20Book/index-module_epurmt.jpg) + +### Ingested Data Models + +Currently, only the following core data models are ingested into Index when you install it: + +- `Product` +- `ProductVariant` +- `Price` +- `SalesChannel` + +Consequently, you can only index custom data models if they are linked to an ingested data model. You'll learn more about this in the [Ingest Custom Data Models](#how-to-ingest-custom-data-models) section. + +Future versions may add more data models to the list. + +*** + +## How to Install the Index Module + +To install the Index Module, run the following command in your Medusa project to install its package: + +```bash npm2yarn +npm install @medusajs/index +``` + +Then, add the Index Module to your Medusa configuration in `medusa-config.ts`: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + // ... + { + resolve: "@medusajs/index", + }, + ], +}) +``` + +Finally, run the migrations to create the necessary tables for the Index Module in your database: + +```bash +npx medusa db:migrate +``` + +The index module only ingests data when you start your Medusa server. So, to ingest the [currently supported data models](#ingested-data-models), start the Medusa application: + +```bash npm2yarn +npm run dev +``` + +The ingestion process may take a while if your product catalog is large. You'll see the following messages in the logs: + +```bash +info: [Index engine] Checking for index changes +info: [Index engine] Found 7 index changes that are either pending or processing +info: [Index engine] syncing entity 'ProductVariant' +info: [Index engine] syncing entity 'ProductVariant' done (+38.73ms) +info: [Index engine] syncing entity 'Product' +info: [Index engine] syncing entity 'Product' done (+18.21ms) +info: [Index engine] syncing entity 'LinkProductVariantPriceSet' +info: [Index engine] syncing entity 'LinkProductVariantPriceSet' done (+33.87ms) +info: [Index engine] syncing entity 'Price' +info: [Index engine] syncing entity 'Price' done (+22.79ms) +info: [Index engine] syncing entity 'PriceSet' +info: [Index engine] syncing entity 'PriceSet' done (+10.72ms) +info: [Index engine] syncing entity 'LinkProductSalesChannel' +info: [Index engine] syncing entity 'LinkProductSalesChannel' done (+11.45ms) +info: [Index engine] syncing entity 'SalesChannel' +info: [Index engine] syncing entity 'SalesChannel' done (+7.00ms) +``` + +### Enable Index Module Feature Flag + +Since the Index Module is still experimental, the `/store/products` and `/admin/products` API routes will use the Index Module to retrieve products only if the Index Module's feature flag is enabled. By enabling the feature flag, you can filter products by their linked data models in these API routes. + +To enable the Index Module's feature flag, add the following line to your `.env` file: + +```env +MEDUSA_FF_INDEX_ENGINE=true +``` + +If you send a request to the `/store/products` or `/admin/products` API routes, you'll receive the following response: + +```json +{ + "products": [ + // ... + ], + "count": 2, + "estimate_count": 2, + "offset": 0, + "limit": 50 +} +``` + +Notice the `estimate_count` property, which is the estimated total number of products in the database. You'll learn more about it in the [Pagination](#apply-pagination-with-the-index-module) section. + +*** + +## How to Use the Index Module + +The Index Module adds a new `index` method to [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query/index.html.md) and it has the same API as the `graph` method. + +For example, to filter products by a sales channel ID: + +```ts title="src/api/custom/products/route.ts" highlights={basicHighlights} +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const query = req.scope.resolve("query") + + const { data: products } = await query.index({ + entity: "product", + fields: ["*", "sales_channels.*"], + filters: { + sales_channels: { + id: "sc_123" + } + } + }) + + res.json({ products }) +} +``` + +This will return all products that are linked to the sales channel with the ID `sc_123`. + +The `index` method accepts an object with the same properties as the `graph` method's parameter: + +- `entity`: The data model's name, as specified in the first parameter of the `model.define` method used for the data model's definition. +- `fields`: An array of the data model’s properties, relations, and linked data models to retrieve in the result. +- `filters`: An object with the filters to apply on the data model's properties, relations, and linked data models that are ingested. + +*** + +## How to Ingest Custom Data Models + +Aside from the [core data models](#ingested-data-models), you can also ingest your own custom data models into the Index Module. You can do so by defining a link between your custom data model and one of the core data models, and setting the `filterable` property in the link definition. + +Read-only links are not supported by the Index Module. + +For example, assuming you have a Brand Module with a Brand data model (as explained in the [Customizations](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)), you can ingest it into the Index Module using the `filterable` property in its link definition to the Product data model: + +```ts title="src/links/product-brand.ts" highlights={filterableHighlights} +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, + }, + { + linkable: BrandModule.linkable.brand, + filterable: ["id", "name"], + }, +) +``` + +The `filterable` property is an array of property names in the data model that can be filtered using the `index` method. When the `filterable` property is set, the Index Module will ingest into its data store the custom data model. + +But first, you must run the migrations to sync the link, then start the Medusa application: + +```bash npm2yarn +npx medusa db:migrate +npm run dev +``` + +You'll then see the following message in the logs: + +```bash +info: [Index engine] syncing entity 'LinkProductProductBrandBrand' +info: [Index engine] syncing entity 'LinkProductProductBrandBrand' done (+3.64ms) +info: [Index engine] syncing entity 'Brand' +info: [Index engine] syncing entity 'Brand' done (+0.99ms) +``` + +You can now filter products by their brand, and vice versa. For example: + +```ts title="src/api/custom/products/route.ts" +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const query = req.scope.resolve("query") + + const { data: products } = await query.index({ + entity: "product", + fields: ["*", "brand.*"], + filters: { + brand: { + name: "Acme" + } + } + }) + + res.json({ products }) +} +``` + +This will return all products that are linked to the brand with the name `Acme`. For example: + +```json title="Example Response" +{ + "products": [ + { + "id": "prod_123", + "brand": { + "id": "brand_123", + "name": "Acme" + }, + // ... + } + ] +} +``` + +*** + +## Apply Pagination with the Index Module + +Similar to Query's `graph` method, the Index Module accepts a `pagination` object to paginate the results. + +For example, to paginate the products and retrieve `10` products per page: + +```ts title="src/api/custom/products/route.ts" highlights={paginationHighlights} +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const query = req.scope.resolve("query") + + const { + data: products, + metadata + } = await query.index({ + entity: "product", + fields: ["*", "brand.*"], + filters: { + brand: { + name: "Acme" + } + }, + pagination: { + take: 10, + skip: 0, + } + }) + + res.json({ products, ...metadata }) +} +``` + +The `pagination` object accepts the following properties: + +- `take`: The number of items to retrieve per page. +- `skip`: The number of items to skip before retrieving the items. + +When the `pagination` property is set, the `index` method will also return a `metadata` property. `metadata` is an object with the following properties: + +- `skip`: The number of items skipped. +- `take`: The number of items retrieved. +- `estimate_count`: The estimated total number of items in the database matching the query. This value is retrieved from the PostgreSQL query planner rather than using a `COUNT` query, so it may not be accurate for smaller data sets. + +For example, this is the response returned by the above API route: + +```json title="Example Response" +{ + "products": [ + // ... + ], + "skip": 0, + "take": 10, + "estimate_count": 100 +} +``` + +*** + +## index Method Usage Examples + +The following sections show examples of how to use the `index` method in different scenarios. + +### Retrieve Linked Data Models + +Retrieve the records of a linked data model by passing in fields the data model's name suffixed with `.*`. + +For example: + +```ts title="src/api/custom/products/route.ts" highlights={[["3"]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["*", "brand.*"], +}) +``` + +This will return all products with their linked brand data model. + +### Use Advanced Filters + +When setting filters on properties, you can use advanced filters like `$ne` and `$gt`. These are the same advanced filters accepted by the [listing methods generated by the Service Factory](https://docs.medusajs.com/resources/service-factory-reference/tips/filtering/index.html.md). + +For example, to only retrieve products linked to a brand: + +```ts title="src/api/custom/products/route.ts" highlights={[["9"]]} +const { + data: products, +} = await query.index({ + entity: "product", + fields: ["*", "brand.*"], + filters: { + brand: { + id: { + $ne: null + } + } + }, +}) +``` + +You use the `$ne` operator to filter products that are linked to a brand. + +Another example is to retrieve products whose brand name starts with `Acme`: + +```ts title="src/api/custom/products/route.ts" highlights={[["9"]]} +const { + data: products, +} = await query.index({ + entity: "product", + fields: ["*", "brand.*"], + filters: { + brand: { + name: { + $like: "Acme%" + } + } + }, +}) +``` + +This will return all products whose brand name starts with `Acme`. + +### Use Request Query Configuations + +API routes using the `graph` method can configure default [query configurations](https://docs.medusajs.com/learn/fundamentals/module-links/query#request-query-configurations/index.html.md), such as which fields to retrieve, while also allowing clients to override them using query parameters. + +The `index` method supports the same configurations. For example, if you add the request query configuration as explained in the [Query documentation](https://docs.medusajs.com/learn/fundamentals/module-links/query#request-query-configurations/index.html.md), you can use those configurations in the `index` method: + +```ts title="src/api/custom/products/route.ts" highlights={[["17"]]} +import { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const query = req.scope.resolve("query") + + const { + data: products, + metadata + } = await query.index({ + entity: "product", + ...req.queryConfig, + filters: { + brand: { + name: "Acme" + } + }, + }) + + res.json({ products, ...metadata }) +} +``` + +You pass the `req.queryConfig` object to the `index` method, which will contain the fields and pagination properties to use in the query. + +### Use Index Module in Workflows + +In a workflow's step, you can resolve `query` and use its `index` method to retrieve data using the Index Module. + +For example: + +```ts title="src/workflows/custom-workflow.ts" highlights={workflowHighlights} +import { + createStep, + createWorkflow, + StepResponse, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" + +const retrieveBrandsStep = createStep( + "retrieve-brands", + async ({}, { container }) => { + const query = container.resolve("query") + + const { data: brands } = await query.index({ + entity: "brand", + fields: ["*", "products.*"], + filters: { + products: { + id: { + $ne: null, + } + } + } + }) + + return new StepResponse(brands) + } +) + +export const retrieveBrandsWorkflow = createWorkflow( + "retrieve-brands", + () => { + const retrieveBrands = retrieveBrandsStep() + + return new WorkflowResponse(retrieveBrands) + } +) +``` + +This will retrieve all brands that are linked to at least one product. + + # Link In this chapter, you’ll learn what Link is and how to use it to manage links. @@ -32434,7 +32895,7 @@ This is useful for development. However, for production, it’s highly recommend The S3 File Module Provider integrates Amazon S3 and services following a compatible API (such as MinIO or DigitalOcean Spaces) to store files uploaded to your Medusa application. -Medusa Cloud offers a managed file storage solution with AWS S3 for your Medusa application. Contact the [sales team](https://medusajs.com/pricing/) to learn more. +Cloud offers a managed file storage solution with AWS S3 for your Medusa application. Contact the [sales team](https://medusajs.com/pricing/) to learn more. ## Prerequisites @@ -46516,7 +46977,7 @@ To learn more about the commerce features that Medusa provides, check out Medusa In this tutorial, you'll learn how to implement a loyalty points system in Medusa. -Medusa Cloud provides a beta Store Credits feature that facilitates building a loyalty point system. [Get in touch](https://medusajs.com/contact) for early access. +Cloud provides a beta Store Credits feature that facilitates building a loyalty point system. [Get in touch](https://medusajs.com/contact) for early access. When you install a Medusa application, you get a fully-fledged commerce platform with a Framework for customization. The Medusa application's commerce features are built around [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md), which are available out-of-the-box. These features include management capabilities related to carts, orders, promotions, and more. @@ -64302,6 +64763,7 @@ A Notification Module Provider sends messages to users and customers in your Med - [SendGrid](https://docs.medusajs.com/infrastructure-modules/notification/sendgrid/index.html.md) - [Resend](https://docs.medusajs.com/integrations/guides/resend/index.html.md) +- [Slack](https://docs.medusajs.com/integrations/guides/slack/index.html.md) - [Twilio SMS](https://docs.medusajs.com/how-to-tutorials/tutorials/phone-auth#step-3-integrate-twilio-sms/index.html.md) Learn how to integrate a third-party notification provider in the [Create Notification Module Provider](https://docs.medusajs.com/references/notification-provider-module/index.html.md) documentation. diff --git a/www/apps/book/sidebar.mjs b/www/apps/book/sidebar.mjs index c66287af9b..808f889220 100644 --- a/www/apps/book/sidebar.mjs +++ b/www/apps/book/sidebar.mjs @@ -220,6 +220,11 @@ export const sidebars = [ path: "/learn/fundamentals/module-links/query", title: "Query", }, + { + type: "link", + path: "/learn/fundamentals/module-links/index-module", + title: "Index Module", + }, { type: "link", path: "/learn/fundamentals/module-links/custom-columns",