Protege tu API: Por qué necesitas Rate Limiting (y cómo implementarlo)

Ayer estuve conversando con un amigo sobre la importancia de la seguridad al crear aplicaciones. Estábamos hablando de una funcionalidad clásica: un formulario de suscripción a un newsletter.
Ya conoces el proceso. Creas un endpoint, por ejemplo /api/subscribe, y agregas las validaciones habituales:
- ¿El campo de email está vacío?
- ¿Tiene formato de email real (regex)?
- ¿Esta persona ya está suscrita?
"Con eso es suficiente, ¿verdad?", me preguntó.
"Bueno", le dije, "es suficiente para mantener tu base de datos limpia, pero no para mantener tu servidor con vida".
El problema no son solo los datos incorrectos: es el volumen. Sin protección, un script simple de un bot podría pegarle a tu endpoint 10.000 veces en un minuto. Incluso si rechazas los emails inválidos, tu base de datos sigue trabajando duro para comprobar si existen. Tu servidor sigue procesando JSON. Y tú estás pagando por esos ciclos de CPU.
¿Qué problema nos resuelve esto como desarrolladores? El Rate Limiting garantiza que ningún usuario (o bot) pueda degradar la experiencia de los demás ni dejarnos en bancarrota con la factura del servidor.
El concepto
Piensa en el Rate Limiting como el portero de una discoteca. Al portero no le importa si tu documento de identidad es válido (de eso se encarga la validación de datos); lo que le importa es cuántas personas están intentando entrar al mismo tiempo. Su trabajo es mantener el flujo bajo control.
El enfoque artesanal (y por qué falla)
Supongamos que queremos limitar a los usuarios a 5 peticiones cada 60 segundos.
Tu primer instinto podría ser: "No necesito una librería sofisticada. Solo voy a usar un Map de JavaScript en memoria".
En una ruta de API de Next.js, se vería más o menos así:
const rateLimitMap = new Map();
export async function POST(request: Request) {
const ip = request.headers.get("x-forwarded-for") || "127.0.0.1";
const now = Date.now();
const windowSize = 60 * 1000; // 60 segundos
// Obtener las marcas de tiempo recientes del usuario
const userHistory = rateLimitMap.get(ip) || [];
// Filtrar las marcas de tiempo con más de 60 segundos
const recentRequests = userHistory.filter(timestamp => now - timestamp < windowSize);
if (recentRequests.length >= 5) {
return Response.json(
{ error: "Too many requests, slow down!" },
{ status: 429 }
);
}
// Registrar esta petición
recentRequests.push(now);
rateLimitMap.set(ip, recentRequests);
// ... continuar con la lógica de suscripción ...
return Response.json({ message: "Subscribed!" });
}El problema de almacenar en memoria
Este código funciona a la perfección... en tu máquina local. Pero en el momento en que lo despliegas en un entorno serverless como Vercel o AWS Lambda, deja de funcionar.
- Falta de estado (Statelessness): Las funciones serverless se inician y mueren rápidamente. La próxima vez que se ejecute la función, tu
rateLimitMapestará completamente vacío. - Múltiples instancias aisladas: Si tu aplicación escala para soportar más tráfico, podrías tener 50 instancias distintas corriendo al mismo tiempo. La instancia A no tiene idea de que el usuario acaba de enviar 500 peticiones a la instancia B.
Entonces, ¿cómo compartimos esta "memoria" entre miles de instancias de servidor que viven pocos segundos?
La solución: Redis (Upstash)
Para resolver esto, necesitamos una memoria externa: una base de datos lo bastante rápida como para responder en milisegundos a la pregunta "¿esta IP ya hizo 5 peticiones?". Redis es la herramienta estándar para este trabajo.
Para aplicaciones serverless, me encanta usar Upstash. Es Redis sobre HTTP, por lo que no necesitas gestionar conexiones persistentes.
Configuración
Vamos a limpiar nuestro código inicial. Primero, instala las librerías necesarias:
npm install @upstash/ratelimit @upstash/redisCopia tu UPSTASH_REDIS_REST_URL y tu UPSTASH_REDIS_REST_TOKEN desde la Consola de Upstash y agrégalos a tu archivo .env.
Implementación
Ahora, proteger nuestro endpoint /api/subscribe es sumamente simple. No necesitamos programar la lógica de ventanas deslizantes (sliding windows) ni marcas de tiempo; la librería se encarga de todo.
// app/api/subscribe/route.ts
import { NextResponse } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import { headers } from "next/headers";
// Crear una instancia unificada del limitador
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, "60 s"), // 5 peticiones por cada 60s
analytics: true,
});
export async function POST(request: Request) {
// Usar la IP como clave (o el ID de usuario si inició sesión)
const ip = headers().get("x-forwarded-for") || "127.0.0.1";
// Consultar a Redis: "¿puede pasar esta petición?"
const { success } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: "Too many requests. Please try again later." },
{ status: 429 }
);
}
// ✅ Seguro para procesar la suscripción
const body = await request.json();
// Ejemplo de lógica de suscripción
console.log(`Subscribing email: ${body.email}`);
return NextResponse.json({ message: "Successfully subscribed!" });
}Hasta aquí configuramos el limitador y lo aplicamos a una ruta. Esto traslada el estado a Upstash, de modo que no importa si tu API corre en un solo servidor o en mil funciones Lambda repartidas por todo el mundo.
Resumen
El Rate Limiting no es simplemente una característica opcional: es una práctica fundamental de seguridad.
- Aprendimos que la validación funcional (como un regex) no es suficiente para proteger nuestros recursos.
- Vimos por qué las soluciones en memoria fallan en arquitecturas serverless modernas.
- Construimos un rate limiter distribuido y robusto utilizando Redis.
Reto
Ahora que tienes esto funcionando, intenta extenderlo:
- ¿Puedes definir límites diferentes para usuarios gratuitos y usuarios premium? (
user.plan === 'pro') - Intenta bloquear IPs específicas de forma permanente si superan un umbral de infracciones.
¡Happy coding!
Foto por Rohit Tandon en Unsplash
Explora más guías prácticas y arquitecturas reales en la serie Cloud.
Artículos Relacionados
Pasar a ciberseguridad desde el rol de desarrollador de software
Una explicación sencilla de los acrónimos (SIEM, IDP, PII, SPII) y conceptos de ciberseguridad que todo desarrollador debe conocer.
Por qué me estoy cambiando de npm a pnpm
Los recientes ataques a la cadena de suministro son una llamada de atención. Aquí te cuento por qué me cambio a pnpm en todos mis proyectos y cómo puedes migrar tu flujo de trabajo de forma segura.
Prompt Injection en aplicaciones agénticas: 4 capas de defensa
Aprende a proteger tus agentes de IA usando una estrategia de defensa en profundidad con estas 4 capas de seguridad esenciales.
Compartir este artículo
Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.
Software real. Lecciones reales.
Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.
Únete a 13,800+ desarrolladores y lectores.
Cero spam. Cancela tu suscripción en cualquier momento.