Skip to content

Commit

Permalink
Ratelimit on both api handlers and TRPC route on user route (#30)
Browse files Browse the repository at this point in the history
* added upstash redis and .env.example

* adding server-side-env and redis client on /ratelimit

* adding sliding window approach

* add new envs to git workflow

* fix t3-env error

---------

Co-authored-by: Touha Zohair <[email protected]>
  • Loading branch information
Kinfe123 and iamtouha authored Feb 22, 2024
1 parent f2d01f7 commit e4305d0
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ STRIPE_WEBHOOK_SECRET='whsec_'
# Stripe Product and Price IDs for your created products
# found at https://dashboard.stripe.com/test/products
STRIPE_PRO_MONTHLY_PLAN_ID='price_'


# Upstash for ratelimits , caches ...
UPSTASH_REDIS_REST_URL='https://redis-rest-api.upstash.com'
UPSTASH_REDIS_REST_TOKEN='upstash_redis_rest_token'
2 changes: 2 additions & 0 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ jobs:
STRIPE_API_KEY: stripe_api_key
STRIPE_WEBHOOK_SECRET: stripe_webhook_secret
STRIPE_PRO_MONTHLY_PLAN_ID: stripe_pro_monthly_plan_id
UPSTASH_REDIS_REST_URL: https://redis-rest-api.upstash.com
UPSTASH_REDIS_REST_TOKEN: upstash_redis_rest_token
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"@upstash/ratelimit": "^1.0.1",
"@upstash/redis": "^1.28.4",
"arctic": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
Expand Down
29 changes: 29 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/app/api/ratelimit/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {Ratelimit} from "@upstash/ratelimit";
import {Redis} from "@upstash/redis";

// creating redis client
const redis = new Redis({
url: 'UPSTASH_REDIS_REST_URL',
token: 'UPSTASH_REDIS_REST_TOKEN',
})
5 changes: 5 additions & 0 deletions src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const env = createEnv({
STRIPE_API_KEY: z.string().trim().min(1),
STRIPE_WEBHOOK_SECRET: z.string().trim().min(1),
STRIPE_PRO_MONTHLY_PLAN_ID: z.string().trim().min(1),
UPSTASH_REDIS_REST_URL: z.string().url(),
UPSTASH_REDIS_REST_TOKEN: z.string().min(5),

},

/**
Expand Down Expand Up @@ -55,6 +58,8 @@ export const env = createEnv({
STRIPE_API_KEY: process.env.STRIPE_API_KEY,
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
STRIPE_PRO_MONTHLY_PLAN_ID: process.env.STRIPE_PRO_MONTHLY_PLAN_ID,
UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
// Client-side env vars
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
},
Expand Down
9 changes: 9 additions & 0 deletions src/lib/ratelimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Ratelimit } from "@upstash/ratelimit"
import { Redis } from "@upstash/redis"

export const ratelimit = new Ratelimit({
redis:Redis.fromEnv(),
// using sliding window approach - found out more - https://github.com/upstash/ratelimit#sliding-window
limiter:Ratelimit.slidingWindow(1 , "10 m")

})
44 changes: 44 additions & 0 deletions src/server/api/ratelimit/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ratelimit } from "@/lib/ratelimit";

export async function GET(req: Request ) {
try {
// const res = await req.()

const ip = req.headers.get('x-forwarded-for') ?? ''

const data = await ratelimit.limit(ip)
if(!data.success) {
return new Response("You can only send 1 req / 30min.", {
status: 429,
})
}

return new Response(JSON.stringify({status:"OK"}))

}catch(err) {
console.log('Error has occured : ' , err)
}
}


export async function POST(req: Request ) {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const res = await req.json()

const ip = req.headers.get('x-forwarded-for') ?? ''
// you can use the response or the user id here instead of ip -
// const {id} = res - by sending as the user id as a post requst
const data = await ratelimit.limit(ip)
if(!data.success) {
return new Response("You can only send 1 req / 30min.", {
status: 429,
})
}

return new Response(JSON.stringify({status:"OK" , ...res}))

}catch(err) {
console.log('Error has occured : ' , err)
}
}
12 changes: 9 additions & 3 deletions src/server/api/routers/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ratelimit } from "@/lib/ratelimit";
import { protectedProcedure, createTRPCRouter } from "../trpc";

import { TRPCError } from "@trpc/server";
export const userRouter = createTRPCRouter({
get: protectedProcedure.query(({ ctx }) => ctx.user),
});
get: protectedProcedure.query(async ({ ctx }) => {
const {success} = await ratelimit.limit(ctx.user.id)
if(!success) throw new TRPCError({ code: "TOO_MANY_REQUESTS" });
return ctx.user

}),
});

0 comments on commit e4305d0

Please sign in to comment.