yarn add lru-cache request-ip
yarn add -D @types/lru-cache @types/request-ip
import LRU from "lru-cache";
import type { NextApiResponse } from "next";
type CheckLimitFunc = () => {
check: (res: NextApiResponse, limit: number, ipAddress: string) => Promise<void>;
};
export const LimitChecker: CheckLimitFunc = () => {
const tokenCache = new LRU<string, number>({
max: 500, // Max 500 users per interval
maxAge: 1000 * 60 * 5, // 5分,
});
return {
check: (res, limit, token): Promise<void> =>
new Promise((resolve, reject) => {
const tokenCount = tokenCache.get(token) || 0;
const currentUsage = tokenCount + 1;
tokenCache.set(token, currentUsage);
const isRateLimited = currentUsage > limit;
res.setHeader("X-RateLimit-Limit", limit);
res.setHeader("X-RateLimit-Remaining", isRateLimited ? 0 : limit - currentUsage);
return isRateLimited ? reject("Too Many Requests") : resolve();
}),
};
};
// pageExtensions の設定をしているので .page.ts になっています
import requestIp from "request-ip";
import type { NextApiRequest, NextApiResponse } from "next";
import { LimitChecker } from "@/lib/limitChecker";
const limitChecker = LimitChecker();
type Data = {
text: string;
clientIp: string;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>): Promise<void> {
const clientIp = requestIp.getClientIp(req) || "IP_NOT_FOUND";
try {
await limitChecker.check(res, 3, clientIp);
} catch (error) {
console.error(error);
res.status(429).json({
text: `Rate Limited`,
clientIp: clientIp,
});
return;
}
res.status(200).json({
text: `テキスト`,
clientIp: clientIp,
});
}