Rate Limiting API Reference
getConsumerIdentifiers
Configure the rate limiting to understand which consumers to group into the same rate limiting bucket. Note that returned values are always hashed before being used as a rate limiting bucket.
A common example is to rate limit authenticated users based on the authentication token and public users based on the IP address:
import { Config } from 'stellate'
const config: Config = {
config: {
getConsumerIdentifiers: (req) => {
return {
user: req.headers.authorization || req.ip,
}
},
},
}
export default config
You can then select which consumer identifier to use with the rateLimit.consumerIdentifier
option:
We allow returning multiple consumer identifiers from the function because we plan to support multiple rate limits for different kinds of identifiers in the future.
import { Config } from 'stellate'
const config: Config = {
config: {
getConsumerIdentifiers: (req) => {
return {
user: req.headers.authorization || req.ip,
}
},
rateLimits: [
{
// Use the "user" consumer identifier
consumerIdentifier: 'user',
name: 'IP limit',
limit: {
type: 'RequestCount',
budget: 10,
window: '1s',
},
},
],
},
}
export default config
req
The req
argument passed to the function contains the following information:
interface EdgeRequest {
method: string
path: string
queryString: string
queryParams: Record<string, string[]>
headers: Record<Lowercase<string>, string[]>
ip: string
jwt: Record<string, unknown> | null
rootFields: { name: string; args: Record<string, unknown>; alias?: string }[]
operation: 'query' | 'mutation' | 'subscription'
}
HTTP headers are case-insensitive. We normalize all keys of the req.headers
record to use lowercase string values only. If you use Authorization
in your
HTTP client, it will be available as req.headers.authorization
. If you send
somthing like X-My-Custom-Header
it will be available as
req.headers['x-my-custom-header']
.
rateLimits
Configure the rate limits you want to apply to your GraphQL API. rateLimits
can both be a list [...]
of rate limit rules or, if you need more flexibility, a callback req => [...]
that returns a dynamic list of rate limits, which can be uniquely generated per request.
Independent of function or not, each rule in the list of rate limit rules has the following properties:
Rate Limit Rule
rule.state
Can be enabled
, disabled
or dryRun
.
In dryRun
mode, the rate limiting will not block any request if the budget is exceeded. The requests will pass as they would without a rate limit. While in dryRun
mode, we will soon show you which requests would’ve been blocked in the Metrics UI.
rule.groupBy
Must be a key that is returned by getConsumerIdentifiers
. Every unique consumer identifier will have a separate budget.
rule.group
Unlike groupBy
, which refers to a specific consumer identifier returned by getConsumerItenfifiers
such as ip
or my-identifier
, the group
property is only available within rate limiting functions and can use a concrete value from the request object:
import { Config } from 'stellate'
const config: Config = {
config: {
rateLimits: (req) => [
{
name: 'My Rule',
state: 'enabled',
group: req.headers['authorization'] ?? req.ip,
limit: {
type: 'RequestCount',
budget: 10,
window: '1s',
},
},
],
},
}
export default config
rule.limit.type
Please note that we currently recommend using only the RequestCount
limit type. This can be used in conjunction with our Rate Limiting Helpers
to apply limits only to GraphQL operations matching specific constraints. This is more powerful than "traditional" request-based rate limits.
Can be one of two options:
RequestCount
: Only allow a certain number of requests to pass. (e.g. 1000 requests per 1 minute): (deprecated)QueryComplexity
Only allow a certain number of complexity points to pass. (e.g., 1000 query complexity points per 1 minute)The complexity depends on the query depth and number of attributes. See How to determine the query complexity budget.
rule.limit.window
Time frame for which the expenses of the requests are summed up for. The maximum window is one hour.
Example formats: "1m"
, "1h"
, { value: 10, unit: "minute" }
rule.limit.budget
The maximum budget to allow during the window
before rate limiting the consumer. This is the “number of request” that match this very rule.
TypeScript Definition
For the TypeScript fans out there, here is the full TypeScript definition of the Rate Limiting API:
export interface RateLimitConfig {
// getConsumerIdentifiers is optional
getConsumerIdentifiers?: (
req: EdgeRequest,
) => Record<string, string | undefined>
// a list of rate limits, can be static, or returned by a callback
rateLimits?:
| Array<RateLimitRule | null>
| ((req: EdgeRequest) => Array<DynamicRateLimitRule | null | undefined>)
}
export interface RateLimitRule {
name: string
description?: string
state?: 'enabled' | 'disabled' | 'dryRun'
groupBy: GroupBy
limit: {
type: 'RequestCount'
budget: number
window:
| `${number}${'s' | 'm' | 'h'}`
| {
value: number
unit: 'seconds' | 'minutes' | 'hours'
}
}
}
// When using a callback, rules can either use groupBy or return a group value directly
export type DynamicRateLimitRule = Omit<RateLimitRule, 'groupBy'> &
(
| {
groupBy: GroupBy
}
| {
// Return the actual group value directly (eg. req.ip)
group: string
}
)
// GroupBy allows specifying how requests are bucketed without the need for a getConsumerIdentifiers function
export type GroupBy =
| 'ip'
| { header: string }
| { jwt: string | string[] }
| { cookie: string }
| { consumerIdentifier: string }
export interface EdgeRequest {
method: string
path: string
queryString: string
queryParams: Record<string, string[]>
headers: Record<Lowercase<string>, string[]>
ip: string
jwt: Record<string, unknown> | null
// Add list of root fields and their arguments (variables should be resolved for arguments)
rootFields: { name: string; args: Record<string, unknown>; alias?: string }[]
operation: 'query' | 'mutation' | 'subscription'
}
Examples
We curated a list of real-world examples for you that show-case the power of Stellate's Rate Limiting. You can check them out here: