npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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

Run tests CodeQL npm version coverage

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

npm add tollbooth

or

yarn add tollbooth

How it works

  1. Checks how many requests does given token still have left.
  2. If the token was not given limit (i.e. setLimits was not called), rejects the request with Unauthorized.
  3. If the token does not have enough requests (i.e. limit == 0), rejects the request with LimitReached.
  4. Checks how many requests did the token make recently.
  5. If the token made more than X requests in the last N seconds (configurable), rejects the request with TooManyRequests.
  6. 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 routes
    • path: Relative path, e.g. /foo, or * to protect all paths with given method.
    • method: One of get, head, post, put, patch, delete, options
  • tokenHeaderName: (Only for Express and AWS Lambda) Name of the header containing token. Default x-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 to true, allows access without token. Default: false
  • debug: (Optional) If set to true, will enable console logging. Default: false
  • failOnExceptions: (Optional) If set to false, will not propagate exceptions (e.g. redis connection error), therefore allowing access. Default: true
  • throttleEnabled: (Optional) If set to false, turns off throttling. Default: true
  • throttleInterval: (Optional) Duration of the throttle interval in seconds. For example, when throttleInterval=2 and throttleLimit=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