Skip to content

Commit

Permalink
Convert api-routes-rate-limit example to TypeScript (#38393)
Browse files Browse the repository at this point in the history
Converted `api-routes-rate-limit` example to TypeScript to match Contribution docs.

- Removed dead/non-standard/typo links for "Deploy You Own ▲" and "View Source"

## Documentation / Examples

- [X] Make sure the linting passes by running `pnpm lint`
- [X] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)
  • Loading branch information
maxproske committed Jul 8, 2022
1 parent 8783793 commit 6d8b2cd
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 62 deletions.
3 changes: 3 additions & 0 deletions examples/api-routes-rate-limit/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ yarn-error.log*

# vercel
.vercel

# typescript
*.tsbuildinfo
5 changes: 5 additions & 0 deletions examples/api-routes-rate-limit/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
15 changes: 11 additions & 4 deletions examples/api-routes-rate-limit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@
"start": "next start"
},
"dependencies": {
"lru-cache": "^6.0.0",
"lru-cache": "^7.12.0",
"next": "latest",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"uuid": "^8.3.1"
"react": "^18.2.0",
"react-dom": "^18.2.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/node": "^18.0.3",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/uuid": "^8.3.4",
"typescript": "^4.7.4"
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import * as uuid from 'uuid'
import type { NextApiRequest, NextApiResponse } from 'next'
import { v4 as uuidv4 } from 'uuid'
import rateLimit from '../../utils/rate-limit'

const limiter = rateLimit({
interval: 60 * 1000, // 60 seconds
uniqueTokenPerInterval: 500, // Max 500 users per second
})

export default async function handler(req, res) {
export default async function handler(
_req: NextApiRequest,
res: NextApiResponse
) {
try {
await limiter.check(res, 10, 'CACHE_TOKEN') // 10 requests per minute
res.status(200).json({ id: uuid.v4() })
res.status(200).json({ id: uuidv4() })
} catch {
res.status(429).json({ error: 'Rate limit exceeded' })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState } from 'react'
import styles from '../styles.module.css'

export default function Index() {
const [response, setResponse] = useState()
const [response, setResponse] = useState<Record<string, unknown> | null>(null)

const makeRequest = async () => {
const res = await fetch('/api/user')
Expand All @@ -24,29 +24,11 @@ export default function Index() {
Functions).
</p>
<button onClick={() => makeRequest()}>Make Request</button>
<code className={styles.code}>
<div>
<b>Status Code: </b>
{response?.status || 'None'}
</div>
<div>
<b>Request Limit: </b>
{response?.limit || 'None'}
</div>
<div>
<b>Remaining Requests: </b>
{response?.remaining || 'None'}
</div>
<div>
<b>Body: </b>
{JSON.stringify(response?.body) || 'None'}
</div>
</code>
<div className={styles.links}>
<a href="#">View Source</a>
{' | '}
<a href="#">Deploy You Own ▲</a>
</div>
{response && (
<code className={styles.code}>
<pre>{JSON.stringify(response, null, 2)}</pre>
</code>
)}
</main>
)
}
20 changes: 20 additions & 0 deletions examples/api-routes-rate-limit/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
31 changes: 0 additions & 31 deletions examples/api-routes-rate-limit/utils/rate-limit.js

This file was deleted.

35 changes: 35 additions & 0 deletions examples/api-routes-rate-limit/utils/rate-limit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { NextApiResponse } from 'next'
import LRU from 'lru-cache'

type Options = {
uniqueTokenPerInterval?: number
interval?: number
}

export default function rateLimit(options?: Options) {
const tokenCache = new LRU({
max: options?.uniqueTokenPerInterval || 500,
ttl: options?.interval || 60000,
})

return {
check: (res: NextApiResponse, limit: number, token: string) =>
new Promise<void>((resolve, reject) => {
const tokenCount = (tokenCache.get(token) as number[]) || [0]
if (tokenCount[0] === 0) {
tokenCache.set(token, tokenCount)
}
tokenCount[0] += 1

const currentUsage = tokenCount[0]
const isRateLimited = currentUsage >= limit
res.setHeader('X-RateLimit-Limit', limit)
res.setHeader(
'X-RateLimit-Remaining',
isRateLimited ? 0 : limit - currentUsage
)

return isRateLimited ? reject() : resolve()
}),
}
}

0 comments on commit 6d8b2cd

Please sign in to comment.