Global Rate Limiting
Answered
Artois Hound posted this in #help-forum
Artois HoundOP
Is there a way to setup all APIs with rate limiting in one place? So I don't have to go through all APIs and add them manually.
Answered by Artois Hound
rn it's this
import { NextRequest } from "next/server";
// Store counts in a Map
const requestCounts = new Map<string, { count: number; timestamp: number }>();
// Clean up the map every minute
setInterval(() => {
const currentTimestamp = Date.now();
requestCounts.forEach((requests, ip) => {
if (currentTimestamp - requests.timestamp > 60000) {
requestCounts.delete(ip);
}
});
}, 60000);
export const config = {
matcher: "/api/:path*",
};
export function middleware(request: NextRequest) {
// Get client IP
const ip =
request.headers.get("x-real-ip") ||
request.headers.get("x-forwarded-for") ||
request.ip ||
"unknown";
if (ip === "unknown") {
return;
}
// Get current timestamp
const now = Date.now();
// Get requests from last 15 seconds
let requests = requestCounts.get(ip) || { count: 0, timestamp: now };
// Increment count
requests.count++;
// Check if past 15 seconds
if (now - requests.timestamp > 15000) {
// Reset count
requests.count = 1;
}
// Set timestamp
requests.timestamp = now;
// Set back to map
requestCounts.set(ip, requests);
// Check if rate limit exceeded
if (requests.count > 300) {
return new Response("Too Many Requests, please try again later.", {
status: 429,
});
}
// Continue request
return;
}68 Replies
this is when you could consider middleware
Artois HoundOP
Anything specific in mind?
like: https://nextjs.org/docs/app/building-your-application/routing/middleware (and you get the paths and everything)
Artois HoundOP
I have this for the matcher but I have no idea how to setup rate limiting with this.
how are you going to store the ratelimiting data?
Artois HoundOP
I have never setup ratelimiting before, what would you recommend?
umm i haven't done it yet, but i have heard good things with upstash ratelimiting: https://upstash.com/blog/upstash-ratelimit
Artois HoundOP
Oh, I don't think this is for me since I'll be running on VPS.
ohh well then you can use still use the library and host your own redis (but that might be over the top if for nothing else)
Artois HoundOP
What would happen if I just store it locally? I mean rate limiting is just temporary so it would get cleaned up quite often ig
well i think you need to store the data external to the nextjs app, as in same vps (but i have gotten cache to persist cross requests), so you could try making a lru cache or basic map to store the data ([like an example does](https://github.com/vercel/next.js/tree/canary/examples/api-routes-rate-limit/utils))
Artois HoundOP
I tried the LRU but it just didn't work for me
how did you do it? set at the root of file? or it just didn't woork self hosted?\
@riský how did you do it? set at the root of file? or it just didn't woork self hosted?\
Artois HoundOP
Idk the lru just didn't even wanna create the object, but it doesn't matter, I tried smth else and this kinda works on a basic level:
import { NextRequest } from 'next/server'
// Store counts in a Map
const requestCounts = new Map();
export const config = {
matcher: '/api/:path*',
}
export function middleware(request: NextRequest) {
// Get client IP
const ip = request.ip || 'unknown';
// Get current timestamp
const now = Date.now();
// Get requests from last 15 seconds
let requests = requestCounts.get(ip) || { count: 0, timestamp: now };
// Increment count
requests.count++;
// Check if past 15 seconds
if(now - requests.timestamp > 15000) {
// Reset count
requests.count = 1;
}
// Set timestamp
requests.timestamp = now;
// Set back to map
requestCounts.set(ip, requests);
// Check if rate limit exceeded
if(requests.count > 15) {
return new Response('Too Many Requests', {
status: 429
});
}
// Continue request
return;
}This works in the sense that it does rate limiting globally from one file, so no extra leg-work, I just don't know at what scale this will work 😅
Artois HoundOP
Well, I made some edits to this and it should be mostly fine, only time will tell.
also, you should be nice and provide the
x-rate-limit-reset and such data in header to be nice (but looks good otherwise)also personaly, i wouldn't set timestamp to now and instead just keep it until it is over 15 sec
like only
requests.timestamp = now in the past 15sec checkas your 15 request ratelimit is per 15sec? (its more for longer ratelimits because it can get annoying if you request it too early and then have to wait even longer)
Artois HoundOP
I edited it a bit
Artois HoundOP
rn it's this
import { NextRequest } from "next/server";
// Store counts in a Map
const requestCounts = new Map<string, { count: number; timestamp: number }>();
// Clean up the map every minute
setInterval(() => {
const currentTimestamp = Date.now();
requestCounts.forEach((requests, ip) => {
if (currentTimestamp - requests.timestamp > 60000) {
requestCounts.delete(ip);
}
});
}, 60000);
export const config = {
matcher: "/api/:path*",
};
export function middleware(request: NextRequest) {
// Get client IP
const ip =
request.headers.get("x-real-ip") ||
request.headers.get("x-forwarded-for") ||
request.ip ||
"unknown";
if (ip === "unknown") {
return;
}
// Get current timestamp
const now = Date.now();
// Get requests from last 15 seconds
let requests = requestCounts.get(ip) || { count: 0, timestamp: now };
// Increment count
requests.count++;
// Check if past 15 seconds
if (now - requests.timestamp > 15000) {
// Reset count
requests.count = 1;
}
// Set timestamp
requests.timestamp = now;
// Set back to map
requestCounts.set(ip, requests);
// Check if rate limit exceeded
if (requests.count > 300) {
return new Response("Too Many Requests, please try again later.", {
status: 429,
});
}
// Continue request
return;
}Answer
Artois HoundOP
Cuz 15 requests is a bit too little lol
I just want this as anti-DDoS
lol 300 in 15sec is alot
Artois HoundOP
well yeah but DDoS attacks usually do hundreds of requests per second lol
personally id be mean and slow down the requests after some figure of time
Artois HoundOP
At first I did 150 in 15 sec
Then I tried to do it in the app as a user
and I was able to get rate limited
obv not using the app as intended but still
The user shouldn't be rate limited
well, i don't know if the user should be able to send that many reqs?
is this a search bar that is running every query? (not debouncing)
Artois HoundOP
Nah, I was testing it on the profile and settings, gotta get quite a few requests there
I could probably make it less in the future tho
hmm that makes somewhat sense if you have lots fetch occuring for each page
but why not put more on server side rendering?
Artois HoundOP
wym SSR is by default ain't it
yeah, but im trying to see why you have that many requests
but now that i think about it, ip should be highish as multiple people can use the same ip
@riský but now that i think about it, ip should be highish as multiple people can use the same ip
Artois HoundOP
That's another thing lol
also 150 requests per 15 seconds is not that lot if you spam it
no normal user would do that probably
and can you show your code when using lru cache as i thought it can be used the same as this for local hosted?
@Artois Hound also 150 requests per 15 seconds is not that lot if you spam it
i did infact forget how long 15 sec is
Artois HoundOP
cannot show u using LRU cuz it didn't work
but just your failed code
Artois HoundOP
it just didn't wanna create the object and was giving me weird errors
I don't have the failed thing
its ok then
Artois HoundOP
I mean
i just wanted to know what was wrong with it
Artois HoundOP
I remove all IPs every 60 seconds so I don't store too much of it
its just lru cache would minimise need for the setInterval (either with their ttl or max length)
Artois HoundOP
I mean, fairly enough, but it's also kinda better to work with minimal dependencies when possible
i believe they might use map anyway
Artois HoundOP
I kinda took that approach to my entire project, most of my stuff is custom lol
good for learning 🙂
Artois HoundOP
Also good for practical stuff
I don't have to worry so much about it
I have enough when I already wanna update next.js and it breaks things 🤣
just one last recomendation would be to define your limit and time as a const at top of file to have easy acess to modify and see (but otherwise if your question solved, as looks good?)
Artois HoundOP
I think it's mostly fine, hopefully someone gets to use this too, for reference, the file is the prescribed middleware.ts
I deleted the default matcher thing for auth since I have authentication manually setup anyway
ill mark that as solution then 🙂