Skip to content

AI Prompt: Forma3D.Connect — Phase 5n: Rate Limiting Implementation

Purpose: This prompt instructs an AI to implement rate limiting on API endpoints to prevent abuse
Estimated Effort: 1-2 days (~8-12 hours)
Prerequisites: Phase 5m completed (Unused Components)
Output: Rate limiting on all public API endpoints with configurable limits
Status: 🟡 PENDING


🎯 Mission

You are continuing development of Forma3D.Connect, building on the Phase 5m foundation. Your task is to implement Phase 5n: Rate Limiting — specifically addressing TD-012 (Missing Rate Limiting) from the technical debt register.

Why This Matters:

Without rate limiting, the system is vulnerable to:

  1. DoS Attacks: Malicious actors can overwhelm the API
  2. Webhook Replay Attacks: Rapid webhook replays could cause issues
  3. Accidental Client Loops: Buggy clients can create infinite request loops
  4. Resource Exhaustion: Database connections and memory can be depleted

Phase 5n delivers:

  • Rate limiting on all API endpoints
  • Per-endpoint configurable limits
  • Webhook-specific rate limiting
  • IP-based and API-key-based limiting
  • Clear rate limit headers in responses

📋 Context: Technical Debt Item

TD-012: Missing Rate Limiting

Attribute Value
Type Architecture Debt
Priority Medium
Location All public API endpoints
Interest Rate Medium
Principal (Effort) 1-2 days

🛠️ Implementation Phases

Phase 1: Install and Configure Throttler (1 hour)

Priority: Critical | Impact: High | Dependencies: None

1. Install NestJS Throttler

pnpm add @nestjs/throttler

2. Add Configuration Variables

Update apps/api/src/config/configuration.ts:

// === Rate Limiting ===
@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(1)
RATE_LIMIT_TTL_SECONDS: number = 60;

@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(1)
RATE_LIMIT_DEFAULT: number = 100;

@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(1)
RATE_LIMIT_WEBHOOK: number = 50;

@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
@Min(1)
RATE_LIMIT_AUTH: number = 10;

3. Update AppConfigService

Add to apps/api/src/config/config.service.ts:

export interface RateLimitConfig {
  ttlSeconds: number;
  defaultLimit: number;
  webhookLimit: number;
  authLimit: number;
}

// In AppConfigService class:
get rateLimit(): RateLimitConfig {
  return {
    ttlSeconds: this.configService.get<number>('RATE_LIMIT_TTL_SECONDS', 60),
    defaultLimit: this.configService.get<number>('RATE_LIMIT_DEFAULT', 100),
    webhookLimit: this.configService.get<number>('RATE_LIMIT_WEBHOOK', 50),
    authLimit: this.configService.get<number>('RATE_LIMIT_AUTH', 10),
  };
}

Phase 2: Configure Throttler Module (1 hour)

Priority: High | Impact: High | Dependencies: Phase 1

1. Create Throttler Configuration

Create apps/api/src/throttler/throttler.module.ts:

import { Module } from '@nestjs/common';
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
import { AppConfigService } from '../config/config.service';

@Module({
  imports: [
    ThrottlerModule.forRootAsync({
      inject: [AppConfigService],
      useFactory: (config: AppConfigService) => ({
        throttlers: [
          {
            name: 'default',
            ttl: config.rateLimit.ttlSeconds * 1000,
            limit: config.rateLimit.defaultLimit,
          },
          {
            name: 'webhook',
            ttl: config.rateLimit.ttlSeconds * 1000,
            limit: config.rateLimit.webhookLimit,
          },
          {
            name: 'auth',
            ttl: config.rateLimit.ttlSeconds * 1000,
            limit: config.rateLimit.authLimit,
          },
        ],
      }),
    }),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
})
export class AppThrottlerModule {}

2. Import in App Module

Update apps/api/src/app.module.ts:

import { AppThrottlerModule } from './throttler/throttler.module';

@Module({
  imports: [
    // ... other imports
    AppThrottlerModule,
  ],
})
export class AppModule {}

Phase 3: Apply Rate Limits to Controllers (2 hours)

Priority: High | Impact: High | Dependencies: Phase 2

1. Create Custom Decorators

Create apps/api/src/throttler/throttle.decorators.ts:

import { Throttle, SkipThrottle } from '@nestjs/throttler';

/**
 * Apply webhook rate limits (lower than default)
 */
export const WebhookThrottle = () => Throttle({ webhook: { ttl: 60000, limit: 50 } });

/**
 * Apply strict rate limits for sensitive endpoints
 */
export const AuthThrottle = () => Throttle({ auth: { ttl: 60000, limit: 10 } });

/**
 * Apply higher limits for read-heavy endpoints
 */
export const ReadThrottle = () => Throttle({ default: { ttl: 60000, limit: 200 } });

/**
 * Skip rate limiting (use sparingly, document why)
 */
export { SkipThrottle };

2. Apply to Webhook Controllers

Update apps/api/src/shopify/shopify.controller.ts:

import { WebhookThrottle } from '../throttler/throttle.decorators';

@Controller('webhooks/shopify')
@WebhookThrottle()
export class ShopifyController {
  @Post('orders/create')
  async handleOrderCreate(@Body() payload: unknown) {
    // ...
  }
}

Update apps/api/src/simplyprint/simplyprint-webhook.controller.ts:

import { WebhookThrottle } from '../throttler/throttle.decorators';

@Controller('webhooks/simplyprint')
@WebhookThrottle()
export class SimplyPrintWebhookController {
  // ...
}

Update apps/api/src/sendcloud/sendcloud.controller.ts:

import { WebhookThrottle } from '../throttler/throttle.decorators';

@Controller('webhooks/sendcloud')
@WebhookThrottle()
export class SendcloudController {
  // ...
}

3. Apply to Read-Heavy Endpoints

Update apps/api/src/orders/orders.controller.ts:

import { ReadThrottle } from '../throttler/throttle.decorators';

@Controller('api/v1/orders')
export class OrdersController {
  @Get()
  @ReadThrottle()
  async findAll(@Query() query: PaginationDto) {
    // Higher limit for list endpoints
  }

  @Get(':id')
  @ReadThrottle()
  async findOne(@Param('id') id: string) {
    // ...
  }
}

4. Skip Health Check Endpoints

Update apps/api/src/health/health.controller.ts:

import { SkipThrottle } from '../throttler/throttle.decorators';

@Controller('health')
@SkipThrottle()
export class HealthController {
  @Get()
  check() {
    return { status: 'ok' };
  }
}

Phase 4: Custom Rate Limit Storage (Optional, 2 hours)

Priority: Medium | Impact: Medium | Dependencies: Phase 3

For horizontal scaling, implement Redis-based storage:

1. Install Redis Throttler Storage

pnpm add @nestjs/throttler-storage-redis ioredis

2. Create Redis Storage Configuration

Update apps/api/src/throttler/throttler.module.ts:

import { ThrottlerStorageRedisService } from '@nestjs/throttler-storage-redis';
import Redis from 'ioredis';

@Module({
  imports: [
    ThrottlerModule.forRootAsync({
      inject: [AppConfigService],
      useFactory: (config: AppConfigService) => ({
        throttlers: [
          // ... throttlers config
        ],
        storage: config.redisUrl
          ? new ThrottlerStorageRedisService(new Redis(config.redisUrl))
          : undefined,
      }),
    }),
  ],
})
export class AppThrottlerModule {}

Phase 5: Add Rate Limit Headers (1 hour)

Priority: Medium | Impact: Medium | Dependencies: Phase 3

1. Create Rate Limit Header Interceptor

Create apps/api/src/throttler/rate-limit-headers.interceptor.ts:

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Response } from 'express';

@Injectable()
export class RateLimitHeadersInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    return next.handle().pipe(
      tap(() => {
        const response = context.switchToHttp().getResponse<Response>();

        // Headers are set by ThrottlerGuard, but we can add custom ones
        if (!response.headersSent) {
          response.setHeader('X-RateLimit-Policy', 'sliding-window');
        }
      }),
    );
  }
}

Phase 6: Testing (1 hour)

Priority: High | Impact: High | Dependencies: Phase 3

1. Create Throttler Unit Tests

Create apps/api/src/throttler/throttler.module.spec.ts:

import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import * as request from 'supertest';

describe('Rate Limiting', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [
        ThrottlerModule.forRoot({
          throttlers: [{ ttl: 60000, limit: 2 }],
        }),
      ],
      // ... test controller
    }).compile();

    app = moduleRef.createNestApplication();
    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  it('should allow requests under the limit', async () => {
    await request(app.getHttpServer())
      .get('/test')
      .expect(200);
  });

  it('should block requests over the limit', async () => {
    // Make requests up to limit
    await request(app.getHttpServer()).get('/test');
    await request(app.getHttpServer()).get('/test');

    // Third request should be blocked
    await request(app.getHttpServer())
      .get('/test')
      .expect(429);
  });
});

📁 Files to Create/Modify

New Files

apps/api/src/throttler/throttler.module.ts
apps/api/src/throttler/throttle.decorators.ts
apps/api/src/throttler/rate-limit-headers.interceptor.ts
apps/api/src/throttler/throttler.module.spec.ts

Modified Files

apps/api/package.json (add @nestjs/throttler)
apps/api/src/app.module.ts
apps/api/src/config/configuration.ts
apps/api/src/config/config.service.ts
apps/api/src/shopify/shopify.controller.ts
apps/api/src/simplyprint/simplyprint-webhook.controller.ts
apps/api/src/sendcloud/sendcloud.controller.ts
apps/api/src/orders/orders.controller.ts
apps/api/src/health/health.controller.ts
apps/api/.env.example

✅ Validation Checklist

  • @nestjs/throttler installed
  • ThrottlerModule configured with defaults
  • Configuration variables added for limits
  • Webhook controllers have lower limits
  • Health endpoints skip throttling
  • Rate limit headers returned in responses
  • Tests verify throttling behavior
  • pnpm nx build api passes
  • pnpm nx test api passes

Final Verification

# Build passes
pnpm nx build api

# Tests pass
pnpm nx test api

# Manual verification
curl -i http://localhost:3000/api/v1/orders
# Check for X-RateLimit-* headers

📝 .env.example Addition

# === Rate Limiting ===
RATE_LIMIT_TTL_SECONDS=60
RATE_LIMIT_DEFAULT=100
RATE_LIMIT_WEBHOOK=50
RATE_LIMIT_AUTH=10
REDIS_URL=redis://localhost:6379  # Optional, for distributed rate limiting

END OF PROMPT


This prompt resolves TD-012 from the technical debt register by implementing rate limiting on all public API endpoints.