0.2.4 • Published 3 years ago
tollbooth v0.2.4
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 tollboothor
yarn add tollboothHow 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 serverBy 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 logicReturn 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.ioredisroutes: 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-keyerrorHandler: (Only for Express and AWS Lambda) Custom error handler function with signature(res: express.Response | APIGatewayProxyCallback, error: tollbooth.TollboothError) => voidallowAnonymous: (Optional) If set totrue, allows access without token. Default:falsedebug: (Optional) If set totrue, will enable console logging. Default:falsefailOnExceptions: (Optional) If set tofalse, will not propagate exceptions (e.g. redis connection error), therefore allowing access. Default:truethrottleEnabled: (Optional) If set tofalse, turns off throttling. Default:truethrottleInterval: (Optional) Duration of the throttle interval in seconds. For example, whenthrottleInterval=2andthrottleLimit=10, it will allow max 10 requests per 2 seconds, or fail with 429 response. Default:1throttleLimit: (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 logicExamples
See examples folder.
Running redis
Running locally
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest3rd party services
Benchmarks
Start redis on localhost:6379 and run
npm run benchmarkSee 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,526Development
Build
npm run buildRun tests
Start redis on localhost:6379 and run
npm test