Skip to content

Instantly share code, notes, and snippets.

@cfluke-cb
Created September 24, 2025 14:47
Show Gist options
  • Select an option

  • Save cfluke-cb/3cbfc2684dc7df63185af32ff35da755 to your computer and use it in GitHub Desktop.

Select an option

Save cfluke-cb/3cbfc2684dc7df63185af32ff35da755 to your computer and use it in GitHub Desktop.
/**
* Copyright 2025-present Coinbase Global, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Rate Limiter Example: Request Transformers for Rate Limiting
*
* This example demonstrates how to add rate limiting to your Prime API client
* using request transformers. Two approaches are shown:
* 1. Simple token bucket rate limiter (self-made)
* 2. Using the popular 'bottleneck' npm package
*
* Rate limiting is important for:
* - Staying within API rate limits
* - Being a good API citizen
* - Preventing overwhelming the server
* - Managing concurrent requests in high-load applications
*
* Usage:
* npm install bottleneck # For approach 2
* node examples/advanced/rateLimiterExample.js
*/
require('dotenv').config();
const { CoinbasePrimeClient, PortfoliosService } = require('@coinbase-sample/prime');
/**
* Approach 1: Simple Token Bucket Rate Limiter
*
* This is a basic implementation that limits requests per second.
* It uses a token bucket algorithm where tokens are replenished at a fixed rate.
*/
class SimpleRateLimiter {
constructor(tokensPerSecond = 10, maxTokens = 10) {
this.tokensPerSecond = tokensPerSecond;
this.maxTokens = maxTokens;
this.tokens = maxTokens;
this.lastRefill = Date.now();
this.waitingQueue = [];
}
/**
* Request permission to make an API call
* @returns {Promise<void>} Resolves when request can proceed
*/
async acquireToken() {
this.refillTokens();
if (this.tokens >= 1) {
this.tokens--;
return Promise.resolve();
}
// No tokens available, wait for next refill
return new Promise((resolve) => {
this.waitingQueue.push(resolve);
this.scheduleNextRefill();
});
}
refillTokens() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
const tokensToAdd = elapsed * this.tokensPerSecond;
this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
this.lastRefill = now;
}
scheduleNextRefill() {
if (this.waitingQueue.length === 0) return;
const timeToNextToken = (1 / this.tokensPerSecond) * 1000;
setTimeout(() => {
this.refillTokens();
if (this.tokens >= 1 && this.waitingQueue.length > 0) {
this.tokens--;
const resolve = this.waitingQueue.shift();
resolve();
}
this.scheduleNextRefill();
}, timeToNextToken);
}
/**
* Create a request transformer that applies rate limiting
* @returns {Function} Request transformer function
*/
createRequestTransformer() {
return async (config) => {
const startTime = Date.now();
await this.acquireToken();
const waitTime = Date.now() - startTime;
if (waitTime > 0) {
console.log(`⏱️ Rate limited: waited ${waitTime}ms for token`);
}
// Add rate limiting headers for debugging
config.headers = config.headers || {};
config.headers['X-Rate-Limit-Wait'] = waitTime.toString();
return config;
};
}
}
/**
* Approach 2: Using Bottleneck Library
*
* Bottleneck is a popular, battle-tested rate limiting library
* with many advanced features like clustering support, Redis backing, etc.
*
* Install with: npm install bottleneck
*/
function createBottleneckRateLimiter() {
let Bottleneck;
try {
Bottleneck = require('bottleneck');
} catch (error) {
console.log('⚠️ Bottleneck not installed. Run: npm install bottleneck');
return null;
}
// Create bottleneck limiter with 10 requests per second
const limiter = new Bottleneck({
minTime: 100, // Minimum 100ms between requests (= 10 requests/second)
maxConcurrent: 5, // Maximum 5 concurrent requests
reservoir: 20, // Start with 20 requests available
reservoirRefreshAmount: 10, // Add 10 requests every refresh
reservoirRefreshInterval: 1000, // Refresh every 1000ms (1 second)
});
// Optional: Add event listeners for monitoring
limiter.on('ready', () => {
console.log('πŸš€ Rate limiter ready');
});
limiter.on('idle', () => {
console.log('πŸ’€ Rate limiter idle (no pending requests)');
});
limiter.on('depleted', () => {
console.log('πŸͺ£ Rate limiter depleted (waiting for tokens)');
});
/**
* Create a request transformer using Bottleneck
* @returns {Function} Request transformer function
*/
function createRequestTransformer() {
return async (config) => {
const startTime = Date.now();
// Wrap the request in bottleneck's schedule method
await limiter.schedule(() => Promise.resolve());
const waitTime = Date.now() - startTime;
if (waitTime > 10) {
// Only log if we actually waited
console.log(`⏱️ Rate limited: waited ${waitTime}ms`);
}
// Add debugging headers
config.headers = config.headers || {};
config.headers['X-Rate-Limit-Wait'] = waitTime.toString();
config.headers['X-Rate-Limit-Library'] = 'bottleneck';
return config;
};
}
return { limiter, createRequestTransformer };
}
/**
* Example 1: Simple Rate Limiter
*/
async function simpleRateLimiterExample() {
console.log('\n=== Example 1: Simple Token Bucket Rate Limiter ===');
try {
// Create client from environment variables
const client = CoinbasePrimeClient.fromEnv();
// Create rate limiter: 5 requests per second, max 5 tokens
const rateLimiter = new SimpleRateLimiter(5, 5);
// Add the rate limiting transformer to the client
client.addTransformRequest(rateLimiter.createRequestTransformer());
const portfolioService = new PortfoliosService(client);
console.log(
'πŸ”„ Making 10 rapid requests (should be rate limited to 5/second)...'
);
const startTime = Date.now();
const requests = [];
// Make 10 concurrent requests
for (let i = 0; i < 10; i++) {
requests.push(
portfolioService
.listPortfolios()
.then((result) => {
const elapsed = Date.now() - startTime;
console.log(`βœ… Request ${i + 1} completed after ${elapsed}ms`);
return result;
})
.catch((error) => {
console.error(`❌ Request ${i + 1} failed:`, error.message);
})
);
}
await Promise.all(requests);
const totalTime = Date.now() - startTime;
console.log(`🏁 All requests completed in ${totalTime}ms`);
console.log(`πŸ“Š Average: ${totalTime / 10}ms per request`);
} catch (error) {
console.error('Simple rate limiter example error:', error.message);
}
}
/**
* Example 2: Bottleneck Rate Limiter
*/
async function bottleneckRateLimiterExample() {
console.log('\n=== Example 2: Bottleneck Rate Limiter ===');
const bottleneckConfig = createBottleneckRateLimiter();
if (!bottleneckConfig) {
console.log('⏭️ Skipping bottleneck example (bottleneck not installed)');
return;
}
try {
const client = CoinbasePrimeClient.fromEnv();
const { limiter, createRequestTransformer } = bottleneckConfig;
// Add the bottleneck rate limiting transformer
client.addTransformRequest(createRequestTransformer());
const portfolioService = new PortfoliosService(client);
console.log('πŸ”„ Making 8 requests with Bottleneck rate limiting...');
const startTime = Date.now();
const requests = [];
for (let i = 0; i < 8; i++) {
requests.push(
portfolioService
.listPortfolios()
.then((result) => {
const elapsed = Date.now() - startTime;
console.log(`βœ… Request ${i + 1} completed after ${elapsed}ms`);
return result;
})
.catch((error) => {
console.error(`❌ Request ${i + 1} failed:`, error.message);
})
);
}
await Promise.all(requests);
const totalTime = Date.now() - startTime;
console.log(`🏁 All requests completed in ${totalTime}ms`);
// Show bottleneck statistics
console.log('πŸ“Š Bottleneck Statistics:');
console.log(` Running: ${limiter.running()}`);
console.log(` Pending: ${limiter.pending()}`);
console.log(` Reservoir: ${limiter.reservoir()}`);
} catch (error) {
console.error('Bottleneck rate limiter example error:', error.message);
}
}
/**
* Example 3: Per-Call Rate Limiting
*
* This shows how to apply rate limiting to individual calls
* rather than the entire client.
*/
async function perCallRateLimitingExample() {
console.log('\n=== Example 3: Per-Call Rate Limiting ===');
try {
const client = CoinbasePrimeClient.fromEnv();
const portfolioService = new PortfoliosService(client);
// Create a rate limiter for this specific operation
const operationRateLimiter = new SimpleRateLimiter(2, 2); // 2 requests per second
console.log('πŸ”„ Making 5 requests with per-call rate limiting...');
const startTime = Date.now();
for (let i = 0; i < 5; i++) {
try {
// Apply rate limiting using call options
const result = await portfolioService.listPortfolios(
{}, // query params
{
transformRequest: operationRateLimiter.createRequestTransformer(),
}
);
const elapsed = Date.now() - startTime;
console.log(`βœ… Request ${i + 1} completed after ${elapsed}ms`);
} catch (error) {
console.error(`❌ Request ${i + 1} failed:`, error.message);
}
}
const totalTime = Date.now() - startTime;
console.log(`🏁 All sequential requests completed in ${totalTime}ms`);
} catch (error) {
console.error('Per-call rate limiting example error:', error.message);
}
}
/**
* Run all examples
*/
async function runAllExamples() {
console.log('🚦 Rate Limiter Examples for Prime SDK');
console.log('=====================================');
try {
await simpleRateLimiterExample();
await bottleneckRateLimiterExample();
await perCallRateLimitingExample();
console.log('\nβœ… All rate limiting examples completed!');
console.log('\nπŸ’‘ Key Takeaways:');
console.log('- Use request transformers for client-wide rate limiting');
console.log('- Use call options for per-operation rate limiting');
console.log('- Simple rate limiters work for basic use cases');
console.log('- Use libraries like Bottleneck for production applications');
console.log('- Always monitor your rate limiting effectiveness');
} catch (error) {
console.error('❌ Examples failed:', error.message);
if (error.message.includes('PRIME_CREDENTIALS')) {
console.log('\nπŸ”§ Setup Instructions:');
console.log('1. Set PRIME_CREDENTIALS environment variable:');
console.log(
' export PRIME_CREDENTIALS=\'{"AccessKey":"your-key","SecretKey":"your-secret","Passphrase":"your-passphrase"}\''
);
console.log('\n2. Or create a .env file with your credentials');
}
}
}
// Only run if this file is executed directly
if (require.main === module) {
runAllExamples().catch(console.error);
}
module.exports = {
SimpleRateLimiter,
createBottleneckRateLimiter,
simpleRateLimiterExample,
bottleneckRateLimiterExample,
perCallRateLimitingExample,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment