Hive GatewayExtend Your GatewaySecurity

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:

  1. Programmatic configuration: define rate limits directly in gateway.config.ts.
  2. @rateLimit directive: 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:

gateway.config.ts
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:

gateway.config.ts
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:

gateway.config.ts
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

OptionTypeDescription
typestringThe GraphQL type that contains the field to rate-limit (e.g. "Query", "Mutation").
fieldstringThe field name on the given type to rate-limit.
maxnumberMaximum number of requests allowed within the ttl window.
ttlnumberDuration of the time window in milliseconds (e.g. 60000 for 1 minute).
identifierstringTemplate 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

gateway.config.ts
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:

subgraph.graphql
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_DEFINITION

Step 3: Annotate fields

Apply the @rateLimit directive to any field you want to protect:

subgraph.graphql
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:

subgraph.graphql
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:

subgraph.graphql
type Mutation {
  bulkCreateItems(items: [ItemInput!]!): [Item]
    @rateLimit(window: "1m", max: 100, arrayLengthField: "items")
}

Directive Field Reference

OptionTypeDescription
windowstringTime interval for the rate limit window. Accepts human-readable durations such as "1s", "30s", "1m", "1h".
maxnumberMaximum 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").
messagestringCustom error message returned when the rate limit is exceeded.
arrayLengthFieldstringName 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:

gateway.config.ts
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.