Rate Limiting Quick Start - nself-org/nchat GitHub Wiki
Rate Limiting & API Protection - Quick Start Guide
Get started with rate limiting and API protection in 5 minutes.
1. Setup (Optional: Redis)
For production, use Redis for distributed rate limiting:
# Option A: Local Redis (development)
docker run -d -p 6379:6379 redis:alpine
# Option B: Upstash (production - serverless)
# Sign up at https://upstash.com
# Copy your Redis URL
Add to .env.local:
# Redis (optional - uses in-memory if not set)
REDIS_URL=redis://localhost:6379
# OR for Upstash
UPSTASH_REDIS_URL=rediss://your-upstash-url
# CSRF Secret (required for production)
CSRF_SECRET=your-32-character-minimum-secret-here
2. Protect an API Route (Basic)
// src/app/api/my-endpoint/route.ts
import { NextRequest } from 'next/server'
import { applyRateLimit, RATE_LIMIT_PRESETS } from '@/lib/api/rate-limiter'
export async function POST(request: NextRequest) {
// 1. Apply rate limit
const result = await applyRateLimit(
request,
RATE_LIMIT_PRESETS.MESSAGE_SEND // 10 requests/min
)
if (!result.allowed) {
return Response.json(
{ error: 'Too many requests', retryAfter: result.retryAfter },
{ status: 429 }
)
}
// 2. Your business logic
return Response.json({ success: true })
}
3. Protect with Middleware (Advanced)
// src/app/api/protected/route.ts
import { compose, withErrorHandler, withAuth, withLogging } from '@/lib/api/middleware'
import { withCsrfProtection } from '@/lib/security/csrf'
// Stack multiple protections
export const POST = compose(
withErrorHandler, // Catch errors
withLogging, // Log requests
withCsrfProtection, // Prevent CSRF
withAuth // Require auth
)(async (request, context) => {
// Your handler - fully protected!
return Response.json({ success: true })
})
4. Client-Side: Get CSRF Token
// In your React component or API client
// 1. Get CSRF token
const getCsrfToken = async () => {
const response = await fetch('/api/csrf')
const { csrfToken, headerName } = await response.json()
return { csrfToken, headerName }
}
// 2. Use in POST requests
const makeProtectedRequest = async (data) => {
const { csrfToken, headerName } = await getCsrfToken()
const response = await fetch('/api/protected', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
[headerName]: csrfToken, // Usually 'X-CSRF-Token'
},
body: JSON.stringify(data),
})
return response.json()
}
5. Handle Rate Limit Responses
// Client-side error handling
const makeRequest = async () => {
const response = await fetch('/api/endpoint', {
method: 'POST',
body: JSON.stringify({ data: 'example' }),
})
if (response.status === 429) {
const error = await response.json()
const retryAfter = response.headers.get('Retry-After')
// Show user-friendly message
toast.error(`Too many requests. Please wait ${retryAfter} seconds.`)
// Optional: Auto-retry after delay
setTimeout(() => makeRequest(), parseInt(retryAfter) * 1000)
return
}
return response.json()
}
6. Admin: Manage IP Blocks
// Block an IP (requires admin role)
await fetch('/api/admin/ip-blocks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
ip: '192.168.1.1',
reason: 'Abuse detected',
duration: 3600, // 1 hour (0 = permanent)
}),
})
// View all blocked IPs
const { blocked, whitelist, blacklist, stats } = await fetch('/api/admin/ip-blocks').then((r) =>
r.json()
)
// Unblock an IP
await fetch('/api/admin/ip-blocks?ip=192.168.1.1', {
method: 'DELETE',
})
// Whitelist an IP (never block)
await fetch('/api/admin/ip-blocks/whitelist', {
method: 'POST',
body: JSON.stringify({ ip: '192.168.1.100' }),
})
7. Custom Rate Limits
import { rateLimiter } from '@/lib/api/rate-limiter'
// Create custom limit
const customLimit = {
maxRequests: 50,
windowSeconds: 300, // 5 minutes
burst: 10,
keyPrefix: 'rl:custom',
}
// Use it
const result = await rateLimiter.check('user:123', customLimit)
8. Record Abuse Events
import { recordAbuseFromRequest } from '@/lib/security/ip-blocker'
// In your API route
export async function POST(request: NextRequest) {
// ... authentication logic ...
if (loginFailed) {
// Record the abuse event
const wasBlocked = await recordAbuseFromRequest(request, 'FAILED_LOGIN', 'medium')
if (wasBlocked) {
// IP was auto-blocked due to too many failures
return Response.json({ error: 'IP blocked due to abuse' }, { status: 403 })
}
}
}
Available Rate Limit Presets
import { RATE_LIMIT_PRESETS } from '@/lib/api/rate-limiter'
// Authentication
RATE_LIMIT_PRESETS.AUTH // 5/min
RATE_LIMIT_PRESETS.AUTH_SIGNUP // 3/hour
RATE_LIMIT_PRESETS.AUTH_RESET // 3/15min
// Messaging
RATE_LIMIT_PRESETS.MESSAGE_SEND // 10/min + 5 burst
RATE_LIMIT_PRESETS.MESSAGE_EDIT // 20/min
// File Operations
RATE_LIMIT_PRESETS.FILE_UPLOAD // 5/min
RATE_LIMIT_PRESETS.FILE_UPLOAD_LARGE // 2/5min
// Search & AI
RATE_LIMIT_PRESETS.SEARCH // 20/min + 10 burst
RATE_LIMIT_PRESETS.AI_QUERY // 10/min
// General
RATE_LIMIT_PRESETS.API_USER // 100/min + 20 burst
RATE_LIMIT_PRESETS.API_IP // 500/min
RATE_LIMIT_PRESETS.GRAPHQL // 100/min
RATE_LIMIT_PRESETS.WEBHOOK // 50/min
Automatic IP Blocking Rules
The system automatically blocks IPs based on these patterns:
| Rule | Threshold | Window | Block Duration |
|---|---|---|---|
| FAILED_LOGIN | 10 failures | 15 min | 1 hour |
| RATE_LIMIT_ABUSE | 50 violations | 5 min | 2 hours |
| CSRF_VIOLATION | 3 violations | 5 min | 2 hours |
| SPAM_DETECTED | 20 messages | 1 hour | 12 hours |
| SQL_INJECTION | 1 attempt | instant | permanent |
| XSS_ATTEMPT | 1 attempt | instant | permanent |
Middleware Layers (Automatic)
Your middleware (src/middleware.ts) automatically applies:
- ✅ IP blocking check (whitelist/blacklist)
- ✅ Penalty box check (temporary blocks)
- ✅ Rate limiting (API routes)
- ✅ Security headers (CSP, HSTS, etc.)
Testing
# Run rate limiter tests
pnpm test src/lib/api/__tests__/rate-limiter.test.ts
# Test an endpoint manually
curl -X POST http://localhost:3000/api/test \
-H "Content-Type: application/json" \
-d '{"test":"data"}' \
-v # View headers including rate limit info
Common Patterns
Pattern 1: Public endpoint with IP-based limiting
export async function POST(request: NextRequest) {
const result = await applyRateLimit(
request,
RATE_LIMIT_PRESETS.API_IP // IP-based, high limit
)
if (!result.allowed) {
return Response.json({ error: 'Rate limited' }, { status: 429 })
}
// Public logic
}
Pattern 2: Authenticated endpoint with user-based limiting
export const POST = compose(withAuth)(async (request: AuthenticatedRequest) => {
const result = await applyRateLimit(
request,
RATE_LIMIT_PRESETS.MESSAGE_SEND,
`user:${request.user.id}` // User-specific limit
)
if (!result.allowed) {
return Response.json({ error: 'Rate limited' }, { status: 429 })
}
// Authenticated logic
})
Pattern 3: Combined protection (recommended)
export const POST = compose(
withErrorHandler, // Handle errors
withLogging, // Log requests
withCsrfProtection, // CSRF check
withAuth // Auth check
)(async (request: AuthenticatedRequest) => {
// Rate limit after auth
const result = await applyRateLimit(
request,
RATE_LIMIT_PRESETS.MESSAGE_SEND,
`user:${request.user.id}`
)
if (!result.allowed) {
return Response.json({ error: 'Rate limited' }, { status: 429 })
}
// Fully protected logic
})
Debugging
Check rate limit status without consuming
import { rateLimiter } from '@/lib/api/rate-limiter'
// Get status without incrementing counter
const status = await rateLimiter.status('user:123', RATE_LIMIT_PRESETS.MESSAGE_SEND)
console.log('Remaining:', status.remaining)
console.log('Reset at:', new Date(status.reset * 1000))
View rate limit headers
# Make a request and view headers
curl -X GET http://localhost:3000/api/endpoint -v
# Look for:
# X-RateLimit-Limit: 100
# X-RateLimit-Remaining: 95
# X-RateLimit-Reset: 1640000000
Clear rate limits (testing)
import { clearAllRateLimits } from '@/middleware/rate-limit'
// Clear all rate limits (useful in tests)
clearAllRateLimits()
Production Checklist
- [ ] Set
REDIS_URLorUPSTASH_REDIS_URL - [ ] Set strong
CSRF_SECRET(32+ characters) - [ ] Enable HTTPS
- [ ] Test rate limits with your traffic patterns
- [ ] Set up monitoring for rate limit violations
- [ ] Configure alerting for IP blocks
- [ ] Review and adjust preset limits
- [ ] Test CSRF protection
- [ ] Document rate limits for API consumers
Resources
- Full Documentation:
/docs/Rate-Limiting-API-Protection.md - Example Code:
/src/app/api/example-protected/route.ts - Tests:
/src/lib/api/__tests__/rate-limiter.test.ts - Implementation Status:
/RATE-LIMITING-IMPLEMENTATION.md
Need Help?
Common issues and solutions:
Rate limits not working?
- Check Redis connection
- Verify environment variables
- Check logs for errors
Too many false positives?
- Adjust thresholds
- Add IPs to whitelist
- Review block rules
Performance issues?
- Use Redis instead of in-memory
- Adjust cleanup intervals
- Check Redis memory usage
You're ready! 🚀
Start with Pattern 3 (combined protection) for most endpoints, and adjust rate limits based on your needs.