npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

firebase-function-rate-limiter

v1.2.0

Published

Rate limiting middleware for Firebase Functions with Firestore, Realtime Database, and memory stores.

Readme

Firebase Function Rate Limiter

Rate limiting for Firebase Functions using Firestore, Firebase Realtime Database, or local memory. It provides Express middleware for HTTP functions and a direct check() API for callable functions.

Features

  • Atomic counters shared by function instances
  • Firestore and Realtime Database backends
  • Express/Firebase onRequest middleware
  • Direct checks for callable functions
  • Rate-limit and retry headers
  • Custom keys, skip rules, messages, and rejection handlers
  • TypeScript declarations

Installation

npm install firebase-function-rate-limiter firebase-admin

Node.js 18 or newer is required. The package uses CommonJS:

const {
  rateLimit,
  FirestoreStore,
  RealtimeDatabaseStore,
  MemoryStore,
} = require('firebase-function-rate-limiter');

Realtime Database quick start

const { initializeApp } = require('firebase-admin/app');
const { getDatabase } = require('firebase-admin/database');
const { onRequest } = require('firebase-functions/v2/https');
const express = require('express');
const { rateLimit, RealtimeDatabaseStore } = require('firebase-function-rate-limiter');

initializeApp();
const app = express();

app.use(rateLimit({
  store: new RealtimeDatabaseStore({
    database: getDatabase(),
    path: '_functionRateLimits',
  }),
  limit: 60,
  windowMs: 60_000,
  keyGenerator: (req) => `${req.user.uid}/hello`,
}));

app.get('/hello', (req, res) => res.json({ message: 'Hello!' }));
exports.api = onRequest(app);

For a non-default RTDB instance, supply its URL:

initializeApp({ databaseURL: 'https://YOUR_PROJECT_ID-default-rtdb.firebaseio.com' });

Firestore quick start

const { initializeApp } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');
const { onRequest } = require('firebase-functions/v2/https');
const express = require('express');
const { rateLimit, FirestoreStore } = require('firebase-function-rate-limiter');

initializeApp();
const app = express();

app.use(rateLimit({
  store: new FirestoreStore({
    firestore: getFirestore(),
    collection: '_functionRateLimits',
  }),
  limit: 60,
  windowMs: 60_000,
  keyGenerator: (req) => `${req.user.uid}/hello`,
}));

app.get('/hello', (req, res) => res.json({ message: 'Hello!' }));
exports.api = onRequest(app);

Callable functions

Use check() without Express. Convert RateLimitError into a Firebase HttpsError for callable clients:

const { onCall, HttpsError } = require('firebase-functions/v2/https');
const { getDatabase } = require('firebase-admin/database');
const { rateLimit, RealtimeDatabaseStore, RateLimitError } = require('firebase-function-rate-limiter');

const limiter = rateLimit({
  store: new RealtimeDatabaseStore({ database: getDatabase() }),
  limit: 10,
  windowMs: 60_000,
});

exports.expensiveTask = onCall(async (request) => {
  if (!request.auth) throw new HttpsError('unauthenticated', 'Sign in first.');
  const key = `${request.auth.uid}auth/expensiveTask`;
  try {
    await limiter.check(key);
  } catch (error) {
    if (error instanceof RateLimitError) {
      throw new HttpsError('resource-exhausted', error.message, {
        retryAt: error.resetTime.toISOString(),
      });
    }
    throw error;
  }
  return { ok: true };
});

Choosing a store

| Store | Use case | Shared across instances | Persistent | | --- | --- | --- | --- | | RealtimeDatabaseStore | Production apps using RTDB | Yes | Yes | | FirestoreStore | Production apps using Firestore | Yes | Yes | | MemoryStore | Tests and local emulators | No | No |

Both Firebase stores use transactions. Do not use MemoryStore for a global production limit: every function instance has a separate counter.

Key selection

Supply the complete key through keyGenerator for middleware or limiter.check(key) for direct checks. The library does not hash keys or add role, meter, or function names.

| Caller | Key with meter | Key without meter | | --- | --- | --- | | Admin | userid/meter-serial/function-name | userid/function-name | | Alt | useridalt/meter-serial/function-name | useridalt/function-name | | Auth | useridauth/meter-serial/function-name | useridauth/function-name | | Consumer | meter-serial/function-name | — |

userid + alt and userid + auth mean concatenation, for example user123alt/meter456/readMeter.

// Construct the key in your application using verified identity and role data.
const key = [userId + 'auth', meterSerial, 'readMeter'].filter(Boolean).join('/');
await limiter.check(key);

RTDB uses the key as a nested path beneath path. Each segment must be a valid RTDB node name (no ., #, $, [, ], or control characters). Firestore stores the key as a single document ID beneath collection, using encodeURIComponent so / becomes %2F. This is reversible encoding, not hashing. For example, user123/meter456/readMeter becomes user123%2Fmeter456%2FreadMeter in Firestore. Caller keys must fit the backend's path and document ID limits.

An optional prefix adds a parent path (prefix/key) before storage or Firestore encoding. Leave it empty to use exactly the formats above. Include the function name to keep independent function counters separate.

If keyGenerator is omitted, the existing UID/IP fallback still applies. The HTTP examples assume authentication middleware has populated req.user.

Migration: These paths differ from the previous hashed IDs, so existing counters will not be reused. Remove old hashed records separately if needed.

Configuration

const limiter = rateLimit({
  store,
  limit: 20,
  windowMs: 5 * 60_000,
  keyGenerator: (req) => req.user?.uid || req.ip,
  skip: (req) => req.path === '/health',
  standardHeaders: true,
  message: 'Please wait before trying again.',
  onRejected: (req, res, error) => {
    res.status(429).json({ error: error.message });
  },
});

See the complete API reference.

Response headers

  • RateLimit-Limit: maximum requests in the window
  • RateLimit-Remaining: requests remaining
  • RateLimit-Reset: Unix timestamp when the window resets
  • Retry-After: seconds to wait, on rejected requests

Database cleanup

Both database stores write only count and resetAt. RTDB uses milliseconds since the Unix epoch for resetAt; Firestore uses a timestamp. Expired counters reset on their next request. Optional cleanup can use resetAt; no separate expiration field is written.

Security

  • Use server-side Admin SDK database instances, not client SDK references.
  • Prefer verified Firebase Auth UIDs over IP addresses.
  • Trust X-Forwarded-For only behind Firebase/Google's trusted proxy.
  • Use different prefix values for limits that should not share counters.
  • Handle database errors in your Express error middleware.

License

MIT