Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ratelimit on both api handlers and TRPC route on user route #30

Merged
merged 5 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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=""
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: upstash_redis_rest_url,
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

}),
});
Loading