tollbooth
v0.2.4
Published
Tollbooth is a small utility for Node.js, Express & AWS Lambda that throttles and limits number of requests per client using Redis.
Downloads
4
Readme
Tollbooth
Tollbooth is a small utility (10kB raw JS) for Node.js, Deno, Express & AWS Lambda that throttles and limits number of requests per client using Redis.
- TypeScript, Node, Deno
- Express middleware
- AWS Lambda HOF
- Examples
Contents
- Install
- How it works
- Usage with Express
- Usage with AWS Lambda
- Manual usage
- Configuration options
- Admin helpers
- Examples
- Running redis
- Benchmarks
- Development
Install
npm add tollbooth
or
yarn add tollbooth
How it works
- Checks how many requests does given token still have left.
- If the token was not given limit (i.e. setLimits was not called), rejects the request with Unauthorized.
- If the token does not have enough requests (i.e. limit == 0), rejects the request with LimitReached.
- Checks how many requests did the token make recently.
- If the token made more than X requests in the last N seconds (configurable), rejects the request with TooManyRequests.
- Otherwise, accepts the request with Ok.
Usage with Express
import express from 'express';
import Redis from 'ioredis';
import Tollbooth from 'tollbooth/express';
const redis = new Redis('redis://localhost:6379');
const app = express();
app.use(
Tollbooth({
redis,
routes: [{ path: '/foo', method: 'get' }],
}),
);
// setup the express app & start the server
By default, the token will be read from x-api-key header. See Configuration Options for customisation.
To manage tokens and limits, you can use Admin helpers.
import { setLimits, getLimit, removeLimits, UNLIMITED } from 'tollbooth';
// set tokens limits
// e.g. post request to create new account, cron job refreshing limits monthly
await setLimits(redis, [{ token: 'my_token', limit: 1_000 }]);
// token with no limit
await setLimits(redis, [{ token: 'my_token', limit: UNLIMITED }]);
// get token limit
// e.g. in user dashboard
const limit: number = await getLimit(redis, 'my_token');
// remove tokens
// e.g. on account termination
await removeLimits(redis, ['my_token']);
Usage with AWS Lambda
import { Context, APIGatewayProxyCallback, APIGatewayEvent } from 'aws-lambda';
import Redis from 'ioredis';
import Tollbooth from 'tollbooth/lambda';
const redis = new Redis('redis://localhost:6379');
const protect = Tollbooth({
redis,
routes: [{ path: '*', method: 'get' }],
});
function handle(_event: APIGatewayEvent, _context: Context, callback: APIGatewayProxyCallback) {
callback(null, {
statusCode: 200,
body: JSON.stringify({ status: 'ok' }),
});
}
export const handler = protect(handle);
By default, the token will be read from x-api-key header. See Configuration Options for options.
Manual usage
import Tollbooth, { TollboothCode, setLimits } from 'tollbooth';
import Redis from 'ioredis';
const redis = new Redis('redis://localhost:6379');
const protect = Tollbooth({
redis,
routes: [{ path: '/foo', method: 'get' }],
});
// ... application logic
await setLimits(redis, [{ token: 'my_token', limit: 5 }]);
const success = await protect({
path: '/foo',
method: 'get',
token: 'my_token',
});
console.assert(success.code === TollboothCode.Ok);
console.log('Result', success);
// ... application logic
Return value
{
// HTTP status code
statusCode: number;
// Internal code
code: TollboothCode.TooManyRequests |
TollboothCode.Unauthorized |
TollboothCode.LimitReached |
TollboothCode.Ok |
TollboothCode.RedisError;
// Human readable code
message: 'TooManyRequests' | 'Unauthorized' | 'LimitReached' | 'Ok' | 'RedisError';
}
Configuration options
redis
: Redis instance, e.g.ioredis
routes
: List of protected routespath
: Relative path, e.g./foo
, or*
to protect all paths with given method.method
: One ofget
,head
,post
,put
,patch
,delete
,options
tokenHeaderName
: (Only for Express and AWS Lambda) Name of the header containing token. Defaultx-api-key
errorHandler
: (Only for Express and AWS Lambda) Custom error handler function with signature(res: express.Response | APIGatewayProxyCallback, error: tollbooth.TollboothError) => void
allowAnonymous
: (Optional) If set totrue
, allows access without token. Default:false
debug
: (Optional) If set totrue
, will enable console logging. Default:false
failOnExceptions
: (Optional) If set tofalse
, will not propagate exceptions (e.g. redis connection error), therefore allowing access. Default:true
throttleEnabled
: (Optional) If set tofalse
, turns off throttling. Default:true
throttleInterval
: (Optional) Duration of the throttle interval in seconds. For example, whenthrottleInterval=2
andthrottleLimit=10
, it will allow max 10 requests per 2 seconds, or fail with 429 response. Default:1
throttleLimit
: (Optional) Maximum number of requests executed during the throttle interval. Default:10
.
Admin helpers
import Redis from 'ioredis';
import { getLimit, removeLimits, setLimits, UNLIMITED } from 'tollbooth';
const redis = new Redis('redis://localhost:6379');
// ... application logic
// set token1 with maximum of 1_000 requests
// set token2 with maximum of 1 request
// set token3 with unlimited requests
await setLimits(redis, [
{ token: 'token1', limit: 1_000 },
{ token: 'token2', limit: 1 },
{ token: 'token3', limit: UNLIMITED },
);
const currentLimit = await getLimit(redis, 'token1');
console.log({ currentLimit });
// { currentLimit: 1000 }
// removes token1
await removeLimits(redis, ['token1']);
const newLimit = await getLimit(redis, 'token1');
console.log({ newLimit });
// { newLimit: 0 }
// deletes all keys saved in redis
await evict(redis);
// ... application logic
Examples
See examples folder.
Running redis
Running locally
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
3rd party services
Benchmarks
Start redis on localhost:6379
and run
npm run benchmark
See benchmarks folder. Currently comparing with executing single redis call. Results on EC2 t4g.small instance with redis running locally.
incrByScalar x 13,199 ops/sec ±2.09% (83 runs sampled)
protect x 7,582 ops/sec ±1.48% (83 runs sampled)
incrByScalar x 62,546 calls took 5903 ms, made 62,547 redis calls
protect x 36,493 calls took 5963 ms, made 145,979 redis calls
total redis calls 208,526
Development
Build
npm run build
Run tests
Start redis on localhost:6379
and run
npm test