Rate Limiting
Learn how to protect your Hive Gateway from abuse by configuring field-level rate limiting, using either programmatic configuration or the @rateLimit directive in your subgraph schema.
Rate limiting is a common API security practice that protects your gateway and upstream subgraphs from being overwhelmed by too many requests. Because GraphQL is highly flexible (clients choose exactly which fields to query), rate limiting needs to be applied at the field level rather than just on HTTP endpoints.
Hive Gateway supports two complementary approaches:
- Programmatic configuration: define rate limits directly in
gateway.config.ts. @rateLimitdirective: annotate fields in your subgraph schemas.
By default, rate limiting state is kept in-memory and is local to each gateway instance. For deployments with multiple gateway instances, configure a shared Redis cache so limits are enforced consistently across all instances.
Programmatic Configuration
Use rateLimiting with an array of rules to configure per-field limits directly in
gateway.config.ts. Each rule targets a specific GraphQL type and field, and lets you customize
the time window, request limit, and how callers are identified.
Limit by Authorization Token
A common pattern is to rate-limit each unique user identified by their authorization token:
import { defineConfig } from "@graphql-hive/gateway";
export const gatewayConfig = defineConfig({
rateLimiting: [
{
type: "Query",
field: "searchProducts",
max: 10, // allow at most 10 calls…
ttl: 60000, // …per 60 seconds (in milliseconds)
identifier: "{context.headers.authorization}", // per unique token
},
],
});Limit by IP Address
If your gateway does not have an authenticated user, you can use the request IP address as the identifier:
import { defineConfig } from "@graphql-hive/gateway";
export const gatewayConfig = defineConfig({
rateLimiting: [
{
type: "Mutation",
field: "createComment",
max: 5,
ttl: 60000, // 60 seconds
identifier: "{context.headers.x-forwarded-for}",
},
],
});Multiple Rules
You can define rate limits for multiple fields at once:
import { defineConfig } from "@graphql-hive/gateway";
export const gatewayConfig = defineConfig({
rateLimiting: [
{
type: "Query",
field: "searchProducts",
max: 10,
ttl: 60000,
identifier: "{context.headers.authorization}",
},
{
type: "Mutation",
field: "createOrder",
max: 3,
ttl: 60000,
identifier: "{context.headers.authorization}",
},
],
});Configuration Options
| Option | Type | Description |
|---|---|---|
type | string | The GraphQL type that contains the field to rate-limit (e.g. "Query", "Mutation"). |
field | string | The field name on the given type to rate-limit. |
max | number | Maximum number of requests allowed within the ttl window. |
ttl | number | Duration of the time window in milliseconds (e.g. 60000 for 1 minute). |
identifier | string | Template string for identifying the caller. Use {context.*} to access request context values such as headers. |
Rate Limiting through @rateLimit Directive
When using Federation, you can annotate fields directly in your subgraph schemas with the
@rateLimit directive. This keeps rate-limit intent close to the schema definition and requires
minimal gateway configuration.
Step 1: Enable directive-based rate limiting in the gateway
import { defineConfig } from "@graphql-hive/gateway";
export const gatewayConfig = defineConfig({
rateLimiting: true,
});Step 2: Add the directive definition to your subgraph
Include the @rateLimit directive definition and import it via Federation's @composeDirective so
the gateway picks it up:
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: ["@composeDirective"]
)
@link(
url: "https://the-guild.dev/graphql/mesh/spec/v1.0"
import: ["@rateLimit"]
)
@composeDirective(name: "@rateLimit")
directive @rateLimit(
max: Int
window: String
message: String
identityArgs: [String]
arrayLengthField: String
) on FIELD_DEFINITIONStep 3: Annotate fields
Apply the @rateLimit directive to any field you want to protect:
type Query {
getItems: [Item]
@rateLimit(window: "1s", max: 5, message: "You are doing that too often.")
searchProducts(query: String!): [Product] @rateLimit(window: "1m", max: 30)
}
type Mutation {
createOrder(input: OrderInput!): Order
@rateLimit(
window: "1m"
max: 5
message: "Too many orders, please slow down."
)
}Limit per Argument (e.g. per ID)
Use identityArgs to rate-limit access per field argument, for example, limiting how often each
unique product ID can be fetched:
type Query {
getProduct(id: ID!): Product
@rateLimit(window: "1m", max: 10, identityArgs: ["id"])
}Limit by Array Length
For mutations or queries that accept arrays, you can count each element in the array as a separate
call toward the limit using arrayLengthField:
type Mutation {
bulkCreateItems(items: [ItemInput!]!): [Item]
@rateLimit(window: "1m", max: 100, arrayLengthField: "items")
}Directive Field Reference
| Option | Type | Description |
|---|---|---|
window | string | Time interval for the rate limit window. Accepts human-readable durations such as "1s", "30s", "1m", "1h". |
max | number | Maximum number of calls to the field allowed within the window. |
identityArgs | [string] | Field argument names used to distinguish callers. For example, ["id"] rate-limits each unique value of the id argument separately. Supports nested paths via dot notation (e.g. "input.userId"). |
message | string | Custom error message returned when the rate limit is exceeded. |
arrayLengthField | string | Name of an array argument whose length is counted as the number of calls (useful for bulk operations). |
Distributed Rate Limiting with Redis
By default, rate limit counters are stored in memory on each gateway instance. In a multi-instance deployment this means every instance has its own independent counter, so the effective rate limit is multiplied by the number of instances.
To enforce rate limits consistently across all gateway instances, configure a shared Redis cache:
import { defineConfig } from "@graphql-hive/gateway";
export const gatewayConfig = defineConfig({
cache: {
type: "redis",
url: "redis://localhost:6379",
},
rateLimiting: [
{
type: "Query",
field: "searchProducts",
max: 10,
ttl: 60000,
identifier: "{context.headers.authorization}",
},
],
});The Redis cache option currently only works in Node.js environments. See the Performance & Caching page for all available cache backends and configuration options.