4.4.3. Query

In this chapter, you’ll learn about the Query utility and how to use it to fetch data from modules.

What is Query?#

Query fetches data across modules. It’s a set of methods registered in the Medusa container under the query key.

In your resources, such as API routes or workflows, you can resolve Query to fetch data across custom modules and Medusa’s commerce modules.


Query Example#

For example, create the route src/api/query/route.ts with the following content:

src/api/query/route.ts
7} from "@medusajs/framework/utils"8
9export const GET = async (10  req: MedusaRequest,11  res: MedusaResponse12) => {13  const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)14
15  const { data: myCustoms } = await query.graph({16    entity: "my_custom",17    fields: ["id", "name"],18  })19
20  res.json({ my_customs: myCustoms })21}

In the above example, you resolve Query from the Medusa container using the ContainerRegistrationKeys.QUERY (query) key.

Then, you run a query using its graph method. This method accepts as a parameter an object with the following required properties:

  • 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 to retrieve in the result.

The method returns an object that has a data property, which holds an array of the retrieved data. For example:

Returned Data
1{2  "data": [3    {4      "id": "123",5      "name": "test"6    }7  ]8}

Querying the Graph#

When you use the query.graph method, you're running a query through an internal graph that the Medusa application creates.

This graph collects data models of all modules in your application, including commerce and custom modules, and identifies relations and links between them.


Retrieve Linked Records#

Retrieve the records of a linked data model by passing in fields the data model's name suffixed with .*.

For example:

Code
1const { data: myCustoms } = await query.graph({2  entity: "my_custom",3  fields: [4    "id", 5    "name",6    "product.*",7  ],8})
Tip.* means that all of data model's properties should be retrieved. To retrieve a specific property, replace the * with the property's name. For example, product.title.

If the linked data model has isList enabled in the link definition, pass in fields the data model's plural name suffixed with .*.

For example:

Code
1const { data: myCustoms } = await query.graph({2  entity: "my_custom",3  fields: [4    "id", 5    "name",6    "products.*",7  ],8})

Apply Filters and Pagination on Linked Records#

Consider you want to apply filters or pagination configurations on the product(s) linked to my_custom. To do that, you must query the module link's table instead.

As mentioned in the Module Link documentation, Medusa creates a table for your module link. So, not only can you retrieve linked records, but you can also retrieve the records in a module link's table.

A module link's definition, exported by a file under src/links, has a special entryPoint property. Use this property when specifying the entity property in Query's graph method.

For example:

Code
1import productCustomLink from "../../../links/product-custom"2
3// ...4
5const { data: productCustoms } = await query.graph({6  entity: productCustomLink.entryPoint,7  fields: ["*", "product.*", "my_custom.*"],8  pagination: {9    take: 5,10    skip: 0,11  },12})

In the object passed to the graph method:

  • You pass the entryPoint property of the link definition as the value for entity. So, Query will retrieve records from the module link's table.
  • You pass three items to the field property:
    • * to retrieve the link table's fields. This is useful if the link table has custom columns.
    • product.* to retrieve the fields of a product record linked to a MyCustom record.
    • my_custom.* to retrieve the fields of a MyCustom record linked to a product record.

You can then apply any filters or pagination configurations.

The returned data is similar to the following:

Example Result
1[{2  "id": "123",3  "product_id": "prod_123",4  "my_custom_id": "123",5  "product": {6    "id": "prod_123",7    // other product fields...8  },9  "my_custom": {10    "id": "123",11    // other my_custom fields...12  }13}]

Apply Filters#

Code
1const { data: myCustoms } = await query.graph({2  entity: "my_custom",3  fields: ["id", "name"],4  filters: {5    id: [6      "mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",7      "mc_01HWSVWK3KYHKQEE6QGS2JC3FX",8    ],9  },10})

The query.graph function accepts a filters property. You can use this property to filter retrieved records.

In the example above, you filter the my_custom records by multiple IDs.

NoteFilters don't apply on fields of linked data models from other modules.

Apply Pagination#

Code
1const { 2  data: myCustoms,3  metadata: { count, take, skip },4} = await query.graph({5  entity: "my_custom",6  fields: ["id", "name"],7  pagination: {8    skip: 0,9    take: 10,10  },11})

The graph method's object parameter accepts a pagination property to configure the pagination of retrieved records.

To paginate the returned records, pass the following properties to pagination:

  • skip: (required to apply pagination) The number of records to skip before fetching the results.
  • take: The number of records to fetch.

When you provide the pagination fields, the query.graph method's returned object has a metadata property. Its value is an object having the following properties:

skipnumber
The number of records skipped.
takenumber
The number of records requested to fetch.
countnumber
The total number of records.

Sort Records#

Code
1const { data: myCustoms } = await query.graph({2  entity: "my_custom",3  fields: ["id", "name"],4  pagination: {5    order: {6      name: "DESC",7    },8  },9})
NoteSorting doesn't work on fields of linked data models from other modules.

To sort returned records, pass an order property to pagination.

The order property is an object whose keys are property names, and values are either:

  • ASC to sort records by that property in ascending order.
  • DESC to sort records by that property in descending order.

Request Query Configurations#

For API routes that retrieve a single or list of resources, Medusa provides a validateAndTransformQuery middleware that:

  • Validates accepted query parameters, as explained in this documentation.
  • Parses configurations that are received as query parameters to be passed to Query.

Using this middleware allows you to have default configurations for retrieved fields and relations or pagination, while allowing clients to customize them per request.

Step 1: Add Middleware#

The first step is to use the validateAndTransformQuery middleware on the GET route. You add the middleware in src/api/middlewares.ts:

src/api/middlewares.ts
1import { defineMiddlewares } from "@medusajs/medusa"2import { 3  validateAndTransformQuery,4} from "@medusajs/framework/http"5import { createFindParams } from "@medusajs/medusa/api/utils/validators"6
7export const GetCustomSchema = createFindParams()8
9export default defineMiddlewares({10  routes: [11    {12      matcher: "/customs",13      method: "GET",14      middlewares: [15        validateAndTransformQuery(16          GetCustomSchema,17          {18            defaults: [19              "id",20              "name",21              "products.*",22            ],23            isList: true,24          }25        ),26      ],27    },28  ],29})

The validateAndTransformQuery accepts two parameters:

  1. A Zod validation schema for the query parameters, which you can learn more about in the API Route Validation documentation. Medusa has a createFindParams utility that generates a Zod schema that accepts four query parameters:
    1. fields: The fields and relations to retrieve in the returned resources.
    2. offset: The number of items to skip before retrieving the returned items.
    3. limit: The maximum number of items to return.
    4. order: The fields to order the returned items by in ascending or descending order.
  2. A Query configuration object. It accepts the following properties:
    1. defaults: An array of default fields and relations to retrieve in each resource.
    2. isList: A boolean indicating whether a list of items are returned in the response.
    3. allowed: An array of fields and relations allowed to be passed in the fields query parameter.
    4. defaultLimit: A number indicating the default limit to use if no limit is provided. By default, it's 50.

Step 2: Use Configurations in API Route#

After applying this middleware, your API route now accepts the fields, offset, limit, and order query parameters mentioned above.

The middleware transforms these parameters to configurations that you can pass to Query in your API route handler. These configurations are stored in the remoteQueryConfig parameter of the MedusaRequest object.

For example, Create the file src/api/customs/route.ts with the following content:

src/api/customs/route.ts
1import {2  MedusaRequest,3  MedusaResponse,4} from "@medusajs/framework/http"5import {6  ContainerRegistrationKeys,7} from "@medusajs/framework/utils"8
9export const GET = async (10  req: MedusaRequest,11  res: MedusaResponse12) => {13  const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)14
15  const { data: myCustoms } = await query.graph({16    entity: "my_custom",17    ...req.remoteQueryConfig,18  })19
20  res.json({ my_customs: myCustoms })21}

This adds a GET API route at /customs, which is the API route you added the middleware for.

In the API route, you pass req.remoteQueryConfig to query.graph. remoteQueryConfig has properties like fields and pagination to configure the query based on the default values you specified in the middleware, and the query parameters passed in the request.

Test it Out#

To test it out, start your Medusa application and send a GET request to the /customs API route. A list of records are retrieved with the specified fields in the middleware.

Returned Data
1{2  "my_customs": [3    {4      "id": "123",5      "name": "test"6    }7  ]8}

Try passing one of the Query configuration parameters, like fields or limit, and you'll see its impact on the returned result.

NoteLearn more about specifing fields and relations and pagination in the API reference.
Was this chapter helpful?
Edit this page