perseidesperseides
rate-limit

Basic Usage

Learn how to use the @perseidesjs/medusa-plugin-rate-limit plugin in your Medusa app

How to use

If you want to start restricting specific routes, you can import the RateLimit class from the plugin and then use it as follows:

src/api/middlewares.ts
import { defineMiddlewares } from "@medusajs/medusa"
import { RateLimit } from '@perseidesjs/medusa-plugin-rate-limit'
import { Modules } from "@medusajs/framework/utils"

export default defineMiddlewares({
  routes: [
    {
      matcher: "/store/custom*",
      middlewares: [
        async (req: MedusaRequest, res: MedusaResponse, next: MedusaNextFunction) => {
          const cacheService = req.scope.resolve(Modules.CACHE)
          const rateLimit = new RateLimit({
            cacheService,
            options: {
              limit: 50, // 50 requests per minute
              window: 60
            }
          })

          const ip = req.socket.remoteAddress as string

          // đŸĒ„ This is where the magic happens
          const { success } = await rateLimit.limit(ip)

          if (!success) {
            res.status(429).send('Too many requests, please try again later.')
            return
          }

          next()
        }
      ],
    },
  ],
})
When building custom middleware, use req.socket.remoteAddress for the client IP. Avoid using x-forwarded-for directly as it can be spoofed. If you need proxy support, use the built-in ipRateLimit middleware with trustProxy instead.

The idea is simple: limit the number of requests by the identifier/key of your choice. This is useful if you want to protect based on some business logic aspects, for example by basing it on a license plate in the context of a car website or by user id, you are free to manage this as you wish.

src/api/middlewares.ts
  // â„šī¸  A few examples in action...

  // For example by header
  await rateLimit.limit(req.headers['x-plate'])

  // By user id
  await rateLimit.limit(req.auth_context.actor_id)

  // Or by a static value that will be global to all users
  await rateLimit.limit('global')

Built-in IP Rate Limiting

For the common use case of IP-based rate limiting, V3 includes a ready-to-use ipRateLimit middleware:

src/api/middlewares.ts
import { defineMiddlewares } from "@medusajs/medusa"
import { ipRateLimit } from '@perseidesjs/medusa-plugin-rate-limit'

export default defineMiddlewares({
  routes: [
    {
      matcher: "/store/auth*",
      middlewares: [ipRateLimit({
        limit: 10,
        window: 60
      })],
    },
  ],
})

The ipRateLimit middleware automatically:

  • Extracts the client IP address
  • Sets appropriate rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining)
  • Returns a 429 status when the limit is exceeded

Extracting the client IP

By default, ipRateLimit uses req.socket.remoteAddress (the direct connection IP). This prevents attackers from bypassing rate limiting by spoofing the X-Forwarded-For header.

If your server is behind a reverse proxy (nginx, Cloudflare, etc.), enable trustProxy:

src/api/middlewares.ts
ipRateLimit({
  limit: 10,
  window: 60,
  trustProxy: 1  // Number of trusted proxy hops
})
trustProxy valueBehavior
false (default)Uses direct connection IP. Ignores X-Forwarded-For.
trueUses leftmost IP from X-Forwarded-For. Only use if your proxy overwrites the header.
numberNumber of trusted proxy hops. Extracts IP from the right side of the header, preventing spoofing.

Example with trustProxy: 1:

X-Forwarded-For: spoofed-ip, real-client-ip
                            ↑ uses this (rightmost)
Only enable trustProxy if your server is behind a trusted reverse proxy. Enabling it without a proxy allows clients to spoof their IP and bypass rate limiting.

On this page