Skip to content

rateLimit ​

Category
Tags
Export size
min 6.25 kB · gzip 2.57 kB

Rate limits service method calls using rate-limiter-flexible. You provide a pre-configured RateLimiterAbstract instance (Memory, Redis, Mongo, etc.) and the hook consumes points per request.

ts
  import { 
rateLimit
} from 'feathers-utils/hooks';

Example ​

ts
import { rateLimit } from 'feathers-utils/hooks'
import { RateLimiterMemory } from 'rate-limiter-flexible'

const rateLimiter = new RateLimiterMemory({ points: 10, duration: 1 })

app.service('users').hooks({
  before: { find: [rateLimit(rateLimiter)] }
})

Type declaration ​

Show Type Declarations
ts
export type RateLimitOptions<H extends HookContext = HookContext> = {
  /**
   * The rate-limiting key, or a function to derive it from the context.
   *
   * Pass a static string to use a single shared bucket (a global rate limit
   * across all requests), or a function to compute the key per request
   * (e.g. per user or per IP).
   *
   * @default context.path
   */
  key?: string | ((context: H) => Promisable<string>)
  /**
   * Number of points to consume per request, or a function to compute it from
   * the context.
   *
   * @default 1
   */
  points?: number | ((context: H) => Promisable<number>)
}
/**
 * Rate limits service method calls using `rate-limiter-flexible`.
 * You provide a pre-configured `RateLimiterAbstract` instance
 * (Memory, Redis, Mongo, etc.) and the hook consumes points per request.
 *
 * @example
 * ```ts
 *
 *
 *
 * const rateLimiter = new RateLimiterMemory({ points: 10, duration: 1 })
 *
 * app.service('users').hooks({
 *   before: { find: [rateLimit(rateLimiter)] }
 * })
 * ```
 *
 * @see https://utils.feathersjs.com/hooks/rate-limit.html
 */
export declare const rateLimit: <H extends HookContext = HookContext>(
  rateLimiter: RateLimiterAbstract,
  options?: RateLimitOptions<H>,
) => (context: H, next?: NextFunction) => Promise<void>
ArgumentTypeDescription
rateLimiterRateLimiterAbstract
optionsRateLimitOptions<H>
typemethodsmulti
before, aroundfind, get, create, update, patch, removeyes

The rateLimit hook limits how many times a service method can be called within a time window using rate-limiter-flexible. You provide a pre-configured rate limiter instance — the hook calls consume() on each request and throws a TooManyRequests error when the limit is exceeded.

Any rate limiter backend supported by rate-limiter-flexible can be used (Memory, Redis, Mongo, Postgres, etc.).

Options ​

keystring | ((context: H) => Promisable<string>)defaultcontext.path

The rate-limiting key, or a function to derive it from the context.

Pass a static string to use a single shared bucket (a global rate limit across all requests), or a function to compute the key per request (e.g. per user or per IP).

pointsnumber | ((context: H) => Promisable<number>)default1

Number of points to consume per request, or a function to compute it from the context.

The RateLimiterRes is stored on context.params.rateLimit on both success and failure, so downstream hooks or services can inspect remainingPoints, consumedPoints, msBeforeNext, etc.

Examples ​

Basic Usage ​

ts
import { rateLimit } from 'feathers-utils/hooks'
import { RateLimiterMemory } from 'rate-limiter-flexible'

const rateLimiter = new RateLimiterMemory({
  points: 10, // 10 requests
  duration: 1, // per 1 second
})

app.service('users').hooks({
  before: {
    find: [rateLimit(rateLimiter)],
  },
})

Rate Limit per User ​

Use the key option to rate limit per authenticated user instead of per service path:

ts
const rateLimiter = new RateLimiterMemory({ points: 100, duration: 60 })

app.service('messages').hooks({
  before: {
    create: [
      rateLimit(rateLimiter, {
        key: (context) => `${context.path}:${context.params.user?.id}`,
      }),
    ],
  },
})

Global Rate Limit ​

Pass a static string as the key to share a single bucket across all requests — a global cap on an endpoint instead of one bucket per context.path:

ts
const rateLimiter = new RateLimiterMemory({ points: 1000, duration: 60 })

app.service('search').hooks({
  before: {
    find: [rateLimit(rateLimiter, { key: 'search' })],
  },
})

Custom Points per Request ​

Pass a static number to consume a fixed cost per request:

ts
app.service('reports').hooks({
  before: {
    find: [rateLimit(rateLimiter, { points: 5 })],
  },
})

Or pass a function to compute the cost from the context — e.g. to charge more for expensive queries:

ts
app.service('reports').hooks({
  before: {
    find: [
      rateLimit(rateLimiter, {
        points: (context) => (context.params.query?.$limit > 100 ? 5 : 1),
      }),
    ],
  },
})

Redis Backend ​

ts
import { RateLimiterRedis } from 'rate-limiter-flexible'
import Redis from 'ioredis'

const redisClient = new Redis()

const rateLimiter = new RateLimiterRedis({
  storeClient: redisClient,
  points: 100,
  duration: 60,
  keyPrefix: 'rl',
})

app.service('users').hooks({
  before: {
    find: [rateLimit(rateLimiter)],
  },
})

Bypass with iff ​

Use iff to skip rate limiting for internal (server-side) calls:

ts
import { rateLimit, iff } from 'feathers-utils/hooks'
import { isProvider } from 'feathers-utils/predicates'

app.service('users').hooks({
  before: {
    find: [
      iff(isProvider('rest', 'socketio', 'external'), rateLimit(rateLimiter)),
    ],
  },
})

Bypass with skippable ​

Use skippable to allow specific callers to opt out of rate limiting:

ts
import { rateLimit, skippable } from 'feathers-utils/hooks'

app.service('users').hooks({
  before: {
    find: [skippable(rateLimit(rateLimiter))],
  },
})

// Skip rate limiting for this call
app.service('users').find({ skipHooks: ['rateLimit'] })

Released under the MIT License.