Tusflow

Rate Limiting

Protect your API with intelligent rate limiting using Unkey Ratelimit

Overview

Tusflow uses Unkey Ratelimit for distributed rate limiting. The rate limiting middleware protects your API from abuse by limiting request frequency based on IP address and HTTP method.

Different rate limits are applied for various operations (uploads, chunks, metadata) to optimize for real-world usage patterns.

Implementation

The rate limiting middleware uses Unkey Ratelimit client with sliding window algorithm:

import { ERROR_MESSAGES, RATE_LIMIT } from '@/config';
import type { Bindings } from '@/types/honoTypes';
import { Ratelimit } from '@unkey/ratelimit';
import type { Context, Next } from 'hono';
import { env } from 'hono/adapter';
 
export const RateLimiter = () => {
  return async (c: Context, next: Next) => {
    if (!RATE_LIMIT.ENABLE) {
      return next();
    }
 
    const { UNKEY_ROOT_KEY } = env<Bindings>(c);
 
    const identifier = c.req.header('CF-Connecting-IP') || 'unknown';
    const method = c.req.method;
 
    const fallback = (identifier: string) => ({
      success: true,
      limit: 0,
      reset: 0,
      remaining: 0,
    });
 
    // Get rate limit config based on HTTP method
    const limitConfig =
      RATE_LIMIT.LIMITS[method as keyof typeof RATE_LIMIT.LIMITS] ||
      RATE_LIMIT.LIMITS.DEFAULT;
 
    const ratelimit = new Ratelimit({
      rootKey: UNKEY_ROOT_KEY,
      namespace: RATE_LIMIT.NAMESPACE,
      limit: limitConfig.tokens,
      duration: `${limitConfig.interval} s`,
      async: true,
      timeout: {
        ms: 3000, // only wait 3s at most before returning the fallback
        fallback,
      },
      onError: (err, identifier) => {
        console.error(`${identifier}:${method} - ${err.message}`);
        return fallback(`${identifier}:${method}`);
      },
    });
 
    try {
      const { success, limit, reset, remaining } = await ratelimit.limit(
        `${identifier}:${method}`
      );
 
      // Set rate limit headers
      c.header('X-RateLimit-Limit', limit.toString());
      c.header('X-RateLimit-Remaining', remaining.toString());
      c.header('X-RateLimit-Reset', reset.toString());
 
      if (!success) {
        return c.json(
          {
            error: ERROR_MESSAGES.RATE_LIMIT.LIMIT_EXCEEDED,
            code: 'RATE_LIMIT_EXCEEDED',
            details: {
              limit,
              reset,
              retryAfter: Math.ceil((reset - Date.now()) / 1000),
            },
          },
          429
        );
      }
 
      await next();
    } catch (error) {
      console.error('Rate limiter error:', error);
      return c.json({ error: 'Rate limiting service error' }, 500);
    }
  };
};

Configuration

Rate limits are configured in ratelimit-config.ts:

export const RATE_LIMIT = {
  ENABLE: true,
  NAMESPACE: 'tusflow-api',
  BLOCK_DURATION: 60 * 60, // 1 hour
  // Different limits for different endpoints
  LIMITS: {
    // For initiating new uploads
    POST: {
      tokens: 50, // Number of requests
      interval: 3600, // Time window in seconds (1 hour)
    },
    // For upload chunks
    PATCH: {
      tokens: 500, // More tokens for chunk uploads
      interval: 3600,
    },
    // For other operations (HEAD, DELETE)
    DEFAULT: {
      tokens: 100,
      interval: 3600,
    },
  },
};

Alternative: Using Upstash Ratelimit

Tusflow also supports using Upstash Ratelimit as an alternative to Unkey Ratelimit. This option provides a Redis-based distributed rate limiting solution with Cloudflare Workers integration.

Install Dependencies

First, install the required packages:

npm install @upstash/ratelimit @upstash/redis

Implementation

Replace the Unkey Ratelimit implementation with the following code:

import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis/cloudflare";
import type { Context, Next } from "hono";
import { env } from "hono/adapter";
import { ERROR_MESSAGES, RATE_LIMIT } from '@/config';
import type { Bindings } from '@/types/honoTypes';
 
export const RateLimiter = () => {
  return async (c: Context, next: Next) => {
    if (!RATE_LIMIT.ENABLE) {
      return next();
    }
 
    const { UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN } = env<Bindings>(c);
    const redis = new Redis({
      url: UPSTASH_REDIS_REST_URL,
      token: UPSTASH_REDIS_REST_TOKEN,
    });
    
    const identifier = c.req.header("CF-Connecting-IP") || "unknown";
    const method = c.req.method;
    
    // Get rate limit config based on HTTP method
    const limitConfig =
      RATE_LIMIT.LIMITS[method as keyof typeof RATE_LIMIT.LIMITS] ||
      RATE_LIMIT.LIMITS.DEFAULT;
      
    const ratelimit = new Ratelimit({
      redis,
      prefix: RATE_LIMIT.PREFIX,
      limiter: Ratelimit.slidingWindow(limitConfig.tokens, `${limitConfig.interval}s`),
    });
    
    try {
      const { success, limit, reset, remaining } = await ratelimit.limit(
        `${identifier}:${method}`
      );
      
      // Set rate limit headers
      c.header("X-RateLimit-Limit", limit.toString());
      c.header("X-RateLimit-Remaining", remaining.toString());
      c.header("X-RateLimit-Reset", reset.toString());
      
      if (!success) {
        return c.json(
          {
            error: ERROR_MESSAGES.RATE_LIMIT.LIMIT_EXCEEDED,
            code: 'RATE_LIMIT_EXCEEDED',
            details: {
              limit,
              reset,
              retryAfter: Math.ceil((reset - Date.now()) / 1000),
            },
          },
          429
        );
      }
      
      await next();
    } catch (error) {
      console.error('Rate limiter error:', error);
      return c.json({ error: 'Rate limiting service error' }, 500);
    }
  };
};

Configuration

Update your configuration to use the Upstash-specific settings:

export const RATE_LIMIT = {
  ENABLE: true,
  PREFIX: "tusflow-api:ratelimit:",
  BLOCK_DURATION: 60 * 60, // 1 hour
  // Different limits for different endpoints
  LIMITS: {
    // For initiating new uploads
    POST: {
      tokens: 50, // Number of requests
      interval: 3600, // Time window in seconds (1 hour)
    },
    // For upload chunks
    PATCH: {
      tokens: 500, // More tokens for chunk uploads
      interval: 3600,
    },
    // For other operations (HEAD, DELETE)
    DEFAULT: {
      tokens: 100,
      interval: 3600,
    },
  },
};

Environment Variables

Add the following environment variables to your .env file or deployment configuration:

UPSTASH_REDIS_REST_URL=https://your-upstash-redis-url.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-upstash-redis-token

Update Bindings Type

Make sure to update your honoTypes.ts file to include the new environment variables:

export interface Bindings {
  // ... existing bindings
  UPSTASH_REDIS_REST_URL: string;
  UPSTASH_REDIS_REST_TOKEN: string;
}

Upstash provides a serverless Redis instance with a REST API, making it well-suited for edge environments like Cloudflare Workers.

Rate Limit Headers

The middleware sets standard rate limit headers:

X-RateLimit-Limit: 500
X-RateLimit-Remaining: 499
X-RateLimit-Reset: 1640995200

Integration

Rate limiting is part of the security middleware stack:

// Combined security middleware
export const securityMiddleware = every(
  secureHeadersMiddleware,
  corsMiddleware,
  authMiddleware,
  csrfProtectionMiddleware,
  rateLimitMiddleware
);

Features

  1. Method-Based Limits

    • Different limits for uploads vs chunks
    • Configurable per HTTP method
    • Default fallback limits
    • Flexible token allocation
  2. Distributed Rate Limiting

    • Sliding window algorithm
    • Global rate limit state
    • High availability
  3. Smart Rate Limiting

    • IP-based identification
    • Cloudflare integration
    • Automatic header management
    • Graceful error handling

Error Handling

When rate limit is exceeded:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please try again later.",
    "details": {
      "limit": 500,
      "reset": 1640995200,
      "retryAfter": 3600
    }
  }
}

Best Practices

Configure Limits

  • Set appropriate token counts
  • Adjust time windows
  • Configure block duration
  • Enable in production

Monitor Usage

  • Track rate limit headers
  • Monitor blocked requests
  • Set up alerts
  • Analyze patterns

Handle Failures

  • Implement retry logic
  • Respect retry-after
  • Cache rate limit state
  • Handle errors gracefully

Always test rate limiting configuration in staging before deploying to production to ensure it doesn't impact legitimate users.

Edit on GitHub

Last updated on

On this page