Kindred
Node.js League of Legends v3 API wrapper with built-in rate-limiting (enforced per region, burst/spread, follows retry headers), caching (in-memory, Redis), automatic retries, and parameter checking on top of Riot's League of Legends API.
Refer to Wiki for Complete Documentation and Working Examples!
Table of Contents:
Core Features
- All standard endpoints covered but tournament endpoints.
- Supports both callbacks and promises.
- Burst/Spread rate limiter that is enforced per region and respects method rate limits (per region as well).
- Retries on 429 and >= 500 until all calls are successful.
- Follows and respects retry headers.
- Built-in parameter checks so you can hopefully refer to documentation less! :)
- Checks type of parameters (id, name, account id).
- Checks if you're not passing valid query parameters!
- Built-in caching (in-memory and Redis).
- Customized expiration timers. You can set a timer for each endpoint type. Refer to Caching for more info.
- Designed to be simple but convenient. For example, you can call an exclusively by-id endpoint (such as grabbing the runes of a player) with just the summoner name.
- Tons of config (showing debug, configurable number of retries, turning off auto-retry, changing method rate limits, etcetc). Check out Initialization for all configs.
How the Methods Work
All list
and by.xxx
functions will have standard parameters.
Any other method will always take in an object as the first parameter, and an optional callback as the second.
These methods can work with different type of parameters (id, name, accountId) when applicable.
What I like about the functions that take object parameters is that you can declare config objects and pass in things really cleanly instead of worrying about the order of parameters in standard functions.
These functions take in an optional region
and an optional options
parameter (whenever possible) WITHIN the same object parameter. Most of the time, when they're called, they look like this:
Object and Callback Functions
const id = 32932398
const name = 'Contractz'
const accId = 47776491
k.Matchlist.get({ id }, KindredAPI.print)
k.Matchlist.get({ name }, KindredAPI.print)
k.Matchlist.get({ accId }, KindredAPI.print)
k.Summoner.get({ id }, KindredAPI.print)
k.Summoner.get({ name }, KindredAPI.print)
k.Summoner.get({ accId }, KindredAPI.print)
const config = {
id: 6323,
region: REGIONS.NORTH_AMERICA,
options: {
tags: ['image', 'sanitizedDescription']
}
}
k.Static.mastery(config, KindredAPI.print)
const matchlistConfig = {
name: 'Contractz',
options: {
queue: QUEUES.TEAM_BUILDER_RANKED_SOLO,
champion: 67
}
}
k.Matchlist.get(matchlistConfig, KindredAPI.print)
const runesConfig = {
options: {
locale: 'es_ES',
tags: 'stats'
},
region: REGIONS.NORTH_AMERICA
}
k.Static.runes(runesConfig, KindredAPI.print)
const championsConfig = {
options: {
tags: 'all',
version: '7.9.1'
}
}
k.Static.champions(championsConfig, KindredAPI.print)
const koreaChampListConfig = {
options: {
tags: 'all'
},
region: REGIONS.KOREA
}
k.Static.champions(koreaChampListConfig)
.then(data => console.log(data))
.catch(error => console.error(error))
const rakanConfig = {
id: 497,
options: {
tags: 'all'
},
region: REGIONS.BRAZIL
}
k.Static.champion(rakanConfig)
.then(data => console.log(data))
.catch(err => console.error(err))
Standard Functions
const QUEUES = KindredAPI.QUEUE_TYPES
k.Summoner.by.name('Contractz', KindredAPI.print)
k.Summoner.by.id(32932398, KindredAPI.print)
k.Summoner
.by.account(47776491)
.then(data => console.log(data))
.catch(error => console.error(error))
const opts = {
queue: [QUEUES.TEAM_BUILDER_RANKED_SOLO, QUEUES.RANKED_FLEX_SR],
champion: 81
}
k.Matchlist.by.name('Contractz', opts, KindredAPI.print)
k.Matchlist.by.name('Contractz', KindredAPI.print)
k.Matchlist
.by.name('Contractz')
.then(data => console.log(data))
.catch(error => console.error(error))
k.Matchlist
.by.name('sktt1peanut', REGIONS.KOREA)
.then(data => console.log(data))
.catch(error => console.error(error))
k.Matchlist
.by.name('sktt1peanut', opts, REGIONS.KOREA)
.then(data => console.log(data))
.catch(error => console.error(error))
k.Static.Champion.list({ tags: 'all' }, KindredAPI.print)
k.Static.Champion.list(KindredAPI.print)
k.Static.Champion.list(REGIONS.KOREA, KindredAPI.print)
k.Static.Champion.list({ tags: 'all' }, REGIONS.KOREA, KindredAPI.print)
Make sure to check out the Wiki
for working, copy-pastable examples.
Quickstart
- Debug on
- Dev key rate limiting per region
- In-memory (JS) cache with default settings on for quick scripts
- Burst Rate Limiter
This initialization's purpose should be fairly obvious; This is just for quickly starting on a project. You can always easily replace the QuickStart code with the code required for initializing through the regular way (see below), and Kindred will work just fine.
Start out with this if you want to make test calls right away to play around with the library and decide if you like it.
For regular and more customizable Initialization (caching, spread rate limiter), refer to wiki.
const KindredAPI = require('kindred-api')
const REGIONS = KindredAPI.REGIONS
const QUEUES = KindredAPI.QUEUE_TYPES
const debug = true
const k = KindredAPI.QuickStart('YOUR_KEY', REGIONS.NORTH_AMERICA, debug)
const Q_STRINGS = KindredAPI.QUEUE_STRINGS
k.Challenger.list(Q_STRINGS.RANKED_SOLO_5x5)
k.Summoner.get({ id: 32932398 }, KindredAPI.print)
k.Summoner.get({ name: 'Contractz' }, KindredAPI.print)
k.Summoner.by.id(32932398, KindredAPI.print)
k.Summoner.by.name('Contractz', REGIONS.NORTH_AMERICA, KindredAPI.print)
const runesConfig = {
options: {
locale: 'es_ES',
tags: 'stats'
},
region: REGIONS.NORTH_AMERICA,
}
k.Static.runes(runesConfig, KindredAPI.print)
const name = 'do not nuke PROD'
const region = REGIONS.NORTH_AMERICA
const options = {
queue: [QUEUES.TEAM_BUILDER_RANKED_SOLO, QUEUES.RANKED_FLEX_SR],
champion: 79
}
k.Summoner
.get({ name, region })
.then(data => k.Matchlist.get(
{ accId: data.accountId, options }
)
)
.then(data => console.log(data))
.catch(err => console.error(err))
k.Matchlist
.get({ name, region, options })
.then(data => console.log(data))
.catch(err => console.error(err))
const accId = 47776491
const id = 32932398
k.Matchlist.get({ name }, KindredAPI.print)
k.Matchlist.get({ accId }, KindredAPI.print)
k.Matchlist.get({ id }, KindredAPI.print)
k.Runes.get({ name }, KindredAPI.print)
k.Summoner.runes({ name }, KindredAPI.print)
k.Matchlist.get({ name }, KindredAPI.print)
k.Summoner.matchlist({ name }, KindredAPI.print)
k.Matchlist.recent({ name }, KindredAPI.print)
k.Summoner.matchHistory({ name }, KindredAPI.print)
const koreaChampListConfig = {
options: {
tags: 'all'
},
region: REGIONS.KOREA
}
k.Static.champions(koreaChampListConfig)
.then(data => console.log(data))
.catch(error => console.error(error))
const rakanConfig = {
id: 497,
options: {
tags: 'all'
},
region: REGIONS.BRAZIL
}
k.Static.champion(rakanConfig)
.then(data => console.log(data))
.catch(err => console.error(err))
k.Static.Champion
.list({ tags: 'all' }, REGIONS.KOREA)
.then(data => console.log(data))
.catch(error => console.error(error))
k.Static.Champion
.by.id(497, { tags: 'all' })
.then(data => console.log(data))
.catch(error => console.error(err))
Known Issues
It'd be cool if we could do something like this library. Adjusting the headers on the first request would be really useful for scripts and all that to prevent rate-limiting (especially when they have high production limits) on app/script restart.
Good explanation by Matviy##4429 in #RiotAPIDevCommunity
The issue with the "burst and stop" method is that the API queues all incoming requests and can only process them so fast. If the requests get stale in the queue (few seconds), then the API won't even try processing them and will just return an error 500 instead.
You'll see this if you burst more than a few hundred requests or so at once, a couple will go through, and then suddenly you'll get a few hundred 500 errors all at once
This is now in the TODO.
Both caches currently (JS in-memory, Redis) are primitive implementations, and can possibly exceed memory limitations.
I haven't had to deal with this in my smaller applications (One Tricks for example) and scripts, but I'm guessing some people might use this library for bigger applications. I can add an LRU cache (and MongoDB) as well as a reset() function or something if people start asking.
One Tricks
is simple, but does take a lot of requests currently (probably 25000~ if nothing's cached).
However, I simply use the cache to store all the summoner information and champion information, grind the stats information, and put the algorithmically-processed data in my database.
The difference between my site and other applications people seem to be working on is everyone seems to be doing some mini-op.gg type of thing which would probably demand a more concrete library with better tools, especially with the deprecation of the Stats endpoint.
Rate Limiter is not as optimized as it should be. (FIXED 2.0.33)
This is problematic because certain calls such as getCurrentGame, which will hit 404's often, will always retry up to 3 times.
This means it'll send a request, get a 404, and then send three more requests for a total of 3 unnecessary requests.
k.CurrentGame.get({ name: 'Contractz' })