Back to all posts

Implements Caching in Medusa V2

Adil Basri

Caching is one of those things you don’t notice until it’s missing. Without it, your Medusa app ends up wasting time on repeated database calls and computations, slowing down responses that could otherwise be instant.

Implements Caching in Medusa V2

That’s fine if you’re testing locally, but in production every wasted millisecond means slower endpoints and unnecessary database load.

Medusa already ships with caching modules ("In-Memory" for dev, "Redis" for prod), so let’s look at how to actually use them in practice.

Custom API Route Caching

The simplest place to start: your own routes.
Here’s an example that checks if products are in cache before hitting the DB.

If cached, return immediately; if not, fetch and save for next time.

typescript
1import { Product } from ".medusa/types/remote-query-entry-points"
2import {
3  MedusaRequest,
4  MedusaResponse,
5} from "@medusajs/framework/http"
6import {
7  ContainerRegistrationKeys,
8  Modules,
9} from "@medusajs/framework/utils"
10
11export const GET = async (
12  req: MedusaRequest,
13  res: MedusaResponse
14) => {
15  const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
16  const cacheService = req.scope.resolve(Modules.CACHE)
17
18  // ℹ️ Define a cache key
19  // The cache key can be unique or not, depending on the query.
20  // For example, we could add the pagination parameters
21  // to the key to make sure we cache different pages of products separately
22  const CACHE_KEY = "medusa:products"
23
24  // ℹ️ First, we check if the data is cached
25  const cached = await cacheService.get<{ data: Product[] }>(CACHE_KEY)
26
27  // ℹ️ If the data is cached, we return it immediately
28  if (cached?.data) {
29    return res.json({ data: cached.data })
30  }
31
32  // ℹ️ If the data is not cached, we fetch it from the database
33  const { data } = await query.graph({
34    entity: "product",
35    fields: ["*"]
36  })
37
38  // ℹ️ We store the fetched data in the cache, for future requests
39  await cacheService.set(CACHE_KEY, { data })
40
41  // ℹ️ Finally, we return the fetched data
42  res.json({ data })
43}

That’s it, fewer queries, almost instant responses.

You can even build cache keys with pagination params (medusa:products:page=2) if needed.

Core API Route Caching

But what about Medusa’s built-in routes like /store/products?
We can’t modify them directly, but we can intercept them with middleware.

Here’s a quick example that:

  • Checks cache first
  • Returns instantly if found,
  • Otherwise intercepts the response, stores it, then sends it to the client.

typescript
1import {
2  defineMiddlewares,
3  type MedusaNextFunction,
4  type MedusaRequest,
5  type MedusaResponse,
6} from "@medusajs/framework/http"
7import { Modules } from "@medusajs/framework/utils"
8import { type HttpTypes } from "@medusajs/framework/types"
9
10export default defineMiddlewares({
11  routes: [
12    {
13      matcher: "/store/products", // ℹ️ The core API route we want to cache
14      method: 'GET',
15      middlewares: [
16        async (
17          req: MedusaRequest,
18          res: MedusaResponse,
19          next: MedusaNextFunction
20        ) => {
21          const cacheModule = req.scope.resolve(Modules.CACHE)
22
23          // ℹ️ This is the part responsible for retrieving the products from the cache
24          const cacheKey = `medusa:products`
25          const cachedProducts = await cacheModule.get<HttpTypes.StoreProductListResponse>(cacheKey)
26
27          if (cachedProducts) {
28            res.json(cachedProducts)
29            return
30          }
31
32          // ℹ️ This is the part responsible for caching the products after they are retrieved from the database
33          const originalJsonFn = res.json
34          Object.assign(res, {
35            json: async function (body: HttpTypes.StoreProductListResponse) {
36              await cacheModule.set(cacheKey, body)
37              await originalJsonFn.call(res, body)
38            },
39          })
40
41          next()
42        },
43      ],
44    },
45  ],
46})

This works with any core route, requires no changes to Medusa’s internals, and gives you a lot of flexibility.

Takeaway

With those two approaches, you’ll cover both custom and core API routes and your store instantly feels faster.


On top of that, don't forget that you can also use this inside your "Workflow's steps", since you can resolve the Cache Module.

Tagged:
Guide