Skip to content

rateLimit

hooks
Source Code | Documentation

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 {  } 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> = {
  /** Generate the rate-limiting key. Defaults to `context.path`. */
  key?: (context: H) => Promisable<string>
  /** Number of points to consume per request. Defaults to `1`. */
  points?: (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<any>
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

OptionTypeDescription
key(context) => stringGenerate the rate-limiting key. Defaults to context.path.
points(context) => numberNumber of points to consume per request. Defaults to 1.

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}`,
      }),
    ],
  },
})

Custom Points per Request

Use the points option to consume more points for expensive operations:

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.