### Run Interactive Demo Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/README.md Follow these steps to set up and run the interactive demo locally. This includes installing dependencies and starting the development server. ```bash cd examples/visualization npm install npm run dev ``` -------------------------------- ### Complete Configuration Example for GraphQL Rate Limiting Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/configuration.md This snippet demonstrates the full setup for GraphQL rate limiting with Redis. It includes initializing the Redis client, creating the rate limit directive transformer with custom options, defining the schema with rate limit directives, and applying the transformer to the schema. Ensure Redis is running and accessible via the configured host and port. ```typescript import { makeExecutableSchema } from "@graphql-tools/schema"; import Redis from "ioredis"; import { RateLimiterRedis } from "rate-limiter-flexible"; import { createRateLimitDirective, rateLimitDirectiveTypeDefs, createCompositeKeyGenerator, } from "graphql-rate-limit-redis-esm"; // Initialize Redis const redis = new Redis({ host: process.env.REDIS_HOST || "localhost", port: parseInt(process.env.REDIS_PORT || "6379"), retryStrategy: () => 5000, }); // Create rate limit transformer const rateLimitTransformer = createRateLimitDirective({ // Rate limiter implementation limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis, }, // Custom key generation keyGenerator: createCompositeKeyGenerator((ctx) => ({ userId: ctx.user?.id, apiKey: ctx.apiKey, tier: ctx.user?.tier || "free", })), // Default key generator options (if using built-in) defaultKeyGeneratorOptions: { anonymousIdentity: "guest", includeUserId: true, includeIP: true, includeApiKey: true, trustProxy: false, }, // Runtime safety limits runtimeLimits: { maxLimit: 1_000_000, maxDurationSeconds: 31_536_000, maxKeyLength: 512, maxLimiterCacheSize: 10_000, }, // Error handling mode serviceErrorMode: "failClosed", }); // Build schema const baseSchema = makeExecutableSchema({ typeDefs: ` ${rateLimitDirectiveTypeDefs} type Query { login: String! @rateLimit(limit: 5, duration: 60) search: [String!]! @rateLimit(limit: 100, duration: 3600) } `, resolvers: { /* ... */ }, }); // Apply rate limiting const schema = rateLimitTransformer(baseSchema); ``` -------------------------------- ### Example RateLimitDirectiveConfig Initialization Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md An example demonstrating how to initialize the RateLimitDirectiveConfig with RateLimiterRedis, store client, and a custom key generator. ```typescript const config: RateLimitDirectiveConfig = { limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, keyGenerator: createUserKeyGenerator((ctx) => ctx.user?.id), serviceErrorMode: "failClosed", }; ``` -------------------------------- ### Install with pnpm Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/README.md Installs the graphql-rate-limit-redis-esm package and its peer dependencies using pnpm. ```bash pnpm add graphql-rate-limit-redis-esm graphql @graphql-tools/utils @graphql-tools/schema rate-limiter-flexible ioredis ``` -------------------------------- ### Install with npm Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/README.md Installs the graphql-rate-limit-redis-esm package and its peer dependencies using npm. ```bash npm install graphql-rate-limit-redis-esm graphql @graphql-tools/utils @graphql-tools/schema rate-limiter-flexible ioredis ``` -------------------------------- ### Install with yarn Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/README.md Installs the graphql-rate-limit-redis-esm package and its peer dependencies using yarn. ```bash yarn add graphql-rate-limit-redis-esm graphql @graphql-tools/utils @graphql-tools/schema rate-limiter-flexible ioredis ``` -------------------------------- ### Install graphql-rate-limit-redis-esm and dependencies Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/README.md Installs the necessary packages for using graphql-rate-limit-redis-esm, including graphql, @graphql-tools/utils, rate-limiter-flexible, and ioredis. ```bash npm install graphql-rate-limit-redis-esm graphql @graphql-tools/utils rate-limiter-flexible ioredis ``` -------------------------------- ### Integration Examples Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Provides examples of how to integrate the rate limiting directive with popular GraphQL server implementations like Apollo Server and GraphQL Yoga. ```APIDOC ## Integration Examples ### Apollo Server ```typescript import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; import { rateLimitDirective } from 'graphql-rate-limit-redis-esm'; const typeDefs = ` directive @rateLimit(max: Int, window: String) on FIELD_DEFINITION type Query { hello: String @rateLimit(max: 10, window: "1m") } `; const resolvers = { Query: { hello: () => 'Hello world!', }, }; const server = new ApolloServer({ typeDefs, resolvers, // Apply the rateLimit directive schemaDirectives: { rateLimit: rateLimitDirective(), }, }); const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }); console.log(`🚀 Server ready at ${url}`); ``` ### GraphQL Yoga ```typescript import { createSchema, createYoga } from 'graphql-yoga'; import { rateLimitDirective } from 'graphql-rate-limit-redis-esm'; const typeDefs = ` directive @rateLimit(max: Int, window: String) on FIELD_DEFINITION type Query { hello: String @rateLimit(max: 10, window: "1m") } `; const resolvers = { Query: { hello: () => 'Hello world!', }, }; const schema = createSchema({ typeDefs, resolvers, // Apply the rateLimit directive directives: { rateLimit: rateLimitDirective(), }, }); const yoga = createYoga({ schema }); const server = Bun.serve({ fetch: yoga, port: 4000, }); console.log(`🚀 Server ready at http://localhost:${server.port}`); ``` ``` -------------------------------- ### Basic GraphQL Rate Limiting Setup Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/README.md Demonstrates how to create a rate-limited GraphQL schema using `graphql-rate-limit-redis-esm`. Ensure Redis is running and accessible. ```typescript import { makeExecutableSchema } from "@graphql-tools/schema"; import Redis from "ioredis"; import { RateLimiterRedis } from "rate-limiter-flexible"; import { createRateLimitDirective, rateLimitDirectiveTypeDefs, } from "graphql-rate-limit-redis-esm"; const redis = new Redis("redis://localhost:6379"); const transformRateLimit = createRateLimitDirective({ limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis, }, }); const schema = makeExecutableSchema({ typeDefs: ` ${rateLimitDirectiveTypeDefs} type Query { login: String! @rateLimit(limit: 5, duration: 60) } `, resolvers: { Query: { login: () => "ok", }, }, }); const schemaWithRateLimit = transformRateLimit(schema); ``` -------------------------------- ### Example RateLimiterOptions with RateLimiterRedis Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md An example of `RateLimiterOptions` configured for `RateLimiterRedis`. It includes a Redis client instance and specific options like `duration` and `points`. ```typescript import Redis from "ioredis"; const redis = new Redis("redis://localhost:6379"); const limiterOptions: RateLimiterOptions = { storeClient: redis, // Additional options passed to RateLimiterRedis: duration: 60, points: 10, }; ``` -------------------------------- ### Key Format Examples Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/README.md Illustrates the format of generated rate limiting keys, which include an identity component and the GraphQL field scope. These examples show different identity sources like user ID, IP address, API key, and anonymous fallback. ```text user:123:Query.login ip:192.168.1.1:Mutation.createPost apiKey:abc123:Query.search anonymous:Query.publicInfo ``` -------------------------------- ### RateLimiterClass Usage Example Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md Demonstrates how to assign a `RateLimiterClass` and configure `RateLimitDirectiveConfig` with it. This example uses `RateLimiterRedis`. ```typescript import { RateLimiterRedis } from "rate-limiter-flexible"; const limiterClass: RateLimiterClass = RateLimiterRedis; const config: RateLimitDirectiveConfig = { limiterClass, limiterOptions: { storeClient: redis, duration: 60, points: 10 }, }; ``` -------------------------------- ### Example RateLimiterInstance Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md An example implementation of the `RateLimiterInstance` interface. This mock consumes a point by decrementing a Redis key and expects the error to have a `msBeforeNext` property. ```typescript const limiter: RateLimiterInstance = { async consume(key: string) { // Consume one point; throws if limit exceeded // Error must have { msBeforeNext: number } property return await redisClient.decrement(key); }, }; ``` -------------------------------- ### Basic GraphQL Yoga Setup with Rate Limiting Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/integration-guide.md Sets up a GraphQL server using Yoga, integrates Redis for rate limiting, and defines a basic query with a rate limit directive. ```typescript import { createYoga } from "graphql-yoga"; import { createServer } from "http"; import { makeExecutableSchema } from "@graphql-tools/schema"; import Redis from "ioredis"; import { RateLimiterRedis } from "rate-limiter-flexible"; import { createRateLimitDirective, rateLimitDirectiveTypeDefs, } from "graphql-rate-limit-redis-esm"; const redis = new Redis("redis://localhost:6379"); const rateLimitTransformer = createRateLimitDirective({ limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, }); const typeDefs = ` ${rateLimitDirectiveTypeDefs} type Query { hello: String! @rateLimit(limit: 10, duration: 60) } `; const resolvers = { Query: { hello: () => "world", }, }; const baseSchema = makeExecutableSchema({ typeDefs, resolvers }); const schema = rateLimitTransformer(baseSchema); const yoga = createYoga({ schema, context: async ({ request }) => ({ ip: request.headers.get("x-forwarded-for") || "unknown", userId: request.headers.get("x-user-id"), }), }); const server = createServer(yoga); server.listen(4000, () => { console.log("Server ready at http://localhost:4000/graphql"); }); ``` -------------------------------- ### Custom Synchronous Key Generator Example Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md Provides an example of a custom synchronous KeyGenerator. This implementation generates a key based on the user ID and the specific GraphQL field being accessed. ```typescript const customKeyGen: KeyGenerator = (directiveArgs, source, args, context, info) => { const userId = context.user?.id || "anonymous"; const fieldScope = `${info.parentType.name}.${info.fieldName}`; return `user:${userId}:${fieldScope}`; }; ``` -------------------------------- ### Key Generator Examples in TypeScript Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/README.md Demonstrates how to create custom key generators using different strategies like user ID, IP address, or a composite of multiple fields. Ensure necessary imports are included. ```typescript import { createCompositeKeyGenerator, createIPKeyGenerator, createUserKeyGenerator, } from "graphql-rate-limit-redis-esm"; const byUser = createUserKeyGenerator((ctx) => ctx.user?.id); const byIp = createIPKeyGenerator((ctx) => ctx.req?.ip); const composite = createCompositeKeyGenerator((ctx) => [ ["userId", ctx.user?.id], ["apiKey", ctx.apiKey], ]); ``` -------------------------------- ### Configuration: Runtime Limits Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Configures runtime rate limits using the `limits` option. This example sets a limit of 100 requests per minute and 1000 requests per hour. ```typescript import { createRateLimitDirective } from "graphql-rate-limit-redis-esm"; const limiter = createRateLimitDirective({ redis: "redis://localhost:6379", limits: [ { duration: 60, points: 100 }, // 100 requests per minute { duration: 3600, points: 1000 }, // 1000 requests per hour ], }); ``` -------------------------------- ### Testing: Unit Test Example Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Provides a basic example of how to unit test the rate limiting directive. This involves mocking the Redis client and asserting the directive's behavior. ```typescript import { createRateLimitDirective } from "graphql-rate-limit-redis-esm"; import { mockClient } from 'redis-mock'; // Example mock library // Mock Redis client const mockRedisClient = mockClient(); const limiter = createRateLimitDirective({ redisClient: mockRedisClient, // Use the mock client limits: [{ duration: 60, points: 100 }], }); describe('RateLimitDirective', () => { it('should limit requests based on configuration', async () => { // Mock GraphQL execution context const context = { /* ... */ }; // Mock field resolver const next = jest.fn().mockResolvedValue('result'); // Call the directive's resolveField method await limiter.resolveField(null, {}, context, { fieldName: 'testField' }, next); // Assertions on mockRedisClient calls and 'next' function calls expect(mockRedisClient.incr).toHaveBeenCalled(); expect(next).toHaveBeenCalled(); }); }); ``` -------------------------------- ### Basic Apollo Server Setup with Rate Limiting Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Integrates the rate limiting directive into an Apollo Server instance. Ensure the directive is correctly configured with your desired limits and key generation strategy. ```typescript import { ApolloServer } from "@apollo/server"; import { startStandaloneServer } from "@apollo/server/standalone"; import { readFileSync } from "fs"; import { join } from "path"; import { createRateLimitDirective, DEFAULT_KEY_GENERATOR } from "graphql-rate-limit-redis-esm"; const typeDefs = readFileSync(join(__dirname, "./schema.graphql"), "utf-8"); const limiter = createRateLimitDirective({ // redis connection string redis: "redis://localhost:6379", // default limits limits: [ { duration: 60, points: 100 }, // 100 requests per minute { duration: 3600, points: 1000 }, // 1000 requests per hour ], // default key generator keyGenerator: DEFAULT_KEY_GENERATOR, }); const server = new ApolloServer({ typeDefs, resolvers, // register the directive schemaDirectives: { rateLimit: limiter, }, }); const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }); console.log(`🚀 Server ready at ${url}`); ``` -------------------------------- ### Safe Key Generator Example Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/errors.md Demonstrates how to create a safe composite key generator that ensures all generated values are non-null and within expected bounds, preventing RATE_LIMIT_KEY_ERROR. ```typescript import { createCompositeKeyGenerator, } from "graphql-rate-limit-redis-esm"; // ❌ Unsafe const unsafeKeyGen = createCompositeKeyGenerator((ctx) => ({ userId: ctx.user?.id, // Could be undefined })); // ✅ Safe const safeKeyGen = createCompositeKeyGenerator((ctx) => ({ userId: ctx.user?.id || "anonymous", // Ensure values are non-null and within bounds })); ``` -------------------------------- ### Asynchronous KeyGenerator Function Example Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md Demonstrates an asynchronous KeyGenerator function that fetches user permissions before generating a rate limit key. This is useful when key generation depends on external data. ```typescript const asyncKeyGen: KeyGenerator = async (directiveArgs, source, args, context, info) => { const permissions = await fetchPermissionsFromDB(context.user?.id); const tier = permissions.tier || "free"; return `tier:${tier}:${info.fieldName}`; }; ``` -------------------------------- ### Configure Rate Limiter with Service Error Modes Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md Examples demonstrating the two service error modes: 'failClosed' (secure default, blocks on failure) and 'failOpen' (availability-first, bypasses on failure). ```typescript // Secure default (block on Redis failure) const config1: RateLimitDirectiveConfig = { limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, serviceErrorMode: "failClosed", // default }; // Availability-first (allow requests if Redis is down) const config2: RateLimitDirectiveConfig = { limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, serviceErrorMode: "failOpen", }; ``` -------------------------------- ### GraphQL Yoga Integration with Rate Limiting Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Demonstrates how to integrate the rate limiting directive with GraphQL Yoga. This setup allows for schema-level rate limiting configuration. ```typescript import { createYoga } from "graphql-yoga"; import { createRateLimitDirective } from "graphql-rate-limit-redis-esm"; import { schema } from "./schema"; const limiter = createRateLimitDirective({ redis: "redis://localhost:6379", limits: [{ duration: 60, points: 100 }], }); const yoga = createYoga({ schema, // register the directive plugins: [ limiter.plugin({ // optional: override default key generator keyGenerator: (context) => { // custom logic to generate key return "custom-key"; }, }), ], }); // ... server setup using yoga instance ``` -------------------------------- ### Create Default Key Generator with Options Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md Example of creating a default key generator with custom options. This configuration influences which parts of the request context are used to generate a unique rate limit key. ```typescript const keyGen = createDefaultKeyGenerator({ anonymousIdentity: "guest", trustProxy: true, includeUserId: true, }); ``` -------------------------------- ### Apollo Server Basic Setup with Rate Limiting Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/integration-guide.md Integrates graphql-rate-limit-redis-esm with Apollo Server for basic rate limiting on GraphQL fields. Requires Redis and ioredis. Ensure Redis is running and accessible. ```typescript import { ApolloServer } from "@apollo/server"; import { startStandaloneServer } from "@apollo/server/standalone"; import { makeExecutableSchema } from "@graphql-tools/schema"; import Redis from "ioredis"; import { RateLimiterRedis } from "rate-limiter-flexible"; import { createRateLimitDirective, rateLimitDirectiveTypeDefs, } from "graphql-rate-limit-redis-esm"; // Create Redis connection const redis = new Redis({ host: process.env.REDIS_HOST || "localhost", port: parseInt(process.env.REDIS_PORT || "6379"), }); // Create rate limit transformer const rateLimitTransformer = createRateLimitDirective({ limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, }); // Define schema with rate-limited fields const typeDefs = ` ${rateLimitDirectiveTypeDefs} type Query { login(email: String!): String! @rateLimit(limit: 5, duration: 60) search(query: String!): [String!]! @rateLimit(limit: 20, duration: 60) } type Mutation { resetPassword(email: String!): Boolean! @rateLimit(limit: 3, duration: 300) } `; const resolvers = { Query: { login: async (_, { email }) => { // Login logic return "token"; }, search: async (_, { query }) => { // Search logic return [query]; }, }, Mutation: { resetPassword: async (_, { email }) => { // Reset logic return true; }, }, }; // Build and transform schema const baseSchema = makeExecutableSchema({ typeDefs, resolvers }); const schema = rateLimitTransformer(baseSchema); // Create and start server const server = new ApolloServer({ schema }); const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }); console.log(`Apollo Server ready at ${url}`); ``` -------------------------------- ### Handling RATE_LIMIT_KEY_ERROR in Client Code Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/errors.md Example of how to catch a RATE_LIMIT_KEY_ERROR in client code, logging an error message to indicate a potential configuration issue with key generation. ```typescript const client = useApolloClient(); try { const result = await client.query({ query: ... }); } catch (error) { if (error.graphQLErrors?.[0]?.extensions?.code === "RATE_LIMIT_KEY_ERROR") { console.error("Key generation failed; possible config issue"); // Log context for debugging (avoid exposing sensitive info) } } ``` -------------------------------- ### Configure Rate Limiter with Runtime Limits Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md Example of creating a rate limit directive with specific runtime limits. This configures the maximum request limit, duration, key length, and cache size. ```typescript const rateLimitTransformer = createRateLimitDirective({ limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, runtimeLimits: { maxLimit: 100_000, maxDurationSeconds: 86400, // 1 day maxKeyLength: 256, maxLimiterCacheSize: 5000, }, }); ``` -------------------------------- ### Multi-Tier Rate Limiting Configuration Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Sets up multiple tiers of rate limits, applying progressively stricter limits based on usage. This example defines a general limit and a stricter limit for authenticated users. ```typescript import { createRateLimitDirective } from "graphql-rate-limit-redis-esm"; const limiter = createRateLimitDirective({ redis: "redis://localhost:6379", // Define multiple limit tiers limits: [ { duration: 60, points: 100 }, // General limit: 100 requests/min { duration: 60, points: 50, keyGenerator: (context) => `premium:${context.currentUser?.id}` }, // Premium user limit: 50 requests/min ], }); ``` -------------------------------- ### Error Handling: Client-Side Retry Logic Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Provides an example of how a client can implement retry logic when encountering a rate limit error. This involves checking the `Retry-After` header and waiting before retrying. ```javascript async function makeGraphQLRequest(query, variables) { let retries = 0; const maxRetries = 3; while (retries < maxRetries) { try { const response = await fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query, variables }), }); const data = await response.json(); if (response.status === 429 || data.errors?.some(e => e.extensions?.code === 'RATE_LIMITED')) { const retryAfter = response.headers.get('Retry-After'); const waitTime = retryAfter ? parseInt(retryAfter, 10) * 1000 : 5000; console.warn(`Rate limit hit. Retrying in ${waitTime / 1000}s...`); await new Promise(resolve => setTimeout(resolve, waitTime)); retries++; continue; // Retry the request } if (data.errors) { throw new Error(`GraphQL Error: ${JSON.stringify(data.errors)}`); } return data.data; } catch (error) { console.error('Request failed:', error); throw error; } } throw new Error('Max retries exceeded'); } ``` -------------------------------- ### Handle Rate Limit Errors in Middleware Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/integration-guide.md This example demonstrates setting the 'Retry-After' header in middleware by inspecting response errors for 'RATE_LIMITED'. Ensure 'extractRetryAfter' is defined elsewhere. ```typescript import { ApolloServer } from "@apollo/server"; import { ERROR_CODES } from "graphql-rate-limit-redis-esm"; const server = new ApolloServer({ schema, plugins: { willSendResponse: async (context) => { const { response } = context; if (response.body?.kind === "complete") { const errors = response.body.string; // Set Retry-After header for rate limit errors if (errors.includes("RATE_LIMITED")) { // Parse and extract retryAfter const retryAfter = extractRetryAfter(errors); context.response.http.headers.set("Retry-After", String(retryAfter)); } } }, }, }); ``` -------------------------------- ### Integrating Rate Limit Directive into GraphQL Schema Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/api-reference/directive.md Example demonstrating how to include the rateLimitDirectiveTypeDefs and apply the @rateLimit directive to fields within your GraphQL schema using makeExecutableSchema. ```typescript import { makeExecutableSchema } from "@graphql-tools/schema"; import { rateLimitDirectiveTypeDefs } from "graphql-rate-limit-redis-esm"; const schema = makeExecutableSchema({ typeDefs: [ rateLimitDirectiveTypeDefs, ` type Query { login: String! @rateLimit(limit: 5, duration: 60) search: [String!]! @rateLimit(limit: 10, duration: 30) } `, ], resolvers: { /* ... */ }, }); ``` -------------------------------- ### Async Key Generator with Caching Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/integration-guide.md Shows an example of an asynchronous key generator that fetches user tier information from a database. It utilizes an LRU cache to store and retrieve tier data, reducing database load for repeated requests from the same user. ```typescript import { LRUCache } from "lru-cache"; const userTierCache = new LRUCache({ max: 1000 }); const keyGenerator: KeyGenerator = async ( directiveArgs, source, args, context, info, ) => { let tier = userTierCache.get(context.userId); if (!tier) { tier = await fetchUserTier(context.userId); // DB lookup userTierCache.set(context.userId, tier); } return `tier:${tier}:${info.fieldName}`; }; ``` -------------------------------- ### Create Default Key Generator Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/api-reference/key-generators.md Creates the built-in rate limit key generator with configurable identity extraction. Use this for basic setup or when needing to customize identity sources like user ID, IP, or API key. ```typescript import { createDefaultKeyGenerator } from "graphql-rate-limit-redis-esm"; // Basic usage with defaults const basicKeyGen = createDefaultKeyGenerator(); // Behind a trusted proxy const proxyKeyGen = createDefaultKeyGenerator({ trustProxy: true, }); // Custom anonymous identity const customAnonKeyGen = createDefaultKeyGenerator({ anonymousIdentity: "guest", trustProxy: false, }); // Only IP-based rate limiting const ipOnlyKeyGen = createDefaultKeyGenerator({ includeUserId: false, includeApiKey: false, }); ``` -------------------------------- ### Handling RATE_LIMITED Error in Middleware Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/errors.md Example of how to intercept GraphQL errors in Express middleware to set the 'Retry-After' header when a RATE_LIMITED error is detected. ```typescript import { createRateLimitedError } from "graphql-rate-limit-redis-esm"; app.use((req, res, next) => { // Intercept GraphQL errors const originalJson = res.json; res.json = function(data) { if (data.errors) { const rateLimitError = data.errors.find( (e) => e.extensions?.code === "RATE_LIMITED" ); if (rateLimitError) { res.set( "Retry-After", String(rateLimitError.extensions.retryAfter) ); } } return originalJson.call(this, data); }; next(); }); ``` -------------------------------- ### Inspect Rate Limit Errors in Resolver Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/integration-guide.md Rate limit errors are thrown before the resolver executes. This example shows that the resolver will not be called if a request is rate-limited. ```typescript import { ERROR_CODES } from "graphql-rate-limit-redis-esm"; const resolvers = { Query: { sensitiveData: async (_, args, context) => { // Rate limit errors are thrown before resolver executes // This resolver won't be called if rate limited return "sensitive"; }, }, }; ``` -------------------------------- ### Configuration Options Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Details the various configuration options available for the createRateLimitDirective function, DefaultKeyGeneratorOptions, and RateLimitRuntimeLimits, as well as environment variable configurations. ```APIDOC ## Configuration Options This library offers extensive configuration through several avenues: ### createRateLimitDirective() Options - **keyGenerator**: Function to generate a unique key for rate limiting. - **message**: Custom message for rate-limited responses. - **statusCode**: Custom HTTP status code for rate-limited responses. - **headers**: Boolean to enable/disable rate limit headers. - **skip**: Function to conditionally skip rate limiting. - **redisClient**: Optional pre-configured Redis client. ### DefaultKeyGeneratorOptions - **context**: The GraphQL context object. - **info**: The GraphQL resolve info object. - **args**: The arguments passed to the GraphQL field. - **request**: The incoming request object (if available). ### RateLimitRuntimeLimits - **max**: Maximum number of requests allowed. - **windowMs**: Time window in milliseconds. - **message**: Custom message for rate-limited responses. - **statusCode**: Custom HTTP status code for rate-limited responses. ### Environment Variables - **REDIS_URL**: URL for the Redis connection. - **REDIS_PORT**: Port for the Redis connection. - **REDIS_PASSWORD**: Password for Redis authentication. - **RATE_LIMIT_DEFAULT_MAX**: Default maximum requests. - **RATE_LIMIT_DEFAULT_WINDOW_MS**: Default time window in milliseconds. ``` -------------------------------- ### Basic Usage of graphql-rate-limit-redis-esm Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/README.md Demonstrates how to set up a basic GraphQL schema with rate limiting using graphql-rate-limit-redis-esm. It shows how to import necessary modules, configure Redis, create the rate limit directive, and apply it to a schema field. ```typescript import { makeExecutableSchema } from "@graphql-tools/schema"; import Redis from "ioredis"; import { RateLimiterRedis } from "rate-limiter-flexible"; import { createRateLimitDirective, rateLimitDirectiveTypeDefs, } from "graphql-rate-limit-redis-esm"; const redis = new Redis("redis://localhost:6379"); const schema = makeExecutableSchema({ typeDefs: ` ${rateLimitDirectiveTypeDefs} type Query { login: String! @rateLimit(limit: 5, duration: 60) } `, resolvers: { Query: { login: () => "token" }, }, }); const rateLimitTransformer = createRateLimitDirective({ limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, }); const rateLimitedSchema = rateLimitTransformer(schema); ``` -------------------------------- ### Run Benchmark Script Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/configuration.md Execute the benchmark script using pnpm. Optionally, customize the number of benchmark iterations by setting the BENCH_ITERATIONS environment variable. ```bash pnpm run benchmark # With custom iterations BENCH_ITERATIONS=10000 pnpm run benchmark ``` -------------------------------- ### Handling RATE_LIMITED Error in Client Code Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/errors.md Example of how to catch a RATE_LIMITED error in Apollo Client and implement a retry mechanism using setTimeout based on the retryAfter value. ```typescript import { gql } from "@apollo/client"; const client = useApolloClient(); try { const result = await client.mutate({ mutation:gql` mutation Login($email: String!) { login(email: $email) } `, variables: { email: "user@example.com" }, }); } catch (error) { if (error.graphQLErrors?.[0]?.extensions?.code === "RATE_LIMITED") { const retryAfter = error.graphQLErrors[0].extensions.retryAfter; console.log(`Rate limited. Retry after ${retryAfter}s`); // Implement exponential backoff setTimeout(() => retryRequest(), retryAfter * 1000); } } ``` -------------------------------- ### Trust Proxy Guidance and Configuration Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/api-reference/key-generators.md A helper string providing guidance for deployments that need trusted proxy support. Configure trustProxy: true in createDefaultKeyGenerator options only when your app runs behind a trusted proxy. ```typescript import { trustProxyGuidance } from "graphql-rate-limit-redis-esm"; console.log(trustProxyGuidance); // Output the guidance message in logs or documentation const keyGen = createDefaultKeyGenerator({ trustProxy: true, // Only when behind a trusted proxy! }); ``` -------------------------------- ### Correct Store Client Configuration Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/configuration.md Provide a valid `storeClient` within `limiterOptions` when using `RateLimiterRedis`. ```typescript // ✅ Correct const redis = new Redis("redis://localhost:6379"); const config = { limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, }; // ❌ Wrong const config = { limiterClass: RateLimiterRedis, limiterOptions: {}, // Missing storeClient }; ``` -------------------------------- ### Correct Limiter Options Configuration Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/errors.md Ensure limiterOptions.storeClient is provided and is a valid Redis instance. ```typescript const config = { limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, // Redis instance }; ``` -------------------------------- ### Run Benchmark Script Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/README.md Executes the benchmark suite for performance testing. Configuration can be adjusted via environment variables like BENCH_ITERATIONS, BENCH_WARMUP, and BENCH_ROUNDS. ```bash pnpm run benchmark ``` -------------------------------- ### Handle RATE_LIMIT_SERVICE_ERROR in GraphQL Error Handler Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/errors.md Implement a custom error handler to detect and manage RATE_LIMIT_SERVICE_ERROR. This example sets a 503 status code and a 'Retry-After' header, providing a fallback response. ```typescript app.use((err, req, res, next) => { if (err.graphQLErrors?.some((e) => e.extensions?.code === "RATE_LIMIT_SERVICE_ERROR")) { // Alert ops, fallback to readonly mode, or retry after delay console.error("Rate limit service down"); // Set 503 status and Retry-After res.status(503); res.set("Retry-After", "60"); res.json({ errors: [{ message: "Service temporarily unavailable" }] }); } next(); }); ``` -------------------------------- ### Create Rate Limit Directive with Custom Key Generator Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/api-reference/directive.md Customize rate limiting behavior by providing a custom key generator function. This example uses a user-specific key based on the context. ```typescript import { createUserKeyGenerator } from "graphql-rate-limit-redis-esm"; const rateLimitTransformer = createRateLimitDirective({ limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, keyGenerator: createUserKeyGenerator((ctx) => ctx.user?.id), }); ``` -------------------------------- ### Error Handling: Setting HTTP Headers Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Demonstrates how to set standard HTTP headers like `Retry-After` when a rate limit is exceeded. This provides clients with information on when they can retry. ```typescript import { RATE_LIMITED } from "graphql-rate-limit-redis-esm"; // In your Apollo Server or GraphQL Yoga setup, within the error handling: const server = new ApolloServer({ // ... plugins: [ { requestDidStart: () => ({ // This hook is called after the request is processed but before response is sent // It allows modification of the response based on errors executionDidEnd: async (result) => { if (result.errors) { for (const error of result.errors) { if (error.extensions?.code === RATE_LIMITED) { // Assuming you have access to the response object here // response.setHeader('Retry-After', error.extensions.retryAfter); } } } }, }), }, ], }); ``` -------------------------------- ### Async Key Generation with Database Lookups Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Demonstrates an asynchronous key generator that fetches additional data (e.g., user tier) from a database before generating the rate limit key. This allows for dynamic rate limiting rules. ```typescript import { createRateLimitDirective } from "graphql-rate-limit-redis-esm"; // Assume getUserTier is an async function that fetches user tier from DB const getUserTier = async (userId: string): Promise => { // ... database lookup logic ... return "premium"; // or "free" }; const limiter = createRateLimitDirective({ redis: "redis://localhost:6379", limits: [ { duration: 60, points: 100 }, { duration: 60, points: 50, keyGenerator: async (context) => { const userId = context.currentUser?.id; if (!userId) return "anonymous"; const tier = await getUserTier(userId); return `tier:${tier}:${userId}`; } }, ], }); ``` -------------------------------- ### Creating a Default Key Generator Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/README.md Instantiate the default key generator with options for trusting proxies and including user IDs in key generation. ```typescript const keyGen = createDefaultKeyGenerator({ trustProxy: true, includeUserId: true, }); ``` -------------------------------- ### GraphQL Error Handler Middleware Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/api-reference/constants.md An example middleware function that handles different GraphQL error codes by returning a structured response. It uses a switch statement to map error codes to appropriate statuses and messages. ```typescript import { ERROR_CODES } from "graphql-rate-limit-redis-esm"; function handleGraphQLError(error) { const code = error.extensions?.code; switch (code) { case ERROR_CODES.RATE_LIMITED: return { status: 429, message: "Too many requests", retryAfter: error.extensions.retryAfter, }; case ERROR_CODES.RATE_LIMIT_KEY_ERROR: return { status: 500, message: "Internal error", }; case ERROR_CODES.RATE_LIMIT_SERVICE_ERROR: return { status: 503, message: "Service temporarily unavailable", }; default: return { status: 500, message: "Unknown error", }; } } ``` -------------------------------- ### Correct Trust Proxy and Include IP Configuration Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/configuration.md When `trustProxy` is enabled, `includeIP` must also be enabled. ```typescript // ✅ Correct const options = { trustProxy: true, includeIP: true, }; // ❌ Wrong const options = { trustProxy: true, includeIP: false, }; ``` -------------------------------- ### Batch Key Generation Strategy Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/integration-guide.md Demonstrates a batch key generation strategy where multiple operations, like fetching user tiers, are batched together. This approach is useful for optimizing I/O operations when processing multiple requests from the same client. ```typescript const batchedKeyGenerator: KeyGenerator = async ( directiveArgs, source, args, context, info, ) => { const userId = context.userId; // Batch with other operations context.batchedOperations.add({ type: "fetchUserTier", userId, }); const tier = await context.batchedResults.get("fetchUserTier"); return `tier:${tier}:${info.fieldName}`; }; ``` -------------------------------- ### Run Redis Integration Tests Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/configuration.md Execute Redis integration tests using pnpm. Ensure the REDIS_URL environment variable is set to your Redis connection string; otherwise, tests will be skipped. ```bash REDIS_URL=redis://localhost:6379 pnpm test ``` -------------------------------- ### Apply Rate Limiting to GraphQL Schema Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md Demonstrates how to create a schema transformer and apply it to a base GraphQL schema to enable rate limiting on decorated fields. Ensure redis client is initialized and available. ```typescript import { GraphQLSchema } from "graphql"; import { createRateLimitDirective } from "graphql-rate-limit-redis-esm"; const transformer: SchemaTransformer = createRateLimitDirective({ limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, }); const baseSchema: GraphQLSchema = makeExecutableSchema({ typeDefs, resolvers }); const rateLimitedSchema: GraphQLSchema = transformer(baseSchema); ``` -------------------------------- ### RateLimiterInstance Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md Defines the minimal interface required from a rate limiter instance, primarily featuring the `consume` method for consuming points. ```APIDOC ## RateLimiterInstance ### Description Minimal interface required from a rate limiter instance. ### Methods | Method | Signature | Behavior | |--------|-----------|----------| | consume | `(key: string): Promise` | Consumes one point for the given key. Rejects if limit exceeded. | ### Rate Limit Rejection When the limit is exceeded, the rejected error must have a `msBeforeNext: number` property indicating milliseconds until the next request is allowed. ### Example ```typescript const limiter: RateLimiterInstance = { async consume(key: string) { // Consume one point; throws if limit exceeded // Error must have { msBeforeNext: number } property return await redisClient.decrement(key); }, }; ``` ``` -------------------------------- ### createIPKeyGenerator Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/api-reference/key-generators.md Creates a key generator that rate limits per IP address. It extracts the IP address from the context and generates a rate limiting key. ```APIDOC ## createIPKeyGenerator ### Description Creates a key generator that rate limits per IP address. ### Parameters #### Path Parameters - **getIP** ((context: TContext) => string | null | undefined) - Required - Function to extract IP address from context ### Return Type `KeyGenerator` — A function that generates keys based on IP address ### Behavior - If `getIP()` returns a value, the key is `ip:{ipAddress}:{ParentType}.{fieldName}` - If `getIP()` returns null/undefined, the key uses `unknown` as the IP: `ip:unknown:{ParentType}.{fieldName}` - IP values are normalized (trimmed, max 256 characters) ### Throws - `Error` — If `getIP` is not a function ### Example ```typescript import { createIPKeyGenerator } from "graphql-rate-limit-redis-esm"; // From Express request const ipKeyGen = createIPKeyGenerator((ctx) => ctx.req?.ip); // From connection info const connKeyGen = createIPKeyGenerator( (ctx) => ctx.connection?.remoteAddress ); // With proxy support (manually) const proxiedIPKeyGen = createIPKeyGenerator((ctx) => { const forwarded = ctx.req?.headers["x-forwarded-for"]; if (typeof forwarded === "string") { return forwarded.split(",")[0]?.trim(); } return ctx.req?.ip; }); ``` ``` -------------------------------- ### Configure Runtime Limits Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/configuration.md Set maximum values for requests, duration, key length, and cache size. Adjust these based on your application's needs and expected load. ```typescript const config = { limiterClass: RateLimiterRedis, limiterOptions: { storeClient: redis }, runtimeLimits: { maxLimit: 1_000_000, maxDurationSeconds: 31_536_000, maxKeyLength: 512, maxLimiterCacheSize: 10_000, }, }; ``` -------------------------------- ### Error Handling: Monitoring and Alerting Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Suggests patterns for monitoring rate limit events and setting up alerts. This involves logging rate limit rejections and tracking their frequency. ```typescript import { RATE_LIMITED } from "graphql-rate-limit-redis-esm"; import { logger } from "./logger"; // Assuming a logger instance // In your GraphQL error handling middleware: const handleGraphQLError = (error: any, context: any) => { if (error.extensions?.code === RATE_LIMITED) { logger.warn({ message: "Rate limit exceeded", userId: context.currentUser?.id, ip: context.request.ip, key: error.extensions.key, limit: error.extensions.limit, points: error.extensions.points, remaining: error.extensions.remaining, resetTime: new Date(error.extensions.resetTime * 1000).toISOString(), }); // Trigger an alert if rate limits are hit too frequently // e.g., using a separate monitoring service } // ... other error handling ... return error; }; ``` -------------------------------- ### Testing: Mock Limiter Patterns Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/DOCUMENT_SUMMARY.txt Illustrates how to mock the rate limiter itself during testing. This is useful for testing components that depend on the rate limiter without actually performing rate limiting checks. ```typescript import { createRateLimitDirective } from "graphql-rate-limit-redis-esm"; // Mock implementation of the directive const mockLimiter = { // Mock methods that your application might call applyRateLimit: jest.fn().mockResolvedValue(true), // Assume it always allows }; // In your test setup, provide this mock where the limiter is expected: // For Apollo Server: // const server = new ApolloServer({ // typeDefs, // resolvers, // schemaDirectives: { // rateLimit: mockLimiter as any, // Cast to any if types don't match exactly // }, // }); describe('ComponentUsingRateLimit', () => { it('should call the rate limiter', async () => { // ... setup component that uses the directive ... // Call the component's method that triggers the rate limit check await component.someAction(); expect(mockLimiter.applyRateLimit).toHaveBeenCalled(); }); }); ``` -------------------------------- ### RateLimiterOptions Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/types.md Specifies the configuration options passed to the rate limiter class constructor, including the backend store client and other limiter-specific settings. ```APIDOC ## RateLimiterOptions ### Description Configuration options passed to the limiter class constructor. ### Properties | Property | Type | Required | Description | |----------|------|----------|-------------| | storeClient | `unknown` | Yes | Backend store client (e.g., Redis client instance) | | [key: string] | `unknown` | No | Additional limiter-specific options | ### Validation - `storeClient` must not be null or undefined - Must be an object (not an array) ### Example with RateLimiterRedis ```typescript import Redis from "ioredis"; const redis = new Redis("redis://localhost:6379"); const limiterOptions: RateLimiterOptions = { storeClient: redis, // Additional options passed to RateLimiterRedis: duration: 60, points: 10, }; ``` ``` -------------------------------- ### trustProxyGuidance Source: https://github.com/lafittemehdy/graphql-rate-limit-redis-esm/blob/master/_autodocs/api-reference/key-generators.md A helper string providing guidance for deployments that need trusted proxy support. It explains that forwarded IP headers are ignored by default and how to enable trust proxy support. ```APIDOC ## trustProxyGuidance ### Description A helper string providing guidance for deployments that need trusted proxy support. It explains that forwarded IP headers are ignored by default and how to enable trust proxy support. ### Value ``` "Forwarded IP headers are ignored by default. Set trustProxy: true in createDefaultKeyGenerator options if your app runs behind a trusted proxy." ``` ### Usage ```typescript import { trustProxyGuidance } from "graphql-rate-limit-redis-esm"; console.log(trustProxyGuidance); // Output the guidance message in logs or documentation const keyGen = createDefaultKeyGenerator({ trustProxy: true, // Only when behind a trusted proxy! }); ``` ```