Social Platform Rate Limiting
Social platforms are prime targets for spam, bots, and coordinated inauthentic behavior. This example shows how to implement rate limiting that stops abuse while keeping the platform enjoyable for real users.
Scenario
- Posts: 10 per hour (prevents spam posting)
- Comments: 30 per hour (allows engagement, stops flooding)
- Likes: 100 per hour (generous but prevents mass-liking bots)
- Follows: 50 per hour (prevents follow/unfollow spam)
- Direct messages: 20 per hour to new users (anti-spam)
- Profile views: 100 per hour (prevents stalking/scraping)
Implementation
src/middleware/social-limits.ts
import { hitlimit } from '@joint-ops/hitlimit'
import { redisStore } from '@joint-ops/hitlimit/stores/redis'
const redis = redisStore({
url: process.env.REDIS_URL,
keyPrefix: 'social:rl:'
})
// Posts - moderate limit with verification bonus
export const postLimit = hitlimit({
store: redis,
limit: (req) => {
// Verified accounts get higher limits
if (req.user?.verified) return 50
if (req.user?.premium) return 25
return 10
},
window: '1h',
key: (req) => `post:${ req.user.id }`,
response: (info) => ({
error: 'POST_RATE_LIMITED',
message: `You've reached your posting limit. Try again in ${ Math.ceil(info.resetIn / 60) } minutes.`,
remaining: info.remaining,
resetIn: info.resetIn
})
})
// Comments - higher limit for engagement
export const commentLimit = hitlimit({
store: redis,
limit: 30,
window: '1h',
key: (req) => `comment:${ req.user.id }`,
response: () => ({
error: 'COMMENT_RATE_LIMITED',
message: 'Slow down! You\'re commenting too fast.'
})
})
// Likes - generous but limited
export const likeLimit = hitlimit({
store: redis,
limit: 100,
window: '1h',
key: (req) => `like:${ req.user.id }`,
response: () => ({
error: 'LIKE_RATE_LIMITED',
message: 'You\'ve liked a lot of content! Take a break.'
})
})
// Follow/unfollow - prevent follow spam
export const followLimit = hitlimit({
store: redis,
limit: 50,
window: '1h',
key: (req) => `follow:${ req.user.id }`,
response: () => ({
error: 'FOLLOW_RATE_LIMITED',
message: 'You\'re following too fast. Please slow down.'
})
})
// Direct messages - stricter for new conversations
export const dmLimit = hitlimit({
store: redis,
limit: 20,
window: '1h',
key: (req) => {
// Different limits for new vs existing conversations
const isNewConversation = !req.body.existingThread
if (isNewConversation) {
return `dm:new:${ req.user.id }`
}
return `dm:existing:${ req.user.id }`
},
limit: (req) => {
// Stricter for new conversations (spam prevention)
return req.body.existingThread ? 100 : 20
},
response: () => ({
error: 'DM_RATE_LIMITED',
message: 'You\'ve sent too many messages. Please wait.'
})
})
// Profile views - prevent scraping/stalking
export const profileViewLimit = hitlimit({
store: redis,
limit: 100,
window: '1h',
key: (req) => `profile-view:${ req.user?.id || req.ip }`,
response: () => ({
error: 'PROFILE_VIEW_LIMITED',
message: 'Slow down. You\'re viewing profiles too quickly.'
})
}) Route Configuration
src/routes/social.ts
import { Router } from 'express'
import { requireAuth } from '../middleware/auth'
import * as limits from '../middleware/social-limits'
const router = Router()
// All social actions require authentication
router.use(requireAuth)
// Content creation
router.post('/posts', limits.postLimit, postController.create)
router.post('/posts/:id/comments', limits.commentLimit, commentController.create)
// Engagement
router.post('/posts/:id/like', limits.likeLimit, likeController.toggle)
router.post('/comments/:id/like', limits.likeLimit, likeController.toggle)
// Following
router.post('/users/:id/follow', limits.followLimit, followController.follow)
router.delete('/users/:id/follow', limits.followLimit, followController.unfollow)
// Messaging
router.post('/messages', limits.dmLimit, messageController.send)
// Profile viewing
router.get('/users/:id', limits.profileViewLimit, userController.getProfile)
export default router Anti-Spam Patterns
src/middleware/anti-spam.ts
import { hitlimit } from '@joint-ops/hitlimit'
import { redisStore } from '@joint-ops/hitlimit/stores/redis'
const redis = redisStore({ url: process.env.REDIS_URL })
// Prevent duplicate content spam
export const duplicateContentLimit = hitlimit({
store: redis,
limit: 1,
window: '5m',
key: (req) => {
// Hash the content to detect duplicates
const contentHash = hashContent(req.body.content)
return `dup:${ req.user.id }:${ contentHash }`
},
response: () => ({
error: 'DUPLICATE_CONTENT',
message: 'You already posted this. Please wait before posting similar content.'
})
})
// Rate limit @mentions to prevent spam tagging
export const mentionLimit = hitlimit({
store: redis,
limit: 20,
window: '1h',
key: (req) => `mentions:${ req.user.id }`,
skip: (req) => {
// Count mentions in content
const mentions = (req.body.content.match(/@\w+/g) || []).length
return mentions === 0
},
response: () => ({
error: 'MENTION_RATE_LIMITED',
message: 'You\'re mentioning too many people. Please slow down.'
})
})
// Hashtag spam prevention
export const hashtagLimit = hitlimit({
store: redis,
limit: 50,
window: '1h',
key: (req) => `hashtags:${ req.user.id }`,
skip: (req) => {
const hashtags = (req.body.content.match(/#\w+/g) || []).length
return hashtags <= 5 // Allow up to 5 hashtags per post
},
response: () => ({
error: 'HASHTAG_SPAM',
message: 'Too many hashtags. Please use fewer hashtags in your posts.'
})
}) Trust-Based Limits
Established users with good standing should have more freedom than new accounts. Implement trust tiers based on account age and behavior.
src/middleware/trust-tiers.ts
import { hitlimit } from '@joint-ops/hitlimit'
import { redisStore } from '@joint-ops/hitlimit/stores/redis'
const redis = redisStore({ url: process.env.REDIS_URL })
type TrustLevel = 'new' | 'basic' | 'trusted' | 'verified'
const getTrustLevel = (user: User): TrustLevel => {
if (user.verified) return 'verified'
const accountAge = Date.now() - new Date(user.createdAt).getTime()
const daysSinceCreation = accountAge / (1000 * 60 * 60 * 24)
if (daysSinceCreation < 7) return 'new'
if (daysSinceCreation < 30) return 'basic'
return 'trusted'
}
const limits: Record<TrustLevel, number> = {
new: 5, // Very strict for new accounts
basic: 15, // Building trust
trusted: 30, // Established users
verified: 100 // Verified accounts
}
export const tieredPostLimit = hitlimit({
store: redis,
limit: (req) => limits[getTrustLevel(req.user)],
window: '1h',
key: (req) => `post:${ req.user.id }`,
response: (info, req) => {
const trust = getTrustLevel(req.user)
return {
error: 'POST_RATE_LIMITED',
message: `Posting limit reached. ${ trust === 'new' ? 'New accounts have lower limits.' : '' }`,
trustLevel: trust,
limit: limits[trust]
}
}
}) Social Platform Best Practices
- Graduated limits: New accounts should have stricter limits that relax over time
- Verified user bonuses: Reward trusted users with higher limits
- Separate new vs existing DMs: Starting conversations with strangers is a spam vector
- Content deduplication: Detect and limit identical or near-identical posts
- Action-specific limits: Different actions need different thresholds
- Consider follow/unfollow cycles: Rate limit both actions together to prevent gaming