Skip to main content
Version: 3.x

Prevent excessive usage with rate limits

This guide explores the implementation of rate limiting techniques to safeguard your applications from excessive use.

Rate Limiter Service​

The Rate Limiter service provides a robust solution for managing and preventing excessive usage by imposing limits on the number of requests that users can make within a specified period. This service utilizes Redis for storage and supports integration into both HTTP contexts and GraphQL operations.

Features​

  • Redis-backed Storage: Utilizes Redis to efficiently track request counts and support high concurrency.
  • Flexible Rate Limits: Configure max requests and duration per rate limit namespace.
  • HTTP and GraphQL Support: Easily integrate rate limiting into Remix actions/loaders and GraphQL resolvers.
  • Automated Response Handling: Automatically sends HTTP 429 Too Many Requests responses when a limit is exceeded.

Service Configuration​

The rate limiter requires a Redis configuration to function. Upon instantiation, it subscribes to a Redis instance and sets up namespaces for different rate limits.

To configure the service, you can add the following to your front-commerce.config.ts:

front-commerce.config.ts
import { defineConfig } from "@front-commerce/core/config";

export default defineConfig({
// ...other configuration options
rateLimiter: {
// feel free to extract this configuration in a separate file to ease maintenance
redis: {
host: process.env.FRONT_COMMERCE_CLOUD_REDIS_SESSIONS_HOST,
port: process.env.FRONT_COMMERCE_CLOUD_REDIS_SESSIONS_PORT || 6379,
db: process.env.FRONT_COMMERCE_CLOUD_REDIS_SESSIONS_DB || 2,
},
},
});
tip

We've suggested a configuration which shares the same Redis DB than sessions. While it's not required, we think it makes sense because both share the same kind of responsibilities.

Implementing Rate Limits​

The service provides two main methods for limiting rates:

  • limitHTTPResource(namespace, requestId, options): Limits rates for HTTP requests by namespace and request ID.
  • limitGraphQLResource(fieldPath, requestId, options): Limits rates for GraphQL queries based on the resolver field path.

There is also an additional exported method in the graphql exports:

  • limitRateByGraphQLResolver(options, resolver): Wraps a GraphQL resolver with rate limiting.

Remix Loader​

In the Remix framework, the rate limiter can be employed within a loader to control request frequency based on URL path and user IP.

import { FrontCommerceApp } from "@front-commerce/remix";
import type { LoaderFunctionArgs } from "@remix-run/node";

export const loader = async ({ context, request }: LoaderFunctionArgs) => {
const app = new FrontCommerceApp(context.frontCommerce);

await app.services.RateLimiter.limitHTTPResource(
new URL(request.url).pathname,
app.user.clientIp,
{
max: 5,
duration: "1m",
}
);

// Perform loader-specific logic here
};

Remix Action​

Similarly, for actions in Remix, the rate limiter ensures that excessive requests are controlled at the action level, again based on URL path and user IP.

import { FrontCommerceApp } from "@front-commerce/remix";
import type { ActionFunctionArgs } from "@remix-run/node";

export const action = async ({ context, request }: ActionFunctionArgs) => {
const app = new FrontCommerceApp(context.frontCommerce);

await app.services.RateLimiter.limitHTTPResource(
new URL(request.url).pathname,
app.user.clientIp,
{
max: 5,
duration: "1m",
}
);

// Perform action-specific logic here
};

GraphQL Resolver​

For GraphQL resolvers, the rate limiter can be directly applied to individual queries or mutations to manage resource consumption effectively.

import { createGraphQLRuntime } from "@front-commerce/core/graphql";

export default createGraphQLRuntime({
resolvers: {
Query: {
helloWorld: async (parent, args, { user, services }) => {
await services.RateLimiter.limitGraphQLResource(
"Query:helloWorld",
user.clientIp,
{
max: 5,
duration: "1m",
}
);

// Perform resolver logic here
},
},
},
});

Or you can use the limitRateByGraphQLResolver method to wrap the resolver with a rate limit:

import {
createGraphQLRuntime,
limitRateByGraphQLResolver,
} from "@front-commerce/core/graphql";

export default createGraphQLRuntime({
resolvers: {
Query: {
helloWorld: limitRateByGraphQLResolver(
{
max: 5,
duration: "1m",
},
async (parent, args, context, info) => {
// Perform resolver logic here
}
),
},
},
});
info

This method will automatically apply a namespace based on the resolver path, for example Query:helloWorld.

Additional Tips​

  • Handling Overlimits: The service automatically handles cases where the limit is exceeded by sending a 429 status code, but this can be customized further if different handling is required.
  • Monitoring and Logs: Integration with winston for logging allows tracking down issues and misuse effectively.
  • Custom namespace and requestId: The namespace and requestId parameters can be customized to suit your application's requirements.