# @routup/rate-limit

[![npm version](https://badge.fury.io/js/@routup%2Frate-limit.svg)](https://badge.fury.io/js/@routup%2Frate-limit)
[![main](https://github.com/routup/plugins/actions/workflows/main.yml/badge.svg)](https://github.com/routup/plugins/actions/workflows/main.yml)
[![codecov](https://codecov.io/gh/routup/plugins/branch/master/graph/badge.svg)](https://codecov.io/gh/routup/plugins)
[![Known Vulnerabilities](https://snyk.io/test/github/routup/plugins/badge.svg)](https://snyk.io/test/github/routup/plugins)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)

This is a rate-limiter middleware.

**Table of Contents**

- [Installation](#installation)
- [Documentation](#documentation)
- [Usage](#usage)
  - [Store](#store)
- [Options](#options)
  - [windowsMs](#windowms)
  - [max](#max)
  - [message](#message)
  - [statusCode](#statuscode)
  - [skipFailedRequest](#skipfailedrequest)
  - [skipSuccessFulRequest](#skipsuccessfulrequest)
  - [keyGenerator](#keygenerator)
  - [handler](#handler)
  - [skip](#skip)
  - [requestWasSuccessful](#requestwassuccessful)
  - [store](#store)
- [License](#license)

## Installation

```bash
npm install @routup/rate-limit --save
```

## Documentation

To read the docs, visit [https://routup.net](https://routup.net)

## Usage

```typescript
import {
    App,
    serve,
} from 'routup';
import { rateLimit } from '@routup/rate-limit';

const router = new App();

router.use(rateLimit({
    // 15 minutes
    windowMs: 15 * 60 * 1000,

    // Limit each IP to 100 requests
    // per `window` (here, per 15 minutes)
    max: 100,
}));

serve(router, { port: 3000 });
```

### Store

To create a custom Store it is mandatory to extend the `Store` interface.
The following adapters are officially provided:
- [@routup/rate-limit-redis](https://www.npmjs.com/package/@routup/rate-limit-redis)

## Options

### `windowMs`

> `number`

Time frame for which requests are checked/remembered. Also used in the
`Retry-After` header when the limit is reached.

Defaults to `60000` ms (= 1 minute).

### `max`

> `number | function`

The maximum number of connections to allow during the `window` before rate
limiting the client.

Can be the limit itself as a number or a (sync/async) function that accepts the
routup `event` and then returns a number.

Defaults to `5`. Set it to `0` to disable the rate limiter.

An example of using a function:

```ts
const isPremium = async (ip: string) => {
	// ...
}

const handler = createHandler({
    // ...
    max: async (event) => {
        const ip = getRequestIP(event, { trustProxy: true }) || '127.0.0.1';
        if (await isPremium(ip)) return 10
        else return 5
    },
})
```

### `message`

> `any`

The response body to send back when a client is rate limited.

May be a `string`, JSON object, or any other value.
It can also be a (sync/async) function that accepts the routup `event`
and then returns a `string`, JSON object or any other value.

Defaults to `'Too many requests, please try again later.'`

An example of using a function:

```ts
const isPremium = async (ip: string) => {
	// ...
}

const handler = createHandler({
    // ...
    message: async (event) => {
        const ip = getRequestIP(event, { trustProxy: true }) || '127.0.0.1';
        if (await isPremium(ip)) {
            return 'You can only make 10 requests every hour.'
        }
			
        return 'You can only make 5 requests every hour.'
    },
})
```

### `statusCode`

> `number`

The HTTP status code to send back when a client is rate limited.

Defaults to `429` (HTTP 429 Too Many Requests - RFC 6585).

### `skipFailedRequest`

> `boolean`

When set to `true`, failed requests won't be counted. Request considered failed
when the `requestWasSuccessful` option returns `false`. By default, this means
requests fail when the response status >= 400.

Defaults to `false`.

### `skipSuccessfulRequest`

> `boolean`

If `true`, the library will (by default) skip all requests that are considered
'successful' by the `requestWasSuccessful` function. By default, this means requests
succeed when the response status code < 400.

Defaults to `false`.

### `keyGenerator`

> `function`

Method to generate custom identifiers for clients.

Should be a (sync/async) function that accepts the routup `event`
and then returns a string.

By default, the client's IP address is used:

```ts
import { getRequestIP } from 'routup';

const handler = createHandler({
    // ...
    keyGenerator: (event) => getRequestIP(event, { trustProxy: true }),
})
```

### `handler`

> `function`

Handler that sends back a response when a client is rate-limited.

By default, sends back the `statusCode` and `message` set via the `options`,
similar to this:

```ts
const handler = createHandler({
    // ...
    handler(event, options) {
        event.response.status = options.statusCode;
        return options.message;
    }
})
```

### `skip`

> `function`

Function to determine whether this request counts towards a client's
quota. Should be a (sync/async) function that accepts the routup `event`
and then returns `true` or `false`.

Could also act as an allow list for certain keys:

```ts
const allowlist = ['192.168.0.56', '192.168.0.21']

const handler = createHandler({
    // ...
    skip: (event) => allowlist.includes(getRequestIP(event)),
})
```

By default, it skips no requests:

```ts
const handler = createHandler({
    // ...
    skip: (event) => false,
})
```

### `requestWasSuccessful`

> `function`

Method to determine whether the request counts as 'successful'. Used when
either `skipSuccessfulRequest` or `skipFailedRequest` is set to true. Should
be a function that accepts the routup `event` and the downstream `Response`
object and then returns `true` or `false`.

By default, requests with a response status code less than 400 are considered
successful:

```ts
const handler = createHandler({
    // ...
    requestWasSuccessful: (event, response) => response.status < 400,
})
```

### `store`

The `Store` to use to store the hit count for each client.

## License

Made with 💚

Published under [MIT License](./LICENSE).

This library is heavily inspired by
[express-rate-limit](https://www.npmjs.com/package/express-rate-limit).
