{
  "slug": "rate-limiter",
  "runtimes": {
    "node": {
      "frameworks": {
        "express": {
          "prompt": "Select rate limiter strategy:",
          "variants": {
            "express-rate-limit": {
              "label": "Express Rate Limiter",
              "dependencies": {
                "runtime": [
                  "express-rate-limit"
                ],
                "dev": []
              },
              "env": [],
              "architectures": {
                "mvc": {
                  "files": [
                    {
                      "type": "file",
                      "path": "src/utils/api-error.ts",
                      "content": "import { STATUS_CODES, StatusCode } from \"../constants/status-codes\";\n\nexport class ApiError extends Error {\n  public readonly statusCode: StatusCode;\n  public readonly isOperational: boolean;\n  public readonly errors?: unknown;\n\n  constructor(\n    statusCode: StatusCode,\n    message: string,\n    errors?: unknown,\n    isOperational = true\n  ) {\n    super(message);\n    this.name = \"ApiError\";\n    this.statusCode = statusCode;\n    this.errors = errors;\n    this.isOperational = isOperational;\n\n    Error.captureStackTrace(this, this.constructor);\n  }\n\n  static badRequest(message = \"Bad Request\", errors?: unknown) {\n    return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);\n  }\n\n  static unauthorized(message = \"Unauthorized\") {\n    return new ApiError(STATUS_CODES.UNAUTHORIZED, message);\n  }\n\n  static forbidden(message = \"Forbidden\") {\n    return new ApiError(STATUS_CODES.FORBIDDEN, message);\n  }\n\n  static notFound(message = \"Not Found\") {\n    return new ApiError(STATUS_CODES.NOT_FOUND, message);\n  }\n\n  static conflict(message = \"Conflict\") {\n    return new ApiError(STATUS_CODES.CONFLICT, message);\n  }\n\n  static validation(message = \"Validation failed\", errors?: unknown) {\n    return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);\n  }\n\n  static notImplemented(message = \"Not Implemented\") {\n    return new ApiError(STATUS_CODES.NOT_IMPLEMENTED, message);\n  }\n\n  static badGateway(message = \"Bad Gateway\") {\n    return new ApiError(STATUS_CODES.BAD_GATEWAY, message);\n  }\n\n  static serviceUnavailable(message = \"Service Unavailable\") {\n    return new ApiError(STATUS_CODES.SERVICE_UNAVAILABLE, message);\n  }\n\n  static tooManyRequests(message = \"Too Many Requests\") {\n    return new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, message);\n  }\n\n  static server(message = \"Internal Server Error\") {\n    return new ApiError(STATUS_CODES.INTERNAL_SERVER_ERROR, message);\n  }\n}\n\n/*\n * Usage:\n * throw new ApiError(STATUS_CODES.NOT_FOUND, \"Not found\");\n * throw ApiError.badRequest(\"Bad request\");\n */\n"
                    },
                    {
                      "type": "file",
                      "path": "src/middlewares/rate-limiter.ts",
                      "content": "import { rateLimit } from \"express-rate-limit\";\nimport { ApiError } from \"../utils/api-error\";\nimport { STATUS_CODES } from \"../constants/status-codes\";\n\n/**\n * Standard rate limiter middleware\n * Limits each IP to 100 requests per 15-minute window\n */\n\nexport const rateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 100, // Limit each IP to 100 requests per window\n  standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers\n  legacyHeaders: false, // Disable the `X-RateLimit-*` headers\n  message: {\n    success: false,\n    message:\n      \"Too many requests from this IP, please try again after 15 minutes\",\n    status: 429\n  },\n  handler: (req, res, next, options) => {\n    next(new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, options.message.message));\n  }\n});\n\n/**\n * Stricter rate limiter for sensitive routes (e.g., auth, login)\n */\nexport const authRateLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000, // 1 hour\n  max: 5, // Limit each IP to 5 failed attempts per hour\n  handler: (req, res, next, options) => {\n    next(\n      ApiError.tooManyRequests(\n        \"Too many login attempts, please try again after an hour\"\n      )\n    );\n  }\n});\n\n/**\n * Rate limiter for login route\n */\nexport const signinRateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many login attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\n/**\n * Rate limiter for registration route\n */\nexport const signupRateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many registration attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const otpRequestLimiter = rateLimit({\n  windowMs: 10 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many OTP requests. Please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const otpVerificationLimiter = rateLimit({\n  windowMs: 10 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many OTP verification attempts. Please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const resetPasswordLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many password reset attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const deleteAccountLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many account deletion attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const changePasswordLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many password change attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\n/**\n * Make sure global error handler is set up to handle ApiError\n */\n"
                    },
                    {
                      "type": "file",
                      "path": "src/constants/status-codes.ts",
                      "content": "export const STATUS_CODES = {\n  // 2xx Success\n  OK: 200,\n  CREATED: 201,\n  ACCEPTED: 202,\n  NO_CONTENT: 204,\n\n  // 3xx Redirection\n  MOVED_PERMANENTLY: 301,\n  FOUND: 302,\n  NOT_MODIFIED: 304,\n\n  // 4xx Client Errors\n  BAD_REQUEST: 400,\n  UNAUTHORIZED: 401,\n  FORBIDDEN: 403,\n  NOT_FOUND: 404,\n  CONFLICT: 409,\n  UNPROCESSABLE_ENTITY: 422,\n  TOO_MANY_REQUESTS: 429,\n\n  // 5xx Server Errors\n  INTERNAL_SERVER_ERROR: 500,\n  NOT_IMPLEMENTED: 501,\n  BAD_GATEWAY: 502,\n  SERVICE_UNAVAILABLE: 503,\n  GATEWAY_TIMEOUT: 504\n} as const;\n\nexport type StatusCode = (typeof STATUS_CODES)[keyof typeof STATUS_CODES];\n"
                    }
                  ]
                },
                "feature": {
                  "files": [
                    {
                      "type": "file",
                      "path": "src/shared/errors/api-error.ts",
                      "content": "import { STATUS_CODES, StatusCode } from \"../constants/status-codes\";\n\nexport class ApiError extends Error {\n  public readonly statusCode: StatusCode;\n  public readonly isOperational: boolean;\n  public readonly errors?: unknown;\n\n  constructor(\n    statusCode: StatusCode,\n    message: string,\n    errors?: unknown,\n    isOperational = true\n  ) {\n    super(message);\n    this.name = \"ApiError\";\n    this.statusCode = statusCode;\n    this.errors = errors;\n    this.isOperational = isOperational;\n\n    Error.captureStackTrace(this, this.constructor);\n  }\n\n  static badRequest(message = \"Bad Request\", errors?: unknown) {\n    return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);\n  }\n\n  static unauthorized(message = \"Unauthorized\") {\n    return new ApiError(STATUS_CODES.UNAUTHORIZED, message);\n  }\n\n  static forbidden(message = \"Forbidden\") {\n    return new ApiError(STATUS_CODES.FORBIDDEN, message);\n  }\n\n  static notFound(message = \"Not Found\") {\n    return new ApiError(STATUS_CODES.NOT_FOUND, message);\n  }\n\n  static conflict(message = \"Conflict\") {\n    return new ApiError(STATUS_CODES.CONFLICT, message);\n  }\n\n  static validation(message = \"Validation failed\", errors?: unknown) {\n    return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);\n  }\n\n  static notImplemented(message = \"Not Implemented\") {\n    return new ApiError(STATUS_CODES.NOT_IMPLEMENTED, message);\n  }\n\n  static badGateway(message = \"Bad Gateway\") {\n    return new ApiError(STATUS_CODES.BAD_GATEWAY, message);\n  }\n\n  static serviceUnavailable(message = \"Service Unavailable\") {\n    return new ApiError(STATUS_CODES.SERVICE_UNAVAILABLE, message);\n  }\n\n  static tooManyRequests(message = \"Too Many Requests\") {\n    return new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, message);\n  }\n\n  static server(message = \"Internal Server Error\") {\n    return new ApiError(STATUS_CODES.INTERNAL_SERVER_ERROR, message);\n  }\n}\n\n/*\n * Usage:\n * throw new ApiError(STATUS_CODES.NOT_FOUND, \"Not found\");\n * throw ApiError.badRequest(\"Bad request\");\n */\n"
                    },
                    {
                      "type": "file",
                      "path": "src/shared/constants/status-codes.ts",
                      "content": "export const STATUS_CODES = {\n  // 2xx Success\n  OK: 200,\n  CREATED: 201,\n  ACCEPTED: 202,\n  NO_CONTENT: 204,\n\n  // 3xx Redirection\n  MOVED_PERMANENTLY: 301,\n  FOUND: 302,\n  NOT_MODIFIED: 304,\n\n  // 4xx Client Errors\n  BAD_REQUEST: 400,\n  UNAUTHORIZED: 401,\n  FORBIDDEN: 403,\n  NOT_FOUND: 404,\n  CONFLICT: 409,\n  UNPROCESSABLE_ENTITY: 422,\n  TOO_MANY_REQUESTS: 429,\n\n  // 5xx Server Errors\n  INTERNAL_SERVER_ERROR: 500,\n  NOT_IMPLEMENTED: 501,\n  BAD_GATEWAY: 502,\n  SERVICE_UNAVAILABLE: 503,\n  GATEWAY_TIMEOUT: 504\n} as const;\n\nexport type StatusCode = (typeof STATUS_CODES)[keyof typeof STATUS_CODES];\n"
                    },
                    {
                      "type": "file",
                      "path": "src/shared/middlewares/rate-limiter.ts",
                      "content": "import { rateLimit } from \"express-rate-limit\";\nimport { ApiError } from \"../errors/api-error\";\nimport { STATUS_CODES } from \"../constants/status-codes\";\n\n/**\n * Standard rate limiter middleware\n * Limits each IP to 100 requests per 15-minute window\n */\n\nexport const rateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000, // 15 minutes\n  max: 100, // Limit each IP to 100 requests per window\n  standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers\n  legacyHeaders: false, // Disable the `X-RateLimit-*` headers\n  message: {\n    success: false,\n    message:\n      \"Too many requests from this IP, please try again after 15 minutes\",\n    status: 429\n  },\n  handler: (req, res, next, options) => {\n    next(new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, options.message.message));\n  }\n});\n\n/**\n * Stricter rate limiter for sensitive routes (e.g., auth, login)\n */\nexport const authRateLimiter = rateLimit({\n  windowMs: 60 * 60 * 1000, // 1 hour\n  max: 5, // Limit each IP to 5 failed attempts per hour\n  handler: (req, res, next, options) => {\n    next(\n      ApiError.tooManyRequests(\n        \"Too many login attempts, please try again after an hour\"\n      )\n    );\n  }\n});\n\n/**\n * Rate limiter for login route\n */\nexport const signinRateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many login attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\n/**\n * Rate limiter for registration route\n */\nexport const signupRateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many registration attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const otpRequestLimiter = rateLimit({\n  windowMs: 10 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many OTP requests. Please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const otpVerificationLimiter = rateLimit({\n  windowMs: 10 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many OTP verification attempts. Please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const resetPasswordLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 6,\n  message: {\n    success: false,\n    message: \"Too many password reset attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const deleteAccountLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many account deletion attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\nexport const changePasswordLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  message: {\n    success: false,\n    message: \"Too many password change attempts, please try again later.\",\n    statusCode: 429\n  },\n  standardHeaders: true,\n  legacyHeaders: false\n});\n\n/**\n * Make sure global error handler is set up to handle ApiError\n */\n"
                    }
                  ]
                }
              }
            },
            "upstash": {
              "label": "Upstash Rate Limiter",
              "dependencies": {
                "runtime": [
                  "@upstash/ratelimit",
                  "@upstash/redis"
                ],
                "dev": []
              },
              "env": [
                "UPSTASH_REDIS_REST_TOKEN",
                "UPSTASH_REDIS_REST_URL"
              ],
              "architectures": {
                "mvc": {
                  "files": [
                    {
                      "type": "file",
                      "path": "src/middlewares/rate-limiter.ts",
                      "content": "import { Ratelimit } from \"@upstash/ratelimit\";\r\nimport redis from \"../configs/redis\";\r\nimport { NextFunction, Request, Response } from \"express\";\r\n\r\ntype RateLimitStrategy = \"slidingWindow\" | \"fixedWindow\" | \"tokenBucket\";\r\n\r\ntype Duration =\r\n  | `${number} ms`\r\n  | `${number} s`\r\n  | `${number} m`\r\n  | `${number} h`\r\n  | `${number} d`;\r\n\r\ntype CreateRateLimitOptions = {\r\n  limit?: number;\r\n  duration?: Duration;\r\n  strategy?: RateLimitStrategy;\r\n  analytics?: boolean;\r\n  prefix?: string;\r\n  message?: string;\r\n};\r\n\r\n/**\r\n * @export\r\n * @param {CreateRateLimitOptions} {\r\n *   limit = 100,\r\n *   duration = \"1 m\",\r\n *   strategy = \"slidingWindow\",\r\n *   analytics = false,\r\n *   prefix = \"@servercn/ratelimit\",\r\n *   message = \"Too many requests. Please try again later.\"\r\n * }\r\n * @return {*}  {*}\r\n */\r\nexport function rateLimiter({\r\n  limit = 100,\r\n  duration = \"1 m\",\r\n  strategy = \"slidingWindow\",\r\n  analytics = false,\r\n  prefix = \"@servercn/ratelimit\",\r\n  message = \"Too many requests. Please try again later.\"\r\n}: CreateRateLimitOptions): any {\r\n  let limiter;\r\n\r\n  switch (strategy) {\r\n    case \"fixedWindow\":\r\n      limiter = Ratelimit.fixedWindow(limit, duration);\r\n      break;\r\n\r\n    case \"tokenBucket\":\r\n      limiter = Ratelimit.tokenBucket(limit, duration, limit);\r\n      break;\r\n\r\n    case \"slidingWindow\":\r\n    default:\r\n      limiter = Ratelimit.slidingWindow(limit, duration);\r\n  }\r\n\r\n  limiter = new Ratelimit({\r\n    redis,\r\n    limiter,\r\n    analytics,\r\n    prefix\r\n  });\r\n\r\n  return async (req: Request, res: Response, next: NextFunction) => {\r\n    try {\r\n      const ip =\r\n        req.ip || req.headers[\"x-forwarded-for\"]?.toString() || \"anonymous\";\r\n\r\n      const { success, limit, remaining, reset } = await limiter.limit(ip);\r\n\r\n      res.setHeader(\"X-RateLimit-Limit\", limit);\r\n      res.setHeader(\"X-RateLimit-Remaining\", remaining);\r\n      res.setHeader(\"X-RateLimit-Reset\", reset);\r\n\r\n      if (!success) {\r\n        return res.status(429).json({\r\n          success: false,\r\n          message,\r\n          status: 429\r\n        });\r\n      }\r\n\r\n      next();\r\n    } catch (error) {\r\n      next(error);\r\n    }\r\n  };\r\n}\r\n\r\n/*\r\n? 1. Global Rate Limit\r\nimport express from \"express\";\r\n\r\nconst app = express();\r\n\r\napp.use(\r\n  rateLimiter({\r\n    limit: 100,\r\n    duration: \"1 m\",\r\n    prefix: \"@servercn/global\"\r\n  })\r\n);\r\n\r\napp.listen(3000, () => {\r\n  console.log(\"Server is running on port 3000\");\r\n});\r\n\r\n? 2. Route-Specific Rate Limit\r\n\r\nrouter.post(\r\n  \"/login\",\r\n  rateLimiter({\r\n    limit: 10,\r\n    duration: \"1 m\",\r\n    strategy: \"fixedWindow\",\r\n    prefix: \"@servercn/login\",\r\n    message: \"Too many login attempts.\"\r\n  }),\r\n  loginController\r\n);\r\n*/\r\n"
                    },
                    {
                      "type": "file",
                      "path": "src/configs/redis.ts",
                      "content": "import { Redis } from \"@upstash/redis\";\r\n\r\nconst redis = new Redis({\r\n  url: process.env.UPSTASH_REDIS_REST_URL,\r\n  token: process.env.UPSTASH_REDIS_REST_TOKEN\r\n});\r\n\r\nexport default redis;"
                    }
                  ]
                },
                "feature": {
                  "files": [
                    {
                      "type": "file",
                      "path": "src/shared/middlewares/rate-limiter.ts",
                      "content": "import { Ratelimit } from \"@upstash/ratelimit\";\r\nimport redis from \"../configs/redis\";\r\nimport { NextFunction, Request, Response } from \"express\";\r\n\r\ntype RateLimitStrategy = \"slidingWindow\" | \"fixedWindow\" | \"tokenBucket\";\r\n\r\ntype Duration =\r\n  | `${number} ms`\r\n  | `${number} s`\r\n  | `${number} m`\r\n  | `${number} h`\r\n  | `${number} d`;\r\n\r\ntype CreateRateLimitOptions = {\r\n  limit?: number;\r\n  duration?: Duration;\r\n  strategy?: RateLimitStrategy;\r\n  analytics?: boolean;\r\n  prefix?: string;\r\n  message?: string;\r\n};\r\n\r\n/**\r\n * @export\r\n * @param {CreateRateLimitOptions} {\r\n *   limit = 100,\r\n *   duration = \"1 m\",\r\n *   strategy = \"slidingWindow\",\r\n *   analytics = false,\r\n *   prefix = \"@servercn/ratelimit\",\r\n *   message = \"Too many requests. Please try again later.\"\r\n * }\r\n * @return {*}  {*}\r\n */\r\nexport function rateLimiter({\r\n  limit = 100,\r\n  duration = \"1 m\",\r\n  strategy = \"slidingWindow\",\r\n  analytics = false,\r\n  prefix = \"@servercn/ratelimit\",\r\n  message = \"Too many requests. Please try again later.\"\r\n}: CreateRateLimitOptions): any {\r\n  let limiter;\r\n\r\n  switch (strategy) {\r\n    case \"fixedWindow\":\r\n      limiter = Ratelimit.fixedWindow(limit, duration);\r\n      break;\r\n\r\n    case \"tokenBucket\":\r\n      limiter = Ratelimit.tokenBucket(limit, duration, limit);\r\n      break;\r\n\r\n    case \"slidingWindow\":\r\n    default:\r\n      limiter = Ratelimit.slidingWindow(limit, duration);\r\n  }\r\n\r\n  limiter = new Ratelimit({\r\n    redis,\r\n    limiter,\r\n    analytics,\r\n    prefix\r\n  });\r\n\r\n  return async (req: Request, res: Response, next: NextFunction) => {\r\n    try {\r\n      const ip =\r\n        req.ip || req.headers[\"x-forwarded-for\"]?.toString() || \"anonymous\";\r\n\r\n      const { success, limit, remaining, reset } = await limiter.limit(ip);\r\n\r\n      res.setHeader(\"X-RateLimit-Limit\", limit);\r\n      res.setHeader(\"X-RateLimit-Remaining\", remaining);\r\n      res.setHeader(\"X-RateLimit-Reset\", reset);\r\n\r\n      if (!success) {\r\n        return res.status(429).json({\r\n          success: false,\r\n          message,\r\n          status: 429\r\n        });\r\n      }\r\n\r\n      next();\r\n    } catch (error) {\r\n      next(error);\r\n    }\r\n  };\r\n}\r\n\r\n/*\r\n? 1. Global Rate Limit\r\nimport express from \"express\";\r\n\r\nconst app = express();\r\n\r\napp.use(\r\n  rateLimiter({\r\n    limit: 100,\r\n    duration: \"1 m\",\r\n    prefix: \"@servercn/global\"\r\n  })\r\n);\r\n\r\napp.listen(3000, () => {\r\n  console.log(\"Server is running on port 3000\");\r\n});\r\n\r\n? 2. Route-Specific Rate Limit\r\n\r\nrouter.post(\r\n  \"/login\",\r\n  rateLimiter({\r\n    limit: 10,\r\n    duration: \"1 m\",\r\n    strategy: \"fixedWindow\",\r\n    prefix: \"@servercn/login\",\r\n    message: \"Too many login attempts.\"\r\n  }),\r\n  loginController\r\n);\r\n*/\r\n"
                    },
                    {
                      "type": "file",
                      "path": "src/shared/configs/redis.ts",
                      "content": "import { Redis } from \"@upstash/redis\";\r\n\r\nconst redis = new Redis({\r\n  url: process.env.UPSTASH_REDIS_REST_URL,\r\n  token: process.env.UPSTASH_REDIS_REST_TOKEN\r\n});\r\n\r\nexport default redis;\r\n"
                    }
                  ]
                }
              }
            },
            "ioredis": {
              "label": "IoRedis Rate Limiter",
              "dependencies": {
                "runtime": [
                  "ioredis"
                ],
                "dev": []
              },
              "env": [
                "REDIS_URL"
              ],
              "architectures": {
                "mvc": {
                  "files": [
                    {
                      "type": "file",
                      "path": "src/middlewares/rate-limiter.ts",
                      "content": "import type { Request, Response, NextFunction } from \"express\";\r\nimport redis from \"../configs/redis\";\r\n\r\n/**\r\n * @export\r\n * @param {number} [limit=100]\r\n * @param {number} [windowInSeconds=60]\r\n * @return {*}\r\n */\r\nexport function rateLimiter(limit = 100, windowInSeconds = 60) {\r\n  return async (req: Request, res: Response, next: NextFunction) => {\r\n    const key = `rate_limit:${req.ip}`;\r\n    const now = Date.now();\r\n    const windowStart = now - windowInSeconds * 1000;\r\n\r\n    await redis\r\n      .multi()\r\n      .zremrangebyscore(key, 0, windowStart)\r\n      .zadd(key, now, `${now}-${Math.random()}`)\r\n      .zcard(key)\r\n      .expire(key, windowInSeconds)\r\n      .exec();\r\n\r\n    const count = await redis.zcard(key);\r\n\r\n    if (count > limit) {\r\n      return res.status(429).json({\r\n        success: false,\r\n        status: 429,\r\n        message: \"Too many requests\"\r\n      });\r\n    }\r\n\r\n    next();\r\n  };\r\n}\r\n\r\n/**\r\n  Usage:\r\n \r\n  import { rateLimiter } from \"./middlewares/rate-limiter\";\r\n \r\n //* global\r\n  app.use(rateLimiter(5, 60));\r\n\r\n  //* per route\r\n  app.get(\"/\", rateLimiter(5, 60), (req, res) => {\r\n    res.send(\"Hello World!\");\r\n  });\r\n \r\n*/\r\n"
                    },
                    {
                      "type": "file",
                      "path": "src/configs/redis.ts",
                      "content": "import Redis from \"ioredis\";\r\n\r\nconst redis = new Redis(process.env.REDIS_URL!);\r\n\r\nredis.on(\"error\", err => console.log(\"Redis Client Error:\", err));\r\n\r\nexport default redis;\r\n"
                    }
                  ]
                },
                "feature": {
                  "files": [
                    {
                      "type": "file",
                      "path": "src/shared/middlewares/rate-limiter.ts",
                      "content": "import type { Request, Response, NextFunction } from \"express\";\r\nimport redis from \"../configs/redis\";\r\n\r\n/**\r\n * @export\r\n * @param {number} [limit=100]\r\n * @param {number} [windowInSeconds=60]\r\n * @return {*}\r\n */\r\nexport function rateLimiter(limit = 100, windowInSeconds = 60) {\r\n  return async (req: Request, res: Response, next: NextFunction) => {\r\n    const key = `rate_limit:${req.ip}`;\r\n    const now = Date.now();\r\n    const windowStart = now - windowInSeconds * 1000;\r\n\r\n    await redis\r\n      .multi()\r\n      .zremrangebyscore(key, 0, windowStart)\r\n      .zadd(key, now, `${now}-${Math.random()}`)\r\n      .zcard(key)\r\n      .expire(key, windowInSeconds)\r\n      .exec();\r\n\r\n    const count = await redis.zcard(key);\r\n\r\n    if (count > limit) {\r\n      return res.status(429).json({\r\n        success: false,\r\n        status: 429,\r\n        message: \"Too many requests\"\r\n      });\r\n    }\r\n\r\n    next();\r\n  };\r\n}\r\n\r\n/**\r\n  Usage:\r\n \r\n  import { rateLimiter } from \"./middlewares/rate-limiter\";\r\n \r\n //* global\r\n  app.use(rateLimiter(5, 60));\r\n\r\n  //* per route\r\n  app.get(\"/\", rateLimiter(5, 60), (req, res) => {\r\n    res.send(\"Hello World!\");\r\n  });\r\n \r\n*/\r\n"
                    },
                    {
                      "type": "file",
                      "path": "src/shared/configs/redis.ts",
                      "content": "import Redis from \"ioredis\";\r\n\r\nconst redis = new Redis(process.env.REDIS_URL!);\r\n\r\nredis.on(\"error\", err => console.log(\"Redis Client Error:\", err));\r\n\r\nexport default redis;\r\n"
                    }
                  ]
                }
              }
            }
          }
        },
        "nextjs": {
          "prompt": "Select rate limiter strategy:",
          "variants": {
            "custom": {
              "label": "Custom Rate Limiter",
              "dependencies": {
                "runtime": [],
                "dev": []
              },
              "env": [],
              "architectures": {
                "file-api": {
                  "files": [
                    {
                      "type": "file",
                      "path": "src/lib/rate-limiter.ts",
                      "content": "import { NextRequest } from \"next/server\";\r\n\r\nexport const RATE_LIMIT_WINDOW = 1 * 60 * 1000; // 1 minute\r\nexport const RATE_LIMIT_MAX_REQUESTS = 5 as const;\r\n\r\nconst rateLimitStore = new Map<string, { count: number; resetTime: number }>();\r\n\r\nexport function getClientIP(request: NextRequest): string {\r\n  return (\r\n    request.headers.get(\"x-forwarded-for\")?.split(\",\")[0].trim() ||\r\n    request.headers.get(\"x-real-ip\") ||\r\n    request.headers.get(\"cf-connecting-ip\") ||\r\n    \"unknown\"\r\n  );\r\n}\r\n\r\nexport function resetRateLimit(clientIP: string) {\r\n  rateLimitStore.delete(clientIP);\r\n}\r\ntype RateLimit = {\r\n  allowed: boolean;\r\n  remaining: number;\r\n  resetTime: number;\r\n};\r\n\r\nexport function checkRateLimit(clientIP: string): RateLimit {\r\n  const now = Date.now();\r\n  const data = rateLimitStore.get(clientIP);\r\n\r\n  if (!data || now > data.resetTime) {\r\n    rateLimitStore.set(clientIP, {\r\n      count: 1,\r\n      resetTime: now + RATE_LIMIT_WINDOW\r\n    });\r\n    return {\r\n      allowed: true,\r\n      remaining: RATE_LIMIT_MAX_REQUESTS - 1,\r\n      resetTime: data?.resetTime || 0\r\n    };\r\n  }\r\n\r\n  if (data.count >= RATE_LIMIT_MAX_REQUESTS) {\r\n    return { allowed: false, remaining: 0, resetTime: data?.resetTime || 0 };\r\n  }\r\n\r\n  data.count++;\r\n  rateLimitStore.set(clientIP, data);\r\n\r\n  return {\r\n    allowed: true,\r\n    remaining: RATE_LIMIT_MAX_REQUESTS - data.count,\r\n    resetTime: data?.resetTime || 0\r\n  };\r\n}\r\n\r\n/**\r\n * ? Usage:\r\nexport const POST = async (req: NextRequest) => {\r\n  const clientIP = getClientIP(req);\r\n  const rateLimit = checkRateLimit(clientIP);\r\n\r\n  if (!rateLimit.allowed) {\r\n    return NextResponse.json(\r\n      {\r\n        message: \"Too many requests, please try again later.\"\r\n      },\r\n      {\r\n        status: 429,\r\n        headers: {\r\n          \"X-RateLimit-Limit\": RATE_LIMIT_MAX_REQUESTS.toString(),\r\n          \"X-RateLimit-Remaining\": rateLimit.remaining.toString(),\r\n          \"X-RateLimit-Reset\": new Date(rateLimit.resetTime)\r\n            .toISOString()\r\n            .substring(0, 19)\r\n            .replace(\"T\", \" \")\r\n            .toString()\r\n        }\r\n      }\r\n    );\r\n  }\r\n  \r\n  //* Your API logic here\r\n};\r\n*/\r\n"
                    }
                  ]
                }
              }
            },
            "upstash": {
              "label": "Upstash Rate Limiter",
              "dependencies": {
                "runtime": [
                  "@upstash/ratelimit",
                  "@upstash/redis",
                  "zod"
                ],
                "dev": []
              },
              "env": [
                "UPSTASH_REDIS_REST_TOKEN",
                "UPSTASH_REDIS_REST_URL"
              ],
              "architectures": {
                "file-api": {
                  "files": [
                    {
                      "type": "file",
                      "path": "src/configs/redis.ts",
                      "content": "import { Redis } from \"@upstash/redis\";\r\n\r\nimport env from \"@/configs/env\";\r\n\r\nconst redis = new Redis({\r\n  url: env.UPSTASH_REDIS_REST_URL,\r\n  token: env.UPSTASH_REDIS_REST_TOKEN\r\n});\r\n\r\nexport default redis;\r\n"
                    },
                    {
                      "type": "file",
                      "path": "src/configs/env.ts",
                      "content": "import z from \"zod\";\r\n\r\nexport const envSchema = z.object({\r\n  UPSTASH_REDIS_REST_TOKEN: z.string(),\r\n  UPSTASH_REDIS_REST_URL: z.string()\r\n});\r\n\r\nexport type Env = z.infer<typeof envSchema>;\r\n\r\nconst result = envSchema.safeParse(process.env);\r\n\r\nif (!result.success) {\r\n  console.error(\"❌ Invalid environment configuration\");\r\n  console.error(z.prettifyError(result.error));\r\n  process.exit(1);\r\n}\r\n\r\nexport const env: Readonly<Env> = Object.freeze(result.data);\r\n\r\nexport default env;\r\n"
                    },
                    {
                      "type": "file",
                      "path": "src/lib/rate-limiter.ts",
                      "content": "import { Ratelimit } from \"@upstash/ratelimit\";\r\n\r\nimport redis from \"@/configs/redis\";\r\n\r\nexport const ratelimit = new Ratelimit({\r\n  redis: redis,\r\n  limiter: Ratelimit.fixedWindow(100, \"10 s\"), //* 100 requests per 10 seconds\r\n  ephemeralCache: new Map(), //* cache for the ratelimit\r\n  prefix: \"@servercn/ratelimit\" //* prefix for the ratelimit\r\n});\r\n\r\n/**\r\n * ? Usage:\r\n \r\nimport { ratelimit } from \"@/lib/rate-limiter\";\r\n\r\n* * 1. proxy.ts (middleware)\r\n\r\nimport { NextResponse } from \"next/server\";\r\nimport type { NextRequest } from \"next/server\";\r\n\r\nexport async function proxy(request: NextRequest) {\r\n  const ip =\r\n    request.headers.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ??\r\n    \"127.0.0.1\";\r\n\r\n  const { success, limit, remaining } = await ratelimit.limit(ip);\r\n  const response = NextResponse.next();\r\n\r\n  if (!success) {\r\n    response.headers.set(\"X-RateLimit-Success\", success.toString());\r\n    response.headers.set(\"X-RateLimit-Limit\", limit.toString());\r\n    response.headers.set(\"X-RateLimit-Remaining\", remaining.toString());\r\n    return response;\r\n  }\r\n\r\n  return NextResponse.next();\r\n}\r\n\r\nexport const config = {\r\n  matcher: [\"/api/:path*\"]\r\n};\r\n\r\n* * 2. API Routes\r\n\r\nexport const POST = async (req: NextRequest) => {\r\n  const ip =\r\n    req.headers.get(\"x-forwarded-for\")?.split(\",\")[0]?.trim() ?? \"127.0.0.1\";\r\n\r\n  const { success, limit, remaining, reset } = await ratelimit.limit(ip);\r\n\r\n  if (!success) {\r\n    return NextResponse.json(\r\n      {\r\n        message: \"Too many requests, please try again later.\"\r\n      },\r\n      {\r\n        status: 429,\r\n        headers: {\r\n          \"X-RateLimit-Limit\": limit.toString(),\r\n          \"X-RateLimit-Remaining\": remaining.toString(),\r\n          \"X-RateLimit-Reset\": reset.toString()\r\n        }\r\n      }\r\n    );\r\n  }\r\n\r\n  //* Your API logic here\r\n};\r\n\r\n** Official Docs:\r\n* https://upstash.com/docs/redis/sdks/ratelimit-ts/gettingstarted\r\n* https://github.com/upstash/ratelimit-js/tree/main/examples/nextjs\r\n* https://github.com/upstash/ratelimit-js/tree/main/examples/nextjs-middleware\r\n**/\r\n"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}
