Implementing Rate Limiting in Fastify with Redis
Introduction
Excessive requests can degrade API performance or lead to denial-of-service attacks. This guide shows how to integrate Redis-backed rate limiting in Fastify to ensure fair usage and protect your services.
Prerequisites
- Node.js >=16
- Redis server running locally or remotely
- Basic Fastify project setup
Step 1: Initialize Project and Install Dependencies
mkdir fastify-rate-limit
cd fastify-rate-limit
npm init -y
npm install fastify fastify-rate-limit ioredis
- fastify: Web framework
- fastify-rate-limit: Plugin for rate limiting
- ioredis: Redis client
Step 2: Configure Redis Connection
Create src/redis.ts
:
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL || "redis://127.0.0.1:6379");
export default redis;
Step 3: Set Up Fastify Server with Rate Limiting
Create src/server.ts
:
import Fastify from "fastify";
import rateLimit from "fastify-rate-limit";
import redis from "./redis";
async function buildServer() {
const app = Fastify({ logger: true });
app.register(rateLimit, {
global: false, // disable global by default
redis: redis,
max: 100, // max requests
timeWindow: "1 minute",
});
app.get(
"/public",
{
config: { rateLimit: { max: 30, timeWindow: "1 minute" } },
},
async (request, reply) => {
return { hello: "world" };
}
);
app.get(
"/auth",
{
config: { rateLimit: { max: 10, timeWindow: "1 minute" } },
},
async (request, reply) => {
return { secure: true };
}
);
return app;
}
if (require.main === module) {
buildServer().then(app => app.listen({ port: 3000 }));
}
export default buildServer;
Step 4: Customize Rate Limits per Route
- /public: 30 requests/minute
- /auth: 10 requests/minute
- Other routes inherit global 100 requests/minute limit
To disable rate limiting on a route, set config.rateLimit = false
.
Step 5: Test Rate Limiting
Use curl
to test limits:
# Exceed public route limit
for i in {1..35}; do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/public; done
HTTP 200 for first 30, then HTTP 429 Too Many Requests.
Step 6: Handling 429 Errors Gracefully
Fastify automatically sets Retry-After
header. You can customize error response:
import fp from "fastify-plugin";
export default fp(async app => {
app.setErrorHandler((error, request, reply) => {
if (error.statusCode === 429) {
reply
.status(429)
.send({ error: "Rate limit exceeded, please try again later." });
} else {
reply.send(error);
}
});
});
Register plugin before routes.
Summary
Integrating fastify-rate-limit with Redis safeguards your API from abuse while allowing dynamic, per-route limits. Customize responses to improve client experience.