File tree 7 files changed +252
-0
lines changed
examples/api-routes-rate-limit
7 files changed +252
-0
lines changed Original file line number Diff line number Diff line change
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
+
8
+ # testing
9
+ /coverage
10
+
11
+ # next.js
12
+ /.next /
13
+ /out /
14
+
15
+ # production
16
+ /build
17
+
18
+ # misc
19
+ .DS_Store
20
+ * .pem
21
+
22
+ # debug
23
+ npm-debug.log *
24
+ yarn-debug.log *
25
+ yarn-error.log *
26
+
27
+ # local env files
28
+ .env.local
29
+ .env.development.local
30
+ .env.test.local
31
+ .env.production.local
32
+
33
+ # vercel
34
+ .vercel
Original file line number Diff line number Diff line change
1
+ # API Routes Rate Limiting Example
2
+
3
+ This example uses ` lru-cache ` to implement a simple rate limiter for API routes ([ Serverless Functions] ( https://vercel.com/docs/serverless-functions/introduction ) ).
4
+
5
+ ** Demo: https://nextjs-rate-limit.vercel.app/ **
6
+
7
+ ``` bash
8
+ curl http://localhost:3000/api/user -I
9
+ HTTP/1.1 200 OK
10
+ X-RateLimit-Limit: 10
11
+ X-RateLimit-Remaining: 9
12
+
13
+ curl http://localhost:3000/api/user -I
14
+ HTTP/1.1 429 Too Many Requests
15
+ X-RateLimit-Limit: 10
16
+ X-RateLimit-Remaining: 0
17
+ ```
18
+
19
+ ## Deploy your own
20
+
21
+ Deploy the example using [ Vercel] ( https://vercel.com ) :
22
+
23
+ [ ![ Deploy with Vercel] ( https://vercel.com/button )] ( https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/api-routes-rate-limit )
24
+
25
+ ## How to use
26
+
27
+ Execute [ ` create-next-app ` ] ( https://github.com/vercel/next.js/tree/canary/packages/create-next-app ) with [ npm] ( https://docs.npmjs.com/cli/init ) or [ Yarn] ( https://yarnpkg.com/lang/en/docs/cli/create/ ) to bootstrap the example:
28
+
29
+ ``` bash
30
+ npx create-next-app --example api-routes api-routes-rate-limit
31
+ # or
32
+ yarn create next-app --example api-routes api-routes-rate-limit
33
+ ```
34
+
35
+ Deploy it to the cloud with [ Vercel] ( https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example ) ([ Documentation] ( https://nextjs.org/docs/deployment ) ).
Original file line number Diff line number Diff line change
1
+ {
2
+ "name" : " nextjs-rate-limit" ,
3
+ "version" : " 0.0.0" ,
4
+ "private" : true ,
5
+ "scripts" : {
6
+ "dev" : " next dev" ,
7
+ "build" : " next build" ,
8
+ "start" : " next start"
9
+ },
10
+ "dependencies" : {
11
+ "lru-cache" : " ^6.0.0" ,
12
+ "next" : " 10.0.3" ,
13
+ "react" : " 17.0.1" ,
14
+ "react-dom" : " 17.0.1" ,
15
+ "uuid" : " ^8.3.1"
16
+ }
17
+ }
Original file line number Diff line number Diff line change
1
+ import * as uuid from 'uuid'
2
+ import rateLimit from '../../utils/rate-limit'
3
+
4
+ const limiter = rateLimit ( {
5
+ interval : 60 * 1000 , // 60 seconds
6
+ uniqueTokenPerInterval : 500 , // Max 500 users per second
7
+ } )
8
+
9
+ export default async function handler ( req , res ) {
10
+ try {
11
+ await limiter . check ( res , 10 , 'CACHE_TOKEN' ) // 10 requests per minute
12
+ res . status ( 200 ) . json ( { id : uuid . v4 ( ) } )
13
+ } catch {
14
+ res . status ( 429 ) . json ( { error : 'Rate limit exceeded' } )
15
+ }
16
+ }
Original file line number Diff line number Diff line change
1
+ import { useState } from 'react'
2
+ import styles from '../styles.module.css'
3
+
4
+ export default function Index ( ) {
5
+ const [ response , setResponse ] = useState ( )
6
+
7
+ const makeRequest = async ( ) => {
8
+ const res = await fetch ( '/api/user' )
9
+
10
+ setResponse ( {
11
+ status : res . status ,
12
+ body : await res . json ( ) ,
13
+ limit : res . headers . get ( 'X-RateLimit-Limit' ) ,
14
+ remaining : res . headers . get ( 'X-RateLimit-Remaining' ) ,
15
+ } )
16
+ }
17
+
18
+ return (
19
+ < main className = { styles . container } >
20
+ < h1 > Next.js API Routes Rate Limiting</ h1 >
21
+ < p >
22
+ This example uses < code className = { styles . inlineCode } > lru-cache</ code > { ' ' }
23
+ to implement a simple rate limiter for API routes (Serverless
24
+ Functions).
25
+ </ p >
26
+ < button onClick = { ( ) => makeRequest ( ) } > Make Request</ button >
27
+ < code className = { styles . code } >
28
+ < div >
29
+ < b > Status Code: </ b >
30
+ { response ?. status || 'None' }
31
+ </ div >
32
+ < div >
33
+ < b > Request Limit: </ b >
34
+ { response ?. limit || 'None' }
35
+ </ div >
36
+ < div >
37
+ < b > Remaining Requests: </ b >
38
+ { response ?. remaining || 'None' }
39
+ </ div >
40
+ < div >
41
+ < b > Body: </ b >
42
+ { JSON . stringify ( response ?. body ) || 'None' }
43
+ </ div >
44
+ </ code >
45
+ < div className = { styles . links } >
46
+ < a href = "#" > View Source</ a >
47
+ { ' | ' }
48
+ < a href = "#" > Deploy You Own ▲</ a >
49
+ </ div >
50
+ </ main >
51
+ )
52
+ }
Original file line number Diff line number Diff line change
1
+ .container {
2
+ padding : 4rem 1rem ;
3
+ max-width : 50rem ;
4
+ margin : 0 auto;
5
+ font-family : -apple-system, BlinkMacSystemFont, sans-serif;
6
+ text-rendering : optimizeLegibility;
7
+ -webkit-font-smoothing : antialiased;
8
+ -moz-osx-font-smoothing : grayscale;
9
+ }
10
+
11
+ .container h1 {
12
+ font-weight : 800 ;
13
+ }
14
+
15
+ .container p {
16
+ margin : 1.5rem 0 ;
17
+ line-height : 1.5 ;
18
+ }
19
+
20
+ .container button {
21
+ border-radius : 4px ;
22
+ height : 40px ;
23
+ padding : 0.5rem 1rem ;
24
+ font-size : 16px ;
25
+ border : none;
26
+ transition : 0.25s all ease;
27
+ background-color : # eaeaea ;
28
+ font-size : 14px ;
29
+ font-weight : 600 ;
30
+ color : # 111 ;
31
+ }
32
+
33
+ .container button : hover {
34
+ box-shadow : 0 5px 10px rgba (0 , 0 , 0 , 0.12 );
35
+ }
36
+
37
+ .container a {
38
+ text-decoration : none;
39
+ color : # 0070f3 ;
40
+ }
41
+
42
+ .inlineCode {
43
+ color : # be00ff ;
44
+ font-size : 16px ;
45
+ white-space : pre-wrap;
46
+ }
47
+
48
+ .inlineCode ::before ,
49
+ .inlineCode ::after {
50
+ content : '`' ;
51
+ }
52
+
53
+ .code {
54
+ margin-top : 16px ;
55
+ display : block;
56
+ background : # 222222 ;
57
+ border-radius : 8px ;
58
+ padding : 16px ;
59
+ color : white;
60
+ font-size : 16px ;
61
+ line-height : 1.4 ;
62
+ }
63
+
64
+ .links {
65
+ margin-top : 16px ;
66
+ color : # 9c9c9c ;
67
+ }
Original file line number Diff line number Diff line change
1
+ const LRU = require ( 'lru-cache' )
2
+
3
+ const rateLimit = ( options ) => {
4
+ const tokenCache = new LRU ( {
5
+ max : parseInt ( options . uniqueTokenPerInterval || 500 , 10 ) ,
6
+ maxAge : parseInt ( options . interval || 60000 , 10 ) ,
7
+ } )
8
+
9
+ return {
10
+ check : ( res , limit , token ) =>
11
+ new Promise ( ( resolve , reject ) => {
12
+ const tokenCount = tokenCache . get ( token ) || [ 0 ]
13
+ if ( tokenCount [ 0 ] === 0 ) {
14
+ tokenCache . set ( token , tokenCount )
15
+ }
16
+ tokenCount [ 0 ] += 1
17
+
18
+ const currentUsage = tokenCount [ 0 ]
19
+ const isRateLimited = currentUsage >= parseInt ( limit , 10 )
20
+ res . setHeader ( 'X-RateLimit-Limit' , limit )
21
+ res . setHeader (
22
+ 'X-RateLimit-Remaining' ,
23
+ isRateLimited ? 0 : limit - currentUsage
24
+ )
25
+
26
+ return isRateLimited ? reject ( ) : resolve ( )
27
+ } ) ,
28
+ }
29
+ }
30
+
31
+ export default rateLimit
You can’t perform that action at this time.
0 commit comments