rl-js-client

RateLimitly JavaScript Client (ratelimitly-client)

CI npm version Node.js Version Zero Dependencies License: MIT

Official Node.js client library for Ratelimitly โ€” high-performance, centralized distributed rate limiting and latency-based load shedding over a low-latency UDP wire protocol.

What is Ratelimitly?

Ratelimitly is rate limiting and load shedding as a service. Your application asks a nearby Ratelimitly server for an admit/deny decision over a single UDP round trip: rate limits enforce per-account and per-resource quotas, and latency guards shed traffic automatically when a protected resource (a database, an upstream service) slows down. Servers are discovered through DNS SRV records tied to your API key, so there is nothing to configure but the key.


Key Highlights


Installation

npm install ratelimitly-client

Requirements: Node.js >= 20.0.0


Quick Start

const { createClient, ResourceRequest } = require('ratelimitly-client');

// 1. Initialize client with your RateLimitly Bech32 API key
// (DNS discovery domain is derived automatically from the key)
const client = createClient(process.env.RATELIMITLY_AUTH_KEY);

// 2. Define resource limit (e.g. 100 requests per 60 seconds)
const resources = [
  new ResourceRequest('api_traffic', 60000, 100, 1)
];

// 3. Check rate limit
client.checkRateLimit(resources, (err, result) => {
  if (err) {
    console.error('Communication error:', err.message);
    return;
  }

  if (result.success) {
    console.log('โœ… Request admitted by RateLimitly');
  } else {
    console.log('โ›” Rate limit exceeded! Tokens deficit:', result.resourceResults[0].tokensDeficit);
  }
});

Core Features & Usage Patterns

1. Multi-Resource Atomic Checks

Evaluate multiple limits in a single datagram. If any quota is exceeded, the request is denied as an atomic unit:

const resources = [
  new ResourceRequest('global_traffic', 60000, 10000, 1), // 10,000 req/min
  new ResourceRequest(`user:${userId}`, 1000, 20, 1),       // 20 req/sec
  new ResourceRequest(`org:${orgId}:burst`, 10000, 100, 1)  // 100 req/10s
];

client.checkRateLimit(resources, (err, result) => {
  if (result && result.success) {
    // All quotas passed
  }
});

2. Latency Guards & Dynamic Load Shedding

Protect downstream services (e.g. databases, external payment APIs) from brownouts:

const { LatencyGuard, ServiceLatencyBlock } = require('ratelimitly-client');

const guards = [
  new LatencyGuard({
    latencyTrackerName: 'primary_postgres',
    thresholdMs: 150,       // Max acceptable downstream latency
    ttlMs: 300000,          // Sample window TTL (5 minutes)
    maxSamples: 32,         // Max moving window samples
    minSampleThreshold: 5   // Minimum samples before guard activates
  })
];

client.checkRateLimit(resources, guards, 'checkout.api', async (err, result) => {
  if (!result || !result.success) {
    // Rate limit or latency guard tripped
    return;
  }

  // Execute database query
  const start = Date.now();
  await executeDatabaseQuery();
  const elapsed = Date.now() - start;

  // Asynchronously report downstream latency back to RateLimitly
  client.reportLatency([
    new ServiceLatencyBlock({
      latencyTrackerName: 'primary_postgres',
      observedLatency: elapsed,
      ttlMs: 300000
    })
  ]);
});

3. Async / Await and Promise Wrapper

Convert callback methods to clean async/await functions:

function checkRateLimitAsync(client, resources, guards = [], metricsLabel = null) {
  return new Promise((resolve, reject) => {
    client.checkRateLimit(resources, guards, metricsLabel, (err, result) => {
      if (err) return reject(err);
      resolve(result);
    });
  });
}

// Usage in Express / Fastify / Koa / NestJS:
async function handleRequest(req, res) {
  const result = await checkRateLimitAsync(client, [
    new ResourceRequest(`ip:${req.ip}`, 1000, 10, 1)
  ]);

  if (!result.success) {
    return res.status(429).json({ error: 'Too Many Requests' });
  }

  return res.json({ status: 'ok' });
}

4. High Availability & Custom Retry Policies

Configure custom retry policies with fixed, linear, or exponential backoff:

const { RequestPolicy, HaSchedule, createClient } = require('ratelimitly-client');

const haPolicy = new RequestPolicy({
  unitMs: 20,                                // Base scheduling quantum (ms)
  replayCount: 2,                            // Max replay rounds
  replayGap: HaSchedule.exponential(1, 2, 4), // Exponential growth: 1 -> 2 -> 4 units
  finalReceiveUnits: 1,                      // Tail receive interval
  completionDelivery: true                   // Broadcast result to missing servers
});

const client = createClient(process.env.RATELIMITLY_AUTH_KEY, null, {
  requestPolicy: haPolicy,
  dnsRefreshIntervalS: 10 // DNS SRV refresh interval (default: 10s)
});

API Reference

createClient(authKey, [dnsName], [options])

Creates and initializes an RClient instance.

RClient Methods

ResourceRequest(bucketName, windowSizeMs, rateLimit, [tokensRequested])

LatencyGuard(options)


Bech32 API Key Codec

The library includes a standalone encoder/decoder for RateLimitlyโ€™s Bech32 key format:

const { decodeApiKey, encodeApiKey, bytesToHex } = require('ratelimitly-client/api_key_codec');

const decoded = decodeApiKey('rl-aes1qx2kk...');
console.log({
  authMethod: decoded.authMethod, // 'aes', 'cookie', or 'none'
  keyId: decoded.keyId,           // BigInt (e.g. 4265246494029998997n)
  quotas: decoded.quotas          // Embedded quota limits
});

Examples Directory

Explore runnable examples in the examples/ directory:


Security

Please review SECURITY.md for our threat model, AES-256-GCM authentication details, and vulnerability disclosure process.


Contributing

Contributions are welcome! Please read CONTRIBUTING.md for development setup and testing instructions.


License

This project is licensed under the MIT License.