├── .github ├── img │ └── dashboard.png └── workflows │ ├── release.yml │ └── tests.yaml ├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── bun.lockb ├── cmd └── set-version.js ├── context7.json ├── eslint.config.mjs ├── examples ├── cloudflare-pages │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── api │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── not-found.tsx │ │ └── page.tsx │ ├── env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── wrangler.toml ├── cloudflare-workers │ ├── .gitignore │ ├── README.md │ ├── bun.lockb │ ├── ci.test.ts │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── wrangler.toml ├── deno │ ├── README.md │ ├── deprecated.ts │ └── main.ts ├── enable-protection │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── api │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── tailwind.config.ts │ └── tsconfig.json ├── nextjs-middleware │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── api │ │ │ ├── blocked │ │ │ │ └── route.tsx │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── middleware.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── tailwind.config.ts │ └── tsconfig.json ├── nextjs │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── api │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── bun.lockb │ ├── ci.test.ts │ ├── next-env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── pages │ │ └── api │ │ │ └── pages-test.ts │ ├── postcss.config.mjs │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── tailwind.config.ts │ └── tsconfig.json ├── remix │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── root.tsx │ │ └── routes │ │ │ └── index.tsx │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── remix.config.js │ ├── remix.env.d.ts │ ├── server.js │ └── tsconfig.json ├── vercel-edge │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── app │ │ ├── api │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── tailwind.config.ts │ └── tsconfig.json └── with-vercel-kv │ ├── .gitignore │ ├── README.md │ ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── next.svg │ └── vercel.svg │ ├── tailwind.config.js │ └── tsconfig.json ├── package.json ├── prettier.config.js ├── src ├── analytics.test.ts ├── analytics.ts ├── blockUntilReady.test.ts ├── cache.test.ts ├── cache.ts ├── deny-list │ ├── deny-list.test.ts │ ├── deny-list.ts │ ├── index.ts │ ├── integration.test.ts │ ├── ip-deny-list.test.ts │ ├── ip-deny-list.ts │ ├── scripts.test.ts │ ├── scripts.ts │ ├── time.test.ts │ └── time.ts ├── duration.test.ts ├── duration.ts ├── getRemainingTokens.test.ts ├── hash.test.ts ├── hash.ts ├── index.ts ├── lua-scripts │ ├── hash.test.ts │ ├── hash.ts │ ├── multi.ts │ ├── reset.ts │ └── single.ts ├── multi.ts ├── ratelimit.test.ts ├── ratelimit.ts ├── resetUsedTokens.test.ts ├── single.ts ├── test_utils.ts ├── tools │ └── seed.ts └── types.ts ├── tsconfig.json ├── tsup.config.js └── turbo.json /.github/img/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/ratelimit-js/a879eff5a9340ea3a523aa624ba318d40c0d3615/.github/img/dashboard.png -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: 12 | group: large-runners 13 | labels: ubuntu-4x 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v3 17 | 18 | - name: Set env 19 | run: echo "VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 20 | 21 | - name: Setup Node 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: 18 25 | 26 | - name: Set package version 27 | run: echo $(jq --arg v "${{ env.VERSION }}" '(.version) = $v' package.json) > package.json 28 | 29 | - name: Setup Bun 30 | uses: oven-sh/setup-bun@v1 31 | with: 32 | bun-version: latest 33 | 34 | - name: Install dependencies 35 | run: bun install 36 | 37 | - name: Build 38 | run: bun run build 39 | 40 | - name: Add npm token 41 | run: echo "//registry.npmjs.org/:_authToken=${{secrets.NPM_TOKEN}}" > .npmrc 42 | 43 | - name: Publish release candidate 44 | if: "github.event.release.prerelease" 45 | run: npm publish --access public --tag=canary --no-git-checks 46 | 47 | - name: Publish 48 | if: "!github.event.release.prerelease" 49 | run: npm publish --access public --no-git-checks 50 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | pull_request: 4 | schedule: 5 | - cron: "0 0 * * *" # daily 6 | 7 | env: 8 | UPSTASH_REDIS_REST_URL: ${{ secrets.UPSTASH_REDIS_REST_URL }} 9 | UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_REDIS_REST_TOKEN }} 10 | 11 | US1_UPSTASH_REDIS_REST_URL: ${{ secrets.US1_UPSTASH_REDIS_REST_URL }} 12 | US1_UPSTASH_REDIS_REST_TOKEN: ${{ secrets.US1_UPSTASH_REDIS_REST_TOKEN }} 13 | 14 | APN_UPSTASH_REDIS_REST_URL: ${{ secrets.APN_UPSTASH_REDIS_REST_URL }} 15 | APN_UPSTASH_REDIS_REST_TOKEN: ${{ secrets.APN_UPSTASH_REDIS_REST_TOKEN }} 16 | 17 | EU2_UPSTASH_REDIS_REST_URL: ${{ secrets.EU2_UPSTASH_REDIS_REST_URL }} 18 | EU2_UPSTASH_REDIS_REST_TOKEN: ${{ secrets.EU2_UPSTASH_REDIS_REST_TOKEN }} 19 | 20 | jobs: 21 | test: 22 | runs-on: 23 | group: large-runners 24 | labels: ubuntu-4x 25 | name: Tests 26 | steps: 27 | - name: Setup repo 28 | uses: actions/checkout@v2 29 | 30 | - name: Setup Bun 31 | uses: oven-sh/setup-bun@v1 32 | with: 33 | bun-version: latest 34 | 35 | - name: Install dependencies 36 | run: bun install 37 | 38 | - name: Run tests 39 | run: bun run test 40 | 41 | cloudflare-workers-local: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Setup repo 45 | uses: actions/checkout@v3 46 | - name: Setup nodejs 47 | uses: actions/setup-node@v3 48 | 49 | - name: Setup Bun 50 | uses: oven-sh/setup-bun@v1 51 | with: 52 | bun-version: latest 53 | 54 | - name: Install dependencies 55 | run: bun install 56 | 57 | - name: Build 58 | run: bun run build 59 | 60 | - name: Install example 61 | run: bun add @upstash/ratelimit@../.. 62 | working-directory: examples/cloudflare-workers 63 | 64 | - name: Add environment 65 | run: | 66 | echo '[vars]' >> wrangler.toml 67 | echo "UPSTASH_REDIS_REST_URL = \"$UPSTASH_REDIS_REST_URL\"" >> ./wrangler.toml 68 | echo "UPSTASH_REDIS_REST_TOKEN = \"$UPSTASH_REDIS_REST_TOKEN\"" >> ./wrangler.toml 69 | working-directory: examples/cloudflare-workers 70 | 71 | - name: Start example 72 | run: bun dev & 73 | working-directory: examples/cloudflare-workers 74 | 75 | - name: Run tests 76 | run: bun test ci.test.ts 77 | working-directory: examples/cloudflare-workers 78 | env: 79 | DEPLOYMENT_URL: http://127.0.0.1:8787 80 | 81 | cloudflare-workers-deployed: 82 | concurrency: cloudflare-workers-deployed 83 | needs: 84 | - release 85 | runs-on: ubuntu-latest 86 | steps: 87 | - name: Setup repo 88 | uses: actions/checkout@v3 89 | - name: Setup nodejs 90 | uses: actions/setup-node@v3 91 | with: 92 | node-version: 18 93 | 94 | - name: Setup Bun 95 | uses: oven-sh/setup-bun@v1 96 | with: 97 | bun-version: latest 98 | 99 | - name: Install example 100 | run: | 101 | bun add @upstash/ratelimit@${{needs.release.outputs.version}} 102 | npm i -g wrangler 103 | working-directory: examples/cloudflare-workers 104 | 105 | - name: Add account ID 106 | run: echo 'account_id = "${{ secrets.CLOUDFLARE_ACCOUNT_ID }}"' >> wrangler.toml 107 | working-directory: examples/cloudflare-workers 108 | 109 | - name: Add environment 110 | run: | 111 | echo '[vars]' >> wrangler.toml 112 | echo "UPSTASH_REDIS_REST_URL = \"$UPSTASH_REDIS_REST_URL\"" >> ./wrangler.toml 113 | echo "UPSTASH_REDIS_REST_TOKEN = \"$UPSTASH_REDIS_REST_TOKEN\"" >> ./wrangler.toml 114 | working-directory: examples/cloudflare-workers 115 | 116 | - name: Deploy 117 | run: wrangler publish 118 | working-directory: examples/cloudflare-workers 119 | env: 120 | CLOUDFLARE_API_TOKEN: ${{secrets.CLOUDFLARE_API_TOKEN}} 121 | 122 | - name: Test 123 | run: bun test examples/cloudflare-workers/ci.test.ts 124 | env: 125 | DEPLOYMENT_URL: https://upstash-ratelimit.upsdev.workers.dev 126 | 127 | nextjs-local: 128 | runs-on: ubuntu-latest 129 | steps: 130 | - name: Setup repo 131 | uses: actions/checkout@v3 132 | - name: Setup nodejs 133 | uses: actions/setup-node@v3 134 | 135 | - name: Setup Bun 136 | uses: oven-sh/setup-bun@v1 137 | with: 138 | bun-version: latest 139 | 140 | - name: Install Dependencies 141 | run: bun install 142 | 143 | - name: Build 144 | run: bun run build 145 | 146 | - name: Install example 147 | run: bun add @upstash/ratelimit@../.. 148 | working-directory: examples/nextjs 149 | 150 | - name: Build example 151 | run: bun run build 152 | working-directory: examples/nextjs 153 | 154 | - name: Run example 155 | run: npm run start & 156 | working-directory: examples/nextjs 157 | 158 | - name: Test 159 | run: bun test ci.test.ts 160 | working-directory: examples/nextjs 161 | env: 162 | DEPLOYMENT_URL: http://localhost:3000 163 | 164 | 165 | nextjs-deployed: 166 | concurrency: nextjs-deployed 167 | runs-on: ubuntu-latest 168 | needs: 169 | - release 170 | steps: 171 | - name: Setup repo 172 | uses: actions/checkout@v3 173 | 174 | - name: Setup node 175 | uses: actions/setup-node@v3 176 | with: 177 | node-version: 20 178 | 179 | - name: Setup Bun 180 | uses: oven-sh/setup-bun@v1 181 | with: 182 | bun-version: latest 183 | 184 | - name: Install Vercel CLI 185 | run: npm install --global vercel@latest 186 | 187 | - name: Pull Vercel Environment Information 188 | run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} 189 | env: 190 | VERCEL_ORG_ID: ${{ secrets.VERCEL_TEAM_ID }} 191 | VERCEL_PROJECT_ID: "prj_NSOSq2ZawugKhtZGb3ViHX4b56hx" 192 | working-directory: examples/nextjs 193 | 194 | - name: Install @upstash/ratelimit canary version 195 | run: npm install @upstash/ratelimit@${{needs.release.outputs.version}} 196 | working-directory: examples/nextjs 197 | 198 | - name: Build Project 199 | run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} 200 | env: 201 | VERCEL_ORG_ID: ${{ secrets.VERCEL_TEAM_ID }} 202 | VERCEL_PROJECT_ID: "prj_NSOSq2ZawugKhtZGb3ViHX4b56hx" 203 | working-directory: examples/nextjs 204 | 205 | - name: Deploy to Vercel 206 | run: | 207 | DEPLOYMENT_URL=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} \ 208 | --env UPSTASH_REDIS_REST_URL=${{ secrets.UPSTASH_REDIS_REST_URL }} \ 209 | --env UPSTASH_REDIS_REST_TOKEN=${{ secrets.UPSTASH_REDIS_REST_TOKEN }}) 210 | echo "DEPLOYMENT_URL=${DEPLOYMENT_URL}" >> $GITHUB_ENV 211 | env: 212 | VERCEL_ORG_ID: ${{ secrets.VERCEL_TEAM_ID }} 213 | VERCEL_PROJECT_ID: "prj_NSOSq2ZawugKhtZGb3ViHX4b56hx" 214 | working-directory: examples/nextjs 215 | 216 | - name: Test 217 | run: bun test ci.test.ts 218 | working-directory: examples/nextjs 219 | 220 | release: 221 | name: Release 222 | concurrency: release 223 | needs: 224 | - cloudflare-workers-local 225 | - nextjs-local 226 | 227 | runs-on: ubuntu-latest 228 | outputs: 229 | version: ${{ steps.version.outputs.version }} 230 | steps: 231 | - name: Checkout Repo 232 | uses: actions/checkout@v3 233 | 234 | - name: Get version 235 | id: version 236 | run: echo "::set-output name=version::v0.0.0-ci.${GITHUB_SHA}-$(date +%Y%m%d%H%M%S)" 237 | 238 | - name: Setup Node 239 | uses: actions/setup-node@v2 240 | with: 241 | node-version: 18 242 | 243 | - name: Set package version 244 | run: echo $(jq --arg v "${{ steps.version.outputs.version }}" '(.version) = $v' package.json) > package.json 245 | 246 | - name: Setup Bun 247 | uses: oven-sh/setup-bun@v1 248 | with: 249 | bun-version: latest 250 | 251 | - name: Install dependencies 252 | run: bun install 253 | 254 | - name: Build 255 | run: bun run build 256 | 257 | - name: Add npm token 258 | run: echo "//registry.npmjs.org/:_authToken=${{secrets.NPM_TOKEN}}" > .npmrc 259 | 260 | - name: Publish release candidate 261 | run: npm publish --access public --tag=ci 262 | 263 | - name: Sleep 264 | run: sleep 5 265 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | redis-server 5 | 6 | **/yarn.lock 7 | **/package-lock.json 8 | **/pnpm-lock.yaml 9 | 10 | *.log 11 | .vercel 12 | .next 13 | 14 | coverage 15 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | examples 3 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Upstash, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Upstash Rate Limit 2 | 3 | [![npm (scoped)](https://img.shields.io/npm/v/@upstash/ratelimit)](https://www.npmjs.com/package/@upstash/ratelimit) 4 | [![Tests](https://github.com/upstash/ratelimit/actions/workflows/tests.yaml/badge.svg)](https://github.com/upstash/ratelimit/actions/workflows/tests.yaml) 5 | 6 | > [!NOTE] 7 | > **This project is in GA Stage.** 8 | > The Upstash Professional Support fully covers this project. It receives regular updates, and bug fixes. The Upstash team is committed to maintaining and improving its functionality. 9 | 10 | It is the only connectionless (HTTP based) rate limiting library and designed 11 | for: 12 | 13 | - Serverless functions (AWS Lambda, Vercel ....) 14 | - Cloudflare Workers & Pages 15 | - Vercel Edge 16 | - Fastly Compute@Edge 17 | - Next.js, Jamstack ... 18 | - Client side web/mobile applications 19 | - WebAssembly 20 | - and other environments where HTTP is preferred over TCP. 21 | 22 | ## Quick Start 23 | 24 | ### Install 25 | 26 | #### npm 27 | 28 | ```bash 29 | npm install @upstash/ratelimit 30 | ``` 31 | 32 | #### Deno 33 | 34 | ```ts 35 | import { Ratelimit } from "https://cdn.skypack.dev/@upstash/ratelimit@latest"; 36 | ``` 37 | 38 | ### Create database 39 | 40 | Create a new redis database on [upstash](https://console.upstash.com/). See [here](https://github.com/upstash/upstash-redis#quick-start) for documentation on how to create a redis instance. 41 | 42 | ### Basic Usage 43 | 44 | ```ts 45 | import { Ratelimit } from "@upstash/ratelimit"; // for deno: see above 46 | import { Redis } from "@upstash/redis"; // see below for cloudflare and fastly adapters 47 | 48 | // Create a new ratelimiter, that allows 10 requests per 10 seconds 49 | const ratelimit = new Ratelimit({ 50 | redis: Redis.fromEnv(), 51 | limiter: Ratelimit.slidingWindow(10, "10 s"), 52 | analytics: true, 53 | /** 54 | * Optional prefix for the keys used in redis. This is useful if you want to share a redis 55 | * instance with other applications and want to avoid key collisions. The default prefix is 56 | * "@upstash/ratelimit" 57 | */ 58 | prefix: "@upstash/ratelimit", 59 | }); 60 | 61 | // Use a constant string to limit all requests with a single ratelimit 62 | // Or use a userID, apiKey or ip address for individual limits. 63 | const identifier = "api"; 64 | const { success } = await ratelimit.limit(identifier); 65 | 66 | if (!success) { 67 | return "Unable to process at this time"; 68 | } 69 | doExpensiveCalculation(); 70 | return "Here you go!"; 71 | ``` 72 | 73 | For more information on getting started, you can refer to [our documentation](https://upstash.com/docs/oss/sdks/ts/ratelimit/gettingstarted). 74 | 75 | [Here's a complete nextjs example](https://github.com/upstash/ratelimit/tree/main/examples/nextjs) 76 | 77 | ## Documentation 78 | 79 | See [the documentation](https://upstash.com/docs/redis/sdks/ratelimit-ts/overview) for more information details about this package. 80 | 81 | ## Contributing 82 | 83 | ### Database 84 | 85 | Create a new redis database on [upstash](https://console.upstash.com/) and copy 86 | the url and token. 87 | 88 | ### Running tests 89 | 90 | To run the tests, you will need to set some environment variables. Here is a list of 91 | variables to set: 92 | - `UPSTASH_REDIS_REST_URL` 93 | - `UPSTASH_REDIS_REST_TOKEN` 94 | - `US1_UPSTASH_REDIS_REST_URL` 95 | - `US1_UPSTASH_REDIS_REST_TOKEN` 96 | - `APN_UPSTASH_REDIS_REST_URL` 97 | - `APN_UPSTASH_REDIS_REST_TOKEN` 98 | - `EU2_UPSTASH_REDIS_REST_URL` 99 | - `EU2_UPSTASH_REDIS_REST_TOKEN` 100 | 101 | You can create a single Upstash Redis and use its URL and token for all four above. 102 | 103 | Once you set the environment variables, simply run: 104 | ```sh 105 | pnpm test 106 | ``` 107 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/ratelimit-js/a879eff5a9340ea3a523aa624ba318d40c0d3615/bun.lockb -------------------------------------------------------------------------------- /cmd/set-version.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | const root = process.argv[2]; // path to project root 5 | const version = process.argv[3].replace(/^v/, ""); // new version 6 | 7 | console.log(`Updating version=${version} in ${root}`); 8 | 9 | const content = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf-8")); 10 | 11 | content.version = version; 12 | 13 | fs.writeFileSync(path.join(root, "package.json"), JSON.stringify(content, null, 2)); 14 | fs.writeFileSync(path.join(root, "src", "version.ts"), `export const VERSION = "${version}";`); 15 | -------------------------------------------------------------------------------- /context7.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectTitle": "Upstash Ratelimit JS", 3 | "description": "Rate limiting library based on Upstash Redis", 4 | "folders": [ 5 | ], 6 | "excludeFolders": [ 7 | "src", 8 | "cmd" 9 | ], 10 | "rules": [ 11 | "Use Upstash Redis as the database.", 12 | "Use single region set up of Upstash." 13 | ], 14 | "previousVersions": [ 15 | { 16 | "tag": "v2.0.0", 17 | "title": "version 2.0" 18 | }, 19 | { 20 | "tag": "v1.0.3", 21 | "title": "version 1.0" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 2 | import unicorn from "eslint-plugin-unicorn"; 3 | import path from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | import js from "@eslint/js"; 6 | import { FlatCompat } from "@eslint/eslintrc"; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | const compat = new FlatCompat({ 11 | baseDirectory: __dirname, 12 | recommendedConfig: js.configs.recommended, 13 | allConfig: js.configs.all, 14 | }); 15 | 16 | export default [ 17 | { 18 | ignores: ["**/*.config.*", "**/examples", "**/dist"], 19 | }, 20 | ...compat.extends( 21 | "eslint:recommended", 22 | "plugin:unicorn/recommended", 23 | "plugin:@typescript-eslint/recommended" 24 | ), 25 | { 26 | plugins: { 27 | "@typescript-eslint": typescriptEslint, 28 | unicorn, 29 | }, 30 | 31 | languageOptions: { 32 | globals: {}, 33 | ecmaVersion: 5, 34 | sourceType: "script", 35 | 36 | parserOptions: { 37 | project: "./tsconfig.json", 38 | }, 39 | }, 40 | 41 | rules: { 42 | "no-console": [ 43 | "error", 44 | { 45 | allow: ["warn", "error"], 46 | }, 47 | ], 48 | 49 | "@typescript-eslint/no-magic-numbers": "off", 50 | "@typescript-eslint/unbound-method": "off", 51 | "@typescript-eslint/prefer-as-const": "error", 52 | "@typescript-eslint/consistent-type-imports": "error", 53 | "@typescript-eslint/no-explicit-any": "off", 54 | "@typescript-eslint/restrict-template-expressions": "off", 55 | "@typescript-eslint/consistent-type-definitions": ["error", "type"], 56 | 57 | "@typescript-eslint/no-unused-vars": [ 58 | "error", 59 | { 60 | varsIgnorePattern: "^_", 61 | argsIgnorePattern: "^_", 62 | }, 63 | ], 64 | 65 | "@typescript-eslint/prefer-ts-expect-error": "off", 66 | 67 | "@typescript-eslint/no-misused-promises": [ 68 | "error", 69 | { 70 | checksVoidReturn: false, 71 | }, 72 | ], 73 | 74 | "unicorn/prevent-abbreviations": "off", 75 | 76 | "no-implicit-coercion": [ 77 | "error", 78 | { 79 | boolean: true, 80 | }, 81 | ], 82 | 83 | "no-extra-boolean-cast": [ 84 | "error", 85 | { 86 | enforceForLogicalOperands: true, 87 | }, 88 | ], 89 | 90 | "no-unneeded-ternary": [ 91 | "error", 92 | { 93 | defaultAssignment: true, 94 | }, 95 | ], 96 | 97 | "unicorn/no-array-reduce": ["off"], 98 | "unicorn/no-nested-ternary": "off", 99 | "unicorn/no-null": "off", 100 | "unicorn/filename-case": "off", 101 | }, 102 | }, 103 | ]; 104 | -------------------------------------------------------------------------------- /examples/cloudflare-pages/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "plugin:eslint-plugin-next-on-pages/recommended" 5 | ], 6 | "plugins": [ 7 | "eslint-plugin-next-on-pages" 8 | ] 9 | } -------------------------------------------------------------------------------- /examples/cloudflare-pages/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # wrangler files 39 | .wrangler 40 | .dev.vars 41 | -------------------------------------------------------------------------------- /examples/cloudflare-pages/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`c3`](https://developers.cloudflare.com/pages/get-started/c3) which examplifies how one can use Upstash Ratelimit with Cloudflare Pages. 2 | 3 | The project was initialized with ([see CF guide](https://developers.cloudflare.com/pages/framework-guides/nextjs/deploy-a-nextjs-site/)): 4 | 5 | ```bash 6 | npm create cloudflare@latest cloudflare-pages -- --framework=next 7 | ``` 8 | 9 | Then, the [page.tsx](https://github.com/upstash/ratelimit/blob/main/examples/cloudflare-pages/app/page.tsx) file was updated with a simple page to test ratelimiting. An api endpoint was added in [route.tsx](https://github.com/upstash/ratelimit/blob/main/examples/cloudflare-pages/app/api/route.tsx). This is where we define the rate limit like the following: 10 | 11 | ```tsx 12 | export const runtime = 'edge'; 13 | 14 | export const dynamic = 'force-dynamic'; 15 | 16 | import { Ratelimit } from "@upstash/ratelimit"; 17 | import { Redis } from "@upstash/redis"; 18 | 19 | // Create a new ratelimiter 20 | const ratelimit = new Ratelimit({ 21 | redis: Redis.fromEnv(), 22 | limiter: Ratelimit.slidingWindow(10, "10 s"), 23 | prefix: "@upstash/ratelimit", 24 | }); 25 | 26 | export async function GET(request: Request) { 27 | 28 | const identifier = "api"; 29 | const { success, limit, remaining } = await ratelimit.limit(identifier); 30 | const response = { 31 | success: success, 32 | limit: limit, 33 | remaining: remaining 34 | } 35 | 36 | if (!success) { 37 | return new Response(JSON.stringify(response), { status: 429 }); 38 | } 39 | return new Response(JSON.stringify(response)); 40 | } 41 | ``` 42 | 43 | ## Getting Started 44 | 45 | First, create an Upstash Redis through [the Upstash console](https://console.upstash.com/redis) and set the environment variables `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`. 46 | 47 | Then, run the development server: 48 | 49 | ```bash 50 | npm run dev 51 | # or 52 | yarn dev 53 | # or 54 | pnpm dev 55 | # or 56 | bun dev 57 | ``` 58 | 59 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 60 | 61 | ## Deployment 62 | 63 | To deploy the project, simply set the environment variables `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` through the Environment Variables section under the Settings tab on [the Cloudflare Dashboard](https://dash.cloudflare.com). Then, run: 64 | 65 | ```bash 66 | npm run deploy 67 | ``` 68 | 69 | Note: if you don't set the environment variables, you may get an error when deploying the project. -------------------------------------------------------------------------------- /examples/cloudflare-pages/app/api/route.ts: -------------------------------------------------------------------------------- 1 | export const runtime = 'edge'; 2 | 3 | export const dynamic = 'force-dynamic'; 4 | 5 | import { Ratelimit } from "@upstash/ratelimit"; 6 | import { Redis } from "@upstash/redis"; 7 | 8 | // Create a new ratelimiter 9 | const ratelimit = new Ratelimit({ 10 | redis: Redis.fromEnv(), 11 | limiter: Ratelimit.slidingWindow(10, "10 s"), 12 | prefix: "@upstash/ratelimit", 13 | }); 14 | 15 | export async function GET(request: Request) { 16 | 17 | const identifier = "api"; 18 | const { success, limit, remaining } = await ratelimit.limit(identifier); 19 | const response = { 20 | success: success, 21 | limit: limit, 22 | remaining: remaining 23 | } 24 | 25 | if (!success) { 26 | return new Response(JSON.stringify(response), { status: 429 }); 27 | } 28 | return new Response(JSON.stringify(response)); 29 | } -------------------------------------------------------------------------------- /examples/cloudflare-pages/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/upstash/ratelimit-js/a879eff5a9340ea3a523aa624ba318d40c0d3615/examples/cloudflare-pages/app/favicon.ico -------------------------------------------------------------------------------- /examples/cloudflare-pages/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | 29 | @layer utilities { 30 | .text-balance { 31 | text-wrap: balance; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/cloudflare-pages/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/cloudflare-pages/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | export const runtime = "edge"; 2 | 3 | export default function NotFound() { 4 | return ( 5 | <> 6 | 404: This page could not be found. 7 |
8 |
9 |