Connect Rate Limiter
A Redis-based rate limiting library for ShipEngine Connect modules, providing distributed rate limiting and quota management capabilities.
Overview
The Connect Rate Limiter provides a simple, Redis-backed solution for implementing rate limiting in Connect carrier modules. It supports multiple rate limiting strategies including rolling and fixed windows, with optional quota management for longer-term limits.
Installation
Using npm link for Local Development
For local testing and development:
cd shared-libs/connect-rate-limiter
npm install
npm run build
npm link
cd modules/your-module
npm link @shipengine/connect-rate-limiter
Using File Path in package.json
Add to your Connect module's package.json:
{
"dependencies": {
"@shipengine/connect-rate-limiter": "file:../../shared-libs/connect-rate-limiter"
}
}
Then run:
npm install
Prerequisites
- Redis server running and accessible
- Node.js 14+ with TypeScript support
- ShipEngine Connect Runtime environment
Local Development Setup
Using Docker Compose (Recommended)
The package includes a Docker Compose configuration for easy local Redis setup:
version: '3.8'
services:
redis:
image: redis:7-alpine
container_name: connect-rate-limiter-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
volumes:
redis_data:
Start Redis for local development:
docker compose up -d
docker compose ps
docker compose logs redis
docker compose down
Configuration
The rate limiter automatically connects to Redis at localhost:6379 by default for local development. For custom Redis configurations, you can provide a Redis configuration object with the following structure:
{
"host": "your-redis-host",
"port": 6379,
"connectTimeout": 3000,
"commandTimeout": 3000,
"retryDelayOnFailover": 100,
"maxRetriesPerRequest": 3,
"tls": {}
}
Configuration Options:
host: Redis server hostname
port: Redis server port (default: 6379)
connectTimeout: Connection timeout in milliseconds
commandTimeout: Command execution timeout in milliseconds
retryDelayOnFailover: Delay between retry attempts in milliseconds
maxRetriesPerRequest: Maximum number of retry attempts per request
tls: TLS configuration object (empty object enables TLS with default settings)
Production Deployment: For production environments, Redis configuration needs to be set up in the infra/helm directory, similar to the current setup for the dummy module. Add the Redis configuration to your module's values.yaml:
secret:
REDIS_CONFIGURATION: '#{YOUR_MODULE_REDIS_CONFIGURATION}'
This ensures proper Redis connectivity in deployed environments by using the appropriate Redis instance for your module.
Usage
Basic Setup
Create a rate limiter configuration file in your Connect module:
import {
RateLimitMode,
RateLimitConfiguration,
RateLimiter,
QuotaRateLimitConfig
} from '@shipengine/connect-rate-limiter';
export const rateLimiter = new RateLimiter();
export const quotaRateLimitConfig: QuotaRateLimitConfig = {
rateLimit: {
maxRequests: 10,
windowSize: 60
},
quota: {
maxRequests: 15,
windowSize: 86400
}
};
export const baseLimitConfig: RateLimitConfiguration = {
strategy: {
mode: RateLimitMode.ROLLING_QUOTA_ROLLING_RATE_LIMIT
},
config: quotaRateLimitConfig
};
Integration in Connect Methods
Method 1: Using checkRateLimitAndIncrementCounter with Base Configuration
This method checks rate limits and increments counters using the base configuration. This should be used for carriers with rate limits configured for our entire organization (one limit for the whole module)
import { TrackingRequest, TrackingResponse } from '@shipengine/connect-carrier-api';
import { rateLimiter, baseLimitConfig } from '../../rate-limit-config';
export const Track = async (request: TrackingRequest): Promise<TrackingResponse> => {
const sellerId = request.metadata?.seller_id;
const limitId = `your_module_se-${sellerId}`;
await rateLimiter.checkRateLimitAndIncrementCounter(limitId, baseLimitConfig);
const apiResponse = await callCarrierAPI(request);
return mapResponse(apiResponse);
};
Method 2: Using checkRateLimitAndIncrementCounter with Metadata Configuration
For cases where rate limit configuration comes from seller metadata (set during registration). This should be used for carriers with rate limits configured per each seller.
import { TrackingRequest, TrackingResponse } from '@shipengine/connect-carrier-api';
import { rateLimiter, baseLimitConfig } from '../../rate-limit-config';
export const Track = async (request: TrackingRequest): Promise<TrackingResponse> => {
const sellerId = request.metadata?.seller_id;
const limitId = `your_module_${sellerId}`;
const rateLimitConfig = request.metadata?.rate_limit_config || baseLimitConfig;
await rateLimiter.checkRateLimitAndIncrementCounter(limitId, rateLimitConfig);
const apiResponse = await callCarrierAPI(request);
return mapResponse(apiResponse);
};
Per-Seller Rate Limiting
For carriers that have per-seller API limits:
export const Track = async (request: TrackingRequest): Promise<TrackingResponse> => {
const sellerId = request.metadata?.seller_id;
const limitId = `your_module_se-${sellerId}`;
await rateLimiter.checkRateLimitAndIncrementCounter(limitId, baseLimitConfig);
return performTracking(request);
};
Dynamic Configuration in Register Method
Set up rate limiting configuration during carrier registration:
import { RegisterRequest, RegisterResponse } from '@shipengine/connect-carrier-api';
import { rateLimiter, RateLimitMode } from '../../rate-limit-config';
export const Register = async (request: RegisterRequest): Promise<RegisterResponse> => {
const registrationInfo = request.registration_info;
if (registrationInfo.api_rate_limit) {
await rateLimiter.setConfiguration(`your_module_se-${request.metadata.seller_id}`, {
strategy: { mode: RateLimitMode.ROLLING_RATE_LIMIT },
config: {
rateLimit: {
maxRequests: registrationInfo.api_rate_limit.max_requests,
windowSize: registrationInfo.api_rate_limit.window_size
}
}
});
}
return mapRegistrationResponse(registrationInfo);
};
Note: Configuration updates should also be handled in the UpdateSettings method. Use the same setConfiguration pattern when processing settings changes to ensure rate limiting configurations stay synchronized with carrier settings.
Rate Limiting Modes
The library supports six different rate limiting modes:
Simple Rate Limiting
ROLLING_RATE_LIMIT: Sliding window rate limiting (most common)
FIXED_RATE_LIMIT: Fixed window rate limiting
Rate Limiting with Quota
ROLLING_QUOTA_ROLLING_RATE_LIMIT: Rolling quota with rolling rate limit
ROLLING_QUOTA_FIXED_RATE_LIMIT: Rolling quota with fixed rate limit
FIXED_QUOTA_ROLLING_RATE_LIMIT: Fixed quota with rolling rate limit
FIXED_QUOTA_FIXED_RATE_LIMIT: Fixed quota with fixed rate limit
Configuration Examples
const defaultConfig: RateLimitConfiguration = {
strategy: { mode: RateLimitMode.ROLLING_QUOTA_ROLLING_RATE_LIMIT },
config: {
rateLimit: {
maxRequests: 10,
windowSize: 60
},
quota: {
maxRequests: 1000,
windowSize: 86400
}
}
};
Error Handling
When using checkRateLimitAndIncrementCounter(), error handling is automatically managed by the package. The library will throw appropriate Connect runtime errors that are handled by the Connect framework.
The library provides specific error types for different rate limiting scenarios:
RateLimitExceededError: Thrown when short-term rate limits are exceeded
QuotaExceededError: Thrown when long-term quota limits are exceeded
RateLimiterError: Thrown for other rate limiter errors
These errors are automatically converted to appropriate Connect runtime errors and handled by the framework. You typically don't need to catch these errors manually when using checkRateLimitAndIncrementCounter().
For advanced use cases where you need custom error handling, you can use the isAllowed() method instead:
import { rateLimiter, baseLimitConfig } from '../../rate-limit-config';
const limitId = `your_module_${sellerId}`;
const isAllowed = await rateLimiter.checkRateLimitAndIncrementCounter(limitId, baseLimitConfig);
if (!isAllowed) {
return customRateLimitResponse();
}
Testing
Running Tests
npm test
npm run test -- --coverage
npm test -- rate-limiter.test.ts
Deployment
The package is automatically published to npm after merging changes to the master branch.
To use the latest version in your Connect module:
npm install @shipengine/connect-rate-limiter@latest
Or update your package.json to use the latest version and run npm install.