Canonical URL
Do not index
Do not index
Related to Authors (1) (Content)
I recently got the chance to use Cloudflare KV storage, and I didn’t realise how awesome it was until I used it.
The use case I used it for, is rate limiting one of my API routes. The only goal for rate limiting was to protect the input from bots.
I had tried using Cloudflare Turnstile (that verify you are a human check) but hadn’t had much success in using it. I kept getting reports from users saying “Hey! I’m not able to login. It says request is not authorised”.
I found that at times the check didn’t even appear to the users, not sure if it was a rendering issue with react or it was something remix was doing.
I tried everything to make it work, used the raw turnstile API, loaded the turnstile script in useEffect and at last even the react-turnstile package. Nothing really solved it. And I was just tired of going back and forth on it.
And then I took a step back and looked at what I was really trying to solve. It was restricting the bots, the bad actors so I thought why not just go about implementing the rate limit myself. My app runs on Cloudflare Workers, so using KV felt like the obvious choice.
The implementation begins by heading over to cloudflare KV and creating a new KV.
Step 1: Create a KV namespace
Head to Cloudflare KV → Create a new KV.
I named mine:
RATE_LIMIT_STORAGE
Once you create it, there is blue button on the right that shows you how to connect this KV to your codebase.

Checkout the wrangler.jsonc file for the configuration needed to make it work with your project.
Step 2: Add rate limiting logic to your route
Since I use Remix, I added this to the
action
function of my login page (which is basically just a server-side POST handler).export const action = async ({ request }) => {
// Get client IP address
const clientIP =
request.headers.get('CF-Connecting-IP') ||
request.headers.get('X-Forwarded-For')?.split(',')[0] ||
request.headers.get('X-Real-IP') ||
'unknown'
// Get pathname for route-specific rate limiting
const url = new URL(request.url)
const {pathname} = url
// Create rate limiting key with current minute and pathname
const currentMinute = Math.floor(Date.now() / 60_000) // Current minute as timestamp
const rateLimitKey = `rate_limit:${clientIP}:${pathname}:${currentMinute}`
// Check current count for this IP in this minute
const currentCount = await RATE_LIMIT_STORAGE.get(rateLimitKey)
const count = currentCount ? Number.parseInt(currentCount, 10) : 0
if (count >= 3) { // 3 is the number of times request is allowed per minute
return json(
{ error: 'Rate limit exceeded. Please try again in 1 minute.' },
{ status: 429 }
)
}
// Increment count and set expiration
await RATE_LIMIT_STORAGE.put(rateLimitKey, (count + 1).toString(), {
expirationTtl: 60, // Expire after 60 seconds
})
// login code that checks for authentication and logs the user
// ...
// ...
}
The restrictions I went for was 3 requests per minute. Very small number but I believe a human would be able to correct if they had typed the wrong email in 3 tries.
The beautiful line which has all the magic is await RATE_LIMIT_STORAGE.put(rateLimitKey… The stored object will just disappear from the storage after the expirationTtl time. No need for a delete logic.
The rate limiting is super simple here but I’m sure it won’t be that complex to add the logic for an exponential timer.
Most of my apps run on Cloudflare infra so I think KV is about to be my goto storage for such kind of feature implementations.