Skip to main content

What’s New in V3

V3 introduces significant architectural changes that provide much greater flexibility and control over rate limiting in your Medusa application. The main focus of this release is to give developers complete freedom in how they implement and customize rate limiting mechanisms.

Key Changes

Breaking Changes:
  • The defaultRateLimit middleware has been removed
  • Global configuration has been removed
New Features:
  • Introduction of the RateLimit class for programmatic rate limiting
  • Built-in ipRateLimit middleware for common IP-based rate limiting
  • Support for custom identifiers beyond IP addresses
  • More granular control over rate limiting logic

Installation

To install the V3 prerelease version:
yarn add @perseidesjs/medusa-plugin-rate-limit@next

The RateLimit Class

The core of V3 is the new RateLimit class that gives you programmatic control over rate limiting. This class integrates directly with Medusa’s cache service and allows you to implement custom rate limiting logic.

Basic Usage

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.headers['x-forwarded-for'] as string
          const { success } = await rateLimit.limit(ip)
          if (!success) {
            res.status(429).send('Too many requests, please try again later.')
            return
          }
          next()
        }
      ],
    },
  ],
})

Available Methods

The RateLimit class provides three main methods:

limit(identifier: string)

Checks and increments the rate limit for a given identifier.
const { success, remaining, limit } = await rateLimit.limit('user-123')
Returns:
  • success: Whether the request is within the rate limit
  • remaining: Number of requests remaining in the current window
  • limit: The configured limit for the window

getRemaining(identifier: string)

Gets the remaining requests for a specific identifier without incrementing the counter.
const remaining = await rateLimit.getRemaining('user-123')

reset(identifier: string)

Resets the request count for a specific identifier.
await rateLimit.reset('user-123')

Configuration Options

OptionTypeDefaultDescription
limitNumber100Maximum number of requests in the time window
windowNumber900Time window in seconds (default: 15 minutes)
prefixString"rl"Prefix for cache keys

Built-in IP Rate Limiting

For the common use case of IP-based rate limiting, V3 includes a ready-to-use ipRateLimit middleware like in V2:
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
The middleware also accepts an options object with the same options as the RateLimit class (limit, window, prefix).

Advanced Usage Examples

User-Based Rate Limiting

Rate limit based on authenticated user ID:
async (req: AuthenticatedMedusaRequest, res: MedusaResponse, next: MedusaNextFunction) => {
  const cacheService = req.scope.resolve(Modules.CACHE)
  const rateLimit = new RateLimit({
    cacheService,
    options: { limit: 100, window: 3600 } // 100 requests per hour
  })

  const userId = req.auth_context.actor_id
  const { success, remaining } = await rateLimit.limit(`user:${userId}`)
  
  if (!success) {
    res.status(429).send('Too many requests, please try again later.')
    return
  }
  
  res.setHeader('X-RateLimit-Remaining', String(remaining))
  next()
}

Multiple rate limiters

You can create multiple rate limiters with different options:
const rateLimiters = {
  loggedInUser: new RateLimit({
    cacheService,
    options: { limit: 100, window: 3600 }
  }),
  anonymousUser: new RateLimit({
    cacheService,
    options: { limit: 10, window: 60 }
  })
}

async (req: MedusaRequest, res: MedusaResponse, next: MedusaNextFunction) => {
  const userId = req.auth_context.actor_id
  const ip = req.headers['x-forwarded-for'] as string
  const { success, remaining } = userId ? await loggedInUserRateLimit.limit(`user:${userId}`) : await anonymousUserRateLimit.limit(ip)

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

  // ...
}

Migration from V2

Before (V2)

import { defaultRateLimit, configureDefaults } from '@perseidesjs/medusa-plugin-rate-limit'

configureDefaults({
  limit: 100,
  window: 900,
})

export default defineMiddlewares({
  routes: [
    {
      matcher: "/store/custom*",
      middlewares: [defaultRateLimit()],
    },
  ],
})

After (V3)

import { ipRateLimit } from '@perseidesjs/medusa-plugin-rate-limit'

export default defineMiddlewares({
  routes: [
    {
      matcher: "/store/custom*",
      middlewares: [ipRateLimit({
        limit: 100,
        window: 900,
      })],
    },
  ],
})

Why the Change?

The V2 approach, while simple, was limited in flexibility. The global configuration system made it difficult to have different rate limiting strategies for different parts of your application. V3 addresses these limitations by:
  1. Removing Global State: No more global configuration that could cause unexpected behavior
  2. Enabling Custom Identifiers: Rate limit by user ID, API key, or any custom identifier
  3. Providing Programmatic Control: Direct access to rate limiting logic for complex scenarios
  4. Maintaining Simplicity: The ipRateLimit middleware covers the most common use case with minimal setup
The new architecture is inspired by modern rate limiting libraries and provides the flexibility needed for complex e-commerce applications while maintaining the simplicity that made the original plugin popular.