3.3.3. Query
In this chapter, you’ll learn about Query 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 all resources that can access the Medusa Container, 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:
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: posts } = await query.graph({16 entity: "post",17 fields: ["id", "title"],18 })19 20 res.json({ posts })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 themodel.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:
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:
.*
means that all of data model's properties should be retrieved. You can also retrieve specific properties by replacing the *
with the property name, for each property.
For example:
In the example above, you retrieve only the id
and title
properties of the product
linked to a post
.
Retrieve List Link Records#
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:
In the example above, you retrieve all products linked to a post.
Apply Filters and Pagination on Linked Records#
Consider you want to apply filters or pagination configurations on the product(s) linked to post
. 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:
In the object passed to the graph
method:
- You pass the
entryPoint
property of the link definition as the value forentity
. 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 aPost
record.post.*
to retrieve the fields of aPost
record linked to a product record.
You can then apply any filters or pagination configurations.
The returned data
is similar to the following:
Apply Filters#
The query.graph
function accepts a filters
property. You can use this property to filter retrieved records.
In the example above, you filter the post
records by the ID post_123
.
You can also filter by multiple values of a property. For example:
In the example above, you filter the post
records by multiple IDs.
Advanced Query Filters#
Under the hood, Query uses the listX
(listPosts
) method of the data model's module's service to retrieve records. This method accepts a filter object that can be used to filter records.
Those filters don't just allow you to filter by exact values. You can also filter by properties that don't match a value, match multiple values, and other filter types.
Refer to the Service Factory Reference for examples of advanced filters. The following sections provide some quick examples.
Filter by Not Matching a Value
In the example above, only posts that have a title are retrieved.
Filter by Not Matching Multiple Values
In the example above, only posts that don't have the title My Post
or Another Post
are retrieved.
Filter by a Range
1const startToday = new Date()2startToday.setHours(0, 0, 0, 0)3 4const endToday = new Date()5endToday.setHours(23, 59, 59, 59)6 7const { data: posts } = await query.graph({8 entity: "post",9 fields: ["id", "title"],10 filters: {11 published_at: {12 $gt: startToday,13 $lt: endToday,14 },15 },16})
In the example above, only posts that were published today are retrieved.
Filter Text by Like Value
text
, id
, and enum
properties.In the example above, only posts that have the word My
in their title are retrieved.
Filter a Relation's Property
While it's not possible to filter by a linked data model's property, you can filter by a relation's property (that is, the property of a related data model that is defined in the same module).
In the example above, only posts that have an author with the name John
are retrieved.
Apply Pagination#
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:
skip
numbertake
numbercount
numberSort Records#
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.
Configure Query to Throw Errors#
By default, if Query doesn't find records matching your query, it returns an empty array. You can add option to configure Query to throw an error when no records are found.
The query.graph
method accepts as a second parameter an object that can have a throwIfKeyNotFound
property. Its value is a boolean indicating whether to throw an error if no record is found when filtering by IDs. By default, it's false
.
For example:
In the example above, if no post is found with the ID post_123
, Query will throw an error. This is useful to stop execution when a record is expected to exist.
Throw Error on Related Data Model#
The throwIfKeyNotFound
option can also be used to throw an error if the ID of a related data model's record (in the same module) is passed in the filters, and the related record doesn't exist.
For example:
In the example above, Query throws an error either if no post is found with the ID post_123
or if its found but its author ID isn't author_123
.
In the above example, it's assumed that a post belongs to an author, so it has an author_id
property. However, this also works in the opposite case, where an author has many posts.
For example:
In the example above, Query throws an error if no author is found with the ID author_123
or if the author is found but doesn't have a post with the ID post_123
.
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
:
1import { 2 validateAndTransformQuery,3 defineMiddlewares,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 "title",21 "products.*",22 ],23 isList: true,24 }25 ),26 ],27 },28 ],29})
The validateAndTransformQuery
accepts two parameters:
- 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:fields
: The fields and relations to retrieve in the returned resources.offset
: The number of items to skip before retrieving the returned items.limit
: The maximum number of items to return.order
: The fields to order the returned items by in ascending or descending order.
- A Query configuration object. It accepts the following properties:
defaults
: An array of default fields and relations to retrieve in each resource.isList
: A boolean indicating whether a list of items are returned in the response.allowed
: An array of fields and relations allowed to be passed in thefields
query parameter.defaultLimit
: A number indicating the default limit to use if no limit is provided. By default, it's50
.
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 queryConfig
parameter of the MedusaRequest
object.
remoteQueryConfig
has been deprecated in favor of queryConfig
. Their usage is still the same, only the property name has changed.For example, Create the file src/api/customs/route.ts
with the following content:
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: posts } = await query.graph({16 entity: "post",17 ...req.queryConfig,18 })19 20 res.json({ posts: posts })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.queryConfig
to query.graph
. queryConfig
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.
Try passing one of the Query configuration parameters, like fields
or limit
, and you'll see its impact on the returned result.