{"id":21526895,"url":"https://github.com/lambdaclass/throttle","last_synced_at":"2025-08-20T22:32:13.597Z","repository":{"id":26078177,"uuid":"107276692","full_name":"lambdaclass/throttle","owner":"lambdaclass","description":"Erlang/OTP application to rate limit resource access","archived":false,"fork":false,"pushed_at":"2021-11-18T18:28:07.000Z","size":798,"stargazers_count":44,"open_issues_count":9,"forks_count":18,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-07-07T01:17:12.460Z","etag":null,"topics":["api","erlang","otp","rate-limiting","throttling"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lambdaclass.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-17T14:02:58.000Z","updated_at":"2025-02-28T07:40:13.000Z","dependencies_parsed_at":"2022-07-27T06:16:25.352Z","dependency_job_id":null,"html_url":"https://github.com/lambdaclass/throttle","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/lambdaclass/throttle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaclass%2Fthrottle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaclass%2Fthrottle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaclass%2Fthrottle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaclass%2Fthrottle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lambdaclass","download_url":"https://codeload.github.com/lambdaclass/throttle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaclass%2Fthrottle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271084976,"owners_count":24696610,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-18T02:00:08.743Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","erlang","otp","rate-limiting","throttling"],"created_at":"2024-11-24T01:47:02.441Z","updated_at":"2025-08-20T22:32:13.262Z","avatar_url":"https://github.com/lambdaclass.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"# throttle\n[![Hex.pm](https://img.shields.io/hexpm/v/lambda_throttle.svg)](https://hex.pm/packages/lambda_throttle)\n[![Build Status](https://travis-ci.org/lambdaclass/throttle.svg?branch=master)](https://travis-ci.org/lambdaclass/throttle)\n[![Coverage Status](https://coveralls.io/repos/github/lambdaclass/throttle/badge.svg?branch=master)](https://coveralls.io/github/lambdaclass/throttle?branch=master)\n\nAn OTP application to implement throttling/rate limiting of resources.\n\n## Rebar3 dependency\n\n```erl\n{throttle, \"0.3.0\", {pkg, lambda_throttle}}\n```\n\n## Build\n\n    $ rebar3 compile\n\n## Usage\n\nThe application allows to limit different resources (scopes) at different rates.\n\n*  `throttle:setup(Scope, RateLimit, RatePeriod)`: setup a rate limit\n   for a given `Scope`, allowing at most `RateLimit` requests per\n   `RatePeriod`. Allowed rate periods are `per_second`, `per_minute`,\n   `per_hour` and `per_day`.\n\n   Rates can also be set via application environment instead of\n   calling `setup`:\n\n   ```erlang\n   {throttle, [{rates, [{my_global_scope, 10, per_second}\n                        {my_expensive_endpoint, 2, per_minute}]}]}\n   ```\n\n* `throttle:check(Scope, Key)`: attempt to request `Scope` with a\n  given `Key` (e.g. user token, IP). The result will be `{ok,\n  RemainingAttempts, TimeToReset}` if there are attempts left or\n  `{limit_exceeded, 0, TimeToReset}` if there aren't.\n\n* `throttle:peek(Scope, Key)`: returns the same result as `check`\n  without increasing the requests count.\n\n### Distributed support\n\nBy default, throttle keeps the attempt counters on ETS tables, and\ntherefore those are local to the Erlang node. Mnesia can be used\ninstead to enfore access limits across all connected nodes, by setting\nthe `driver` configuration parameter to `throttle_mnesia`:\n\n``` erlang\n{throttle, [{driver, throttle_mnesia},\n            {rates, [{my_global_scope, 10, per_second}]}]}\n```\n\nWhen using the Mnesia driver, `throttle_mnesia:setup()` needs to be\ncalled after the cluster is connected (the tables have to be shared across\nnodes, so the nodes must be visible before intialization):\n\n``` erlang\n(n1@127.0.0.1)1\u003e application:set_env(throttle, driver, throttle_mnesia).\nok\n(n1@127.0.0.1)2\u003e application:ensure_all_started(throttle).\n{ok,[throttle]}\n(n1@127.0.0.1)3\u003e net_kernel:connect('n2@127.0.0.1').\ntrue\n(n1@127.0.0.1)4\u003e throttle_mnesia:setup().\nok\n```\n\nWhen checking for a Key to access a given Scope, an access counter is\nincremented in Mnesia. The\n[activity access context](http://learnyousomeerlang.com/mnesia#access-and-context)\nfor that operation can be configured with the `access_context`\nparameter:\n\n``` erlang\n{throttle, [{driver, throttle_mnesia},\n            {access_context, sync_transaction}]}.\n```\n\nBy default, the `async_dirty` context is used, which prioritizes speed\nover consistency when propagating the counter increment. This means\nthere's a chance of two nodes getting access to a resource when there\nis one attempt left. Depending the application, it may make more\nsense to choose a different context (like `sync_transaction`) to\nreduce the chances of allowing accesses above the limit.\n\n## Examples\n\n### Shell\n``` erlang\n1\u003e application:ensure_all_started(throttle).\n{ok,[throttle]}\n2\u003e throttle:setup(my_api_endpoint, 3, per_minute).\nok\n3\u003e throttle:check(my_api_endpoint, my_token_or_ip).\n{ok,2,30362}\n4\u003e throttle:check(my_api_endpoint, my_token_or_ip).\n{ok,1,29114}\n5\u003e throttle:check(my_api_endpoint, my_token_or_ip).\n{ok,0,27978}\n6\u003e throttle:check(my_api_endpoint, my_token_or_ip).\n{limit_exceeded,0,26722}\n```\n\n### Cowboy 2.0 limit by IP\n\nMiddleware module:\n\n``` erlang\n-module(throttling_middleware).\n\n-behavior(cowboy_middleware).\n\n-export([execute/2]).\n\nexecute(Req, Env) -\u003e\n  {{IP, _}, Req2} = cowboy_req:peer(Req),\n\n  case throttle:check(my_api_rate, IP) of\n    {limit_exceeded, _, _} -\u003e\n      lager:warning(\"IP ~p exceeded api limit\", [IP]),\n      Req3 = cowboy_req:reply(429, Req2),\n      {stop, Req3};\n    _ -\u003e\n      {ok, Req2, Env}\n  end.\n```\n\nUsing it:\n\n``` erlang\ncowboy:start_clear(my_http_listener, [{port, 8080}], #{\n\t\tenv =\u003e #{dispatch =\u003e Dispatch},\n\t\tmiddlewares =\u003e [cowboy_router, throttling_middleware, cowboy_handler]\n\t}),\n```\n\n### Cowboy 2.0 limit by Authorization header\n\n``` erlang\n-module(throttling_middleware).\n\n-behavior(cowboy_middleware).\n\n-export([execute/2]).\n\nexecute(Req, Env) -\u003e\n  Authorization = cowboy_req:header(\u003c\u003c\"authorization\"\u003e\u003e, Req),\n\n  case throttle:check(my_api_rate, Authorization) of\n    {limit_exceeded, _, _} -\u003e\n      lager:warning(\"Auth ~p exceeded api limit\", [Authorization]),\n      Req3 = cowboy_req:reply(429, Req),\n      {stop, Req2};\n    _ -\u003e\n      {ok, Req, Env}\n  end.\n```\n\nNote that assumes all requests have an authorization header. A more\nrealistic approach would be to fallback to an IP limit when\nAuthorization is not present.\n\n### Cowboy 1.0 limit by IP\n\nMiddleware module:\n\n``` erlang\n-module(throttling_middleware).\n\n-behavior(cowboy_middleware).\n\n-export([execute/2]).\n\nexecute(Req, Env) -\u003e\n  {{IP, _}, Req2} = cowboy_req:peer(Req),\n\n  case throttle:check(my_api_rate, IP) of\n    {limit_exceeded, _, _} -\u003e\n      lager:warning(\"IP ~p exceeded api limit\", [IP]),\n      {error, 429, Req2};\n    _ -\u003e\n      {ok, Req2, Env}\n  end.\n```\n\nUsing it:\n\n``` erlang\ncowboy:start_http(my_http_listener, 100, [{port, 8080}],\n                    [{env, [{dispatch, Dispatch}]},\n                     {middlewares, [cowboy_router, throttling_middleware, cowboy_handler]}]\n                   ),\n```\n\nA more detailed example, choosing the rate based on the path, can be found [here](https://github.com/lambdaclass/holiday_ping/blob/26a3d83faaad6977c936a40fe273cd45954d9259/src/throttling_middleware.erl).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdaclass%2Fthrottle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flambdaclass%2Fthrottle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdaclass%2Fthrottle/lists"}