I februari 2025 skickade angripare 12 miljoner inloggningsförsök mot ett stort fintech-API på 48 timmar, spridda över 50 000 olika IP-adresser. Ungefär 2 400 konton knäcktes trots att API:t faktiskt hade rate limiting installerat. Problemet var att gränserna räknades per process och per IP, vilket gjorde att ett botnät aldrig triggade några spärrar. Den här guiden visar hur du bygger rate limiting i Node.js som faktiskt håller, med Redis som delat state, sliding window-algoritmer och extra skydd för känsliga endpoints som inloggning och lösenordsåterställning.
Du får ett komplett Express-projekt att bygga vidare på, kod du kan klistra in direkt, och en felsökningslista för de vanligaste problemen som dyker upp när rate limiting möter verklig produktionstrafik.
Vad är rate limiting och varför behöver ditt API det 2026?
Rate limiting begränsar hur många anrop en klient får göra mot ditt API inom ett visst tidsfönster. Utan den spärren kan en enda skript eller ett botnät skicka tiotusentals förfrågningar per minut mot en inloggningsruta, ett sök-API eller ett betalningsflöde, och antingen tömma serverns resurser eller helt enkelt gissa sig fram till giltiga lösenord.
Tekniken är gammal, men sättet den behöver implementeras på har förändrats i takt med att applikationer flyttat från en enda server till flera samtidigt körande instanser i containrar och molntjänster. En gräns som fungerade utmärkt på en monolit för tio år sedan kan vara helt verkningslös idag om den inte är medveten om att trafiken sprids över flera processer. Den här guiden bygger därför skyddet från grunden med det i åtanke, snarare än att bara visa den enklaste möjliga implementationen.
Cloudflare beskriver funktionen rakt av: “Rate Limiting protects against denial-of-service attacks, brute force login attempts, traffic surges, and other types of abuse targeting APIs and applications.” (Cloudflare, produktdokumentation). Det är exakt de tre hoten en Node.js-utvecklare möter dagligen: överbelastning, inloggningsattacker och missbruk av öppna endpoints.
MDN beskriver samma sak från utvecklarperspektiv: “These controls prevent overloading systems and offer a level of protection against brute force and Distributed Denial of Service (DDoS) attacks.” (MDN Web Docs). Poängen är att rate limiting inte bara stoppar attacker, det håller också servern stabil när riktiga användare skickar för många förfrågningar av misstag, till exempel via en trasig frontend-loop.
OWASP API Security Top 10 (2023 års utgåva) lade till en helt ny kategori just för detta: Unrestricted Access to Sensitive Business Flows, skapad specifikt för hot som rate limiting kan mildra. Kategorin API4:2023, Unrestricted Resource Consumption, går ännu längre och pekar ut bristande begränsningar på antal, innehåll och typ av förfrågningar som en direkt sårbarhet, inte bara en prestandafråga (OWASP API Security Top 10, 2023).
Guidelinjen är tydlig även på detaljnivå: gränser bör inte bara räknas per IP-adress, utan även per klient-ID, användargrupp, användarnamn eller andra variabler kopplade till ett specifikt anropsflöde. Traditionellt bot-skydd som bygger på cookies och JavaScript-körning i webbläsaren fungerar dessutom sällan för rena API-anrop, eftersom den trafiken saknar båda delarna. Det gör själva API-lagret till den plats där skyddet måste sitta, inte enbart i en WAF framför webbplatsen.
För nordiska bolag finns det ytterligare ett skäl att ta detta på allvar. NIS2-direktivet ställer krav på riskhantering och incidentrapportering för allt fler sektorer i Sverige och övriga Norden, och ett API som saknar grundläggande skydd mot överbelastning och kontokapning är precis den typen av brist en tillsynsmyndighet eller revisor kommer att fråga efter under en granskning.
Verkliga attacker som kunde stoppats med rätt rate limiting
Fintech-fallet från februari 2025 är ett bra exempel på varför “vi har rate limiting” inte räcker som svar på en säkerhetsrevision. Efterhandsanalysen pekade ut tre konkreta brister: räknarna sparades i processminnet istället för i ett delat lager som Redis, det fanns ingen sammansatt nyckel som kombinerade IP och enhetsfingeravtryck, och systemet saknade en circuit breaker som stänger ner vid fel i rate limit-lagret. Utan de tre bitarna blir gränserna lätta att kringgå för ett distribuerat botnät.
Ett annat exempel som beskrivits i säkerhetsgenomgångar 2025 rör en flygbolagsapplikations bokningsrelaterade API, där avsaknad av rate limiting på ett endpoint för bokningsuppslag gjorde det möjligt att systematiskt läsa ut bokningsinformation genom att iterera igenom bokningsnummer. Ingen inloggning krävdes, och ingen gräns stoppade den automatiserade genomsökningen. Det är precis det scenario OWASP:s kategori API4:2023 varnar för: endpoints utan tak på antalet förfrågningar, oavsett om anropen i sig ser “normala” ut.
Gemensamt för båda fallen är att skadan hade kunnat begränsas kraftigt, inte nödvändigtvis stoppas helt, om API:et hade haft distribuerad rate limiting med sammansatta nycklar och strängare gränser på känsliga flöden som inloggning, lösenordsåterställning och uppslag av personuppgifter.
Det finns också en enklare, mer vardaglig kategori av incidenter som rate limiting löser: den egna trasiga klienten. En frontend med en bugg i en polling-loop, ett internt skript som glömts kvar i en cron-tjänst, eller en integrationspartner vars system börjar spamma på grund av ett eget fel, kan alla knäcka en oskyddad backend lika effektivt som en riktig attack. Skillnaden är att den här typen av incident är betydligt vanligare i vardagen än en riktad attack, vilket gör rate limiting till ett av de mest kostnadseffektiva skydden du kan lägga till, oavsett hotbild.
Förkunskaper: verktyg och versioner du behöver
Du behöver inte vara senior backend-utvecklare för att följa guiden, men grundläggande JavaScript och Express bör du kunna. Har du redan ett eget Express-API kan du hoppa direkt till steg 5 och koppla på Redis-delen, resten av grunden bygger du sannolikt redan. Så här ser verktygslistan ut, med de versioner som var aktuella i augusti 2026:
- Node.js 22 LTS eller senare. Node.js senaste Current-utgåva var 26.8.1, släppt 26 augusti 2026, men för produktion rekommenderas en LTS-gren.
- npm 10.x, som följer med Node.js-installationen.
- Express 4.x som webbramverk.
- express-rate-limit version 8.6.2, den senaste stabila utgåvan i skrivande stund (släppt 4 augusti 2026, utan kända sårbarheter).
- rate-limiter-flexible version 11.2.0, för mer avancerad, Redis-baserad rate limiting.
- Redis 8.10.x som delat lagringslager (patchversionen 8.10.1 släpptes 17 augusti 2026 och rekommenderas för säkerhetsuppdateringarna).
- Docker (valfritt men praktiskt för att köra Redis lokalt).
- En kodredigerare, till exempel VS Code.
Redis 8.x-serien är värd att notera separat: den distribueras återigen som öppen källkod under AGPLv3, vilket gör den enkel att självhosta för nordiska team som vill hålla data on-prem av GDPR-skäl.
Sliding window vs token bucket: vilken algoritm ska du välja?
Innan du skriver kod behöver du välja algoritm, för den avgör hur “rättvist” och hur minneseffektivt din rate limiting blir. De fyra vanligaste modellerna i Node.js-ekosystemet är fixed window, sliding window, token bucket och leaky bucket. De löser samma problem på olika sätt, med olika avvägningar mellan precision och resursförbrukning.
| Algoritm | Hur den fungerar | Fördel | Nackdel | Bäst för |
|---|---|---|---|---|
| Fixed window | Räknar anrop inom fasta tidsblock, t.ex. varje minut | Enkel att implementera, låg minnesanvändning | Kan släppa igenom dubbla gränsen vid fönsterbyte | Enkla interna API:er |
| Sliding window | Väger ihop nuvarande och föregående fönster löpande | Jämnare begränsning, svårare att kringgå | Något mer beräkning per anrop | Publika API:er och inloggning |
| Token bucket | Klienten “spenderar” tokens som fylls på i konstant takt | Tillåter kortvariga trafiktoppar (burst) | Kräver att burst-storlek tunas noggrant | API:er med ojämn men legitim trafik |
| Leaky bucket | Kö som töms i jämn takt oavsett inflöde | Mycket förutsägbar utgående takt | Kan skapa köfördröjning för legitima anrop | Nedströms-skydd av långsamma tjänster |
För de flesta Node.js-API:er är sliding window ett bra default-val, det är vad express-rate-limit använder som standardstrategi. Token bucket via rate-limiter-flexible passar bättre när du vill tillåta legitima trafiktoppar, till exempel när en mobilapp synkroniserar data efter att ha varit offline. I den här guiden bygger vi båda: sliding window för det generella API-skyddet, och en striktare, token bucket-liknande gräns för inloggningsflödet.
Steg 1-2: Skapa projektet och installera beroenden
Börja med ett tomt Node.js-projekt. Öppna terminalen och kör:
mkdir rate-limit-demo && cd rate-limit-demo
npm init -y
npm install express express-rate-limit rate-limiter-flexible ioredis dotenv
npm install --save-dev nodemon autocannon
Skapa sedan en .env-fil för konfiguration, och en mappstruktur som håller isär logik:
touch .env server.js
mkdir middleware
touch middleware/rateLimiters.js
Lägg in grundkonfigurationen i .env:
PORT=3000
REDIS_URL=redis://localhost:6379
NODE_ENV=development
Om du inte redan har Redis installerat, starta det snabbaste sättet via Docker:
docker run -d --name redis-ratelimit -p 6379:6379 redis:8.10
Steg 3-4: Bygg en Express-server med grundläggande rate limiting
Nu bygger vi grunden. express-rate-limit fungerar som vanlig Express-middleware och kräver ingen extern lagring för att komma igång, vilket gör den perfekt som första skyddslager.
// server.js
require('dotenv').config();
const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();
app.set('trust proxy', 1); // krävs bakom load balancer/proxy
app.use(express.json());
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minut
limit: 100, // max 100 anrop per fönster och klient
standardHeaders: true, // skickar RateLimit-* headers
legacyHeaders: false,
message: { error: 'For manga forfragningar, forsok igen om en stund.' }
});
app.use('/api/', apiLimiter);
app.get('/api/status', (req, res) => {
res.json({ status: 'ok', time: new Date().toISOString() });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Servern kor pa port ${PORT}`));
CoreUI sammanfattar principen bra: “Rate limiting protects your Node.js API from abuse by restricting the number of requests a client can make in a time window.” (CoreUI). Notera raden app.set('trust proxy', 1), den är avgörande om appen körs bakom en load balancer, annars räknar rate limiting fel klient-IP och riskerar att blockera alla användare samtidigt.
Starta servern och testa:
node server.js
curl -i http://localhost:3000/api/status
Förväntad output i terminalen ser ut så här, med rate limit-headers synliga:
HTTP/1.1 200 OK
RateLimit-Limit: 100
RateLimit-Remaining: 99
RateLimit-Reset: 58
Content-Type: application/json; charset=utf-8
{"status":"ok","time":"2026-08-28T09:12:41.203Z"}
Steg 5-6: Koppla rate limiting till Redis för distribuerad drift
Standardlagret i express-rate-limit sparar räknare i processminne. Det räcker för en enda instans, men bryter samman så fort du kör flera Node.js-processer bakom en load balancer, eller skalar horisontellt i Kubernetes. Då ser varje instans bara sin egen del av trafiken, och den faktiska gränsen blir din inställda gräns multiplicerad med antalet instanser.
Lösningen är att flytta räknarna till Redis, som alla instanser delar. Redis är ett naturligt val här eftersom det redan är snabbt, in-memory och stödjer atomiska operationer som gör att flera processer kan öka samma räknare samtidigt utan att räkna fel, en så kallad race condition. Skapa middleware/rateLimiters.js:
// middleware/rateLimiters.js
const { RateLimiterRedis } = require('rate-limiter-flexible');
const Redis = require('ioredis');
const redisClient = new Redis(process.env.REDIS_URL, {
enableOfflineQueue: false
});
const apiLimiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: 'rl_api',
points: 100, // 100 anrop
duration: 60, // per 60 sekunder
blockDuration: 30 // blockera i 30 sek vid overtramp
});
module.exports = { apiLimiter, redisClient };
Använd den sedan som middleware i server.js:
const { apiLimiter } = require('./middleware/rateLimiters');
app.use('/api/', async (req, res, next) => {
try {
await apiLimiter.consume(req.ip);
next();
} catch (rejRes) {
res.set('Retry-After', String(Math.ceil(rejRes.msBeforeNext / 1000)));
res.status(429).json({ error: 'For manga forfragningar, forsok igen senare.' });
}
});
rate-limiter-flexible beskrivs av utvecklaren som ett bibliotek som skyddar mot DoS och brute force “at any scale”, vilket i praktiken betyder att det är byggt för just den här Redis-baserade, distribuerade modellen (rate-limiter-flexible på GitHub). Fördelen jämfört med processminne är enkel: oavsett hur många Node.js-instanser du kör delar de samma räknare i Redis, så en klient kan inte kringgå gränsen genom att träffa olika servrar.
Steg 7-8: Skydda inloggning och känsliga endpoints extra hårt
Ett generellt API-tak på 100 anrop per minut är för löst för ett inloggningsendpoint. Där vill du ha en mycket strängare gräns, kombinerad med progressiv fördröjning. OWASP:s vägledning för API4:2023 pekar specifikt ut inloggning, lösenordsåterställning och uppslag av känslig data som flöden som kräver egna, striktare gränser utöver den generella API-begränsningen.
// middleware/rateLimiters.js (tillagg)
const loginLimiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: 'rl_login',
points: 5, // max 5 inloggningsforsok
duration: 15 * 60, // per 15 minuter
blockDuration: 15 * 60
});
module.exports = { apiLimiter, loginLimiter, redisClient };
// server.js
const { loginLimiter } = require('./middleware/rateLimiters');
app.post('/api/login', async (req, res, next) => {
try {
await loginLimiter.consume(req.ip);
next();
} catch (rejRes) {
const secs = Math.ceil(rejRes.msBeforeNext / 1000);
res.set('Retry-After', String(secs));
res.status(429).json({
error: `For manga inloggningsforsok. Forsok igen om ${secs} sekunder.`
});
}
}, (req, res) => {
// din faktiska inloggningslogik har
res.json({ message: 'Inloggning mottagen' });
});
Fem försök per 15 minuter är en rimlig utgångspunkt för ett konsumentinriktat API, men justera efter din riskbild. En bank-app bör ligga lägre, en intern admin-panel bakom VPN kan ligga högre. Kombinera gärna med kontolåsning efter upprepade misslyckanden och tvåfaktorsautentisering som extra skyddslager, inte som ersättning för rate limiting.
Steg 9-10: Sammansatta nycklar och circuit breaker
Det februari 2025-fallet vi nämnde inledningsvis visar exakt varför IP-baserad begränsning ensam inte räcker. Ett botnät med 50 000 IP-adresser kringgår enkelt en per-IP-gräns. Lösningen är sammansatta nycklar som kombinerar flera signaler, till exempel IP-adress plus ett enhets- eller sessionsfingeravtryck, eller kontots användar-ID när användaren redan är inloggad.
function buildCompositeKey(req) {
const ip = req.ip;
const fingerprint = req.headers['x-device-fingerprint'] || 'unknown';
const accountId = req.body?.email || 'anonymous';
return `${ip}:${fingerprint}:${accountId}`;
}
app.post('/api/login', async (req, res, next) => {
const key = buildCompositeKey(req);
try {
await loginLimiter.consume(key);
next();
} catch (rejRes) {
res.status(429).json({ error: 'For manga forsok fran denna kalla.' });
}
}, loginHandler);
Nästa svaga punkt är vad som händer om Redis själv går ner. Många implementationer faller då “öppet”, det vill säga släpper igenom all trafik obegränsat, vilket i praktiken stänger av skyddet precis när det behövs som mest under en attack. En circuit breaker som failar stängt löser det:
redisClient.on('error', (err) => {
console.error('Redis-fel:', err.message);
});
async function safeConsume(limiter, key, res) {
try {
await limiter.consume(key);
return true;
} catch (err) {
if (err instanceof Error) {
// Redis nere eller natverksfel - fail closed pa kansliga endpoints
res.status(503).json({ error: 'Tjansten ar tillfalligt otillganglig.' });
return false;
}
// Vanligt overtramp av gransen
res.status(429).json({ error: 'For manga forfragningar.' });
return false;
}
}
Om ditt API är mindre känsligt kan du istället välja att faila öppet med en varningslogg, men för inloggning, betalningar och personuppgifter är fail-closed det säkrare valet, även om det innebär en kort driftstörning om Redis skulle krascha.
Testa den sammansatta nyckeln separat innan du litar på den i produktion. Ett enkelt sätt är att skicka samma inloggningsförsök från olika simulerade “enheter” (olika värden på x-device-fingerprint) mot samma konto, och verifiera att varje kombination får sin egen kvot, medan upprepade försök med exakt samma IP, fingeravtryck och konto korrekt blockeras efter fem försök. Om alla kombinationer delar samma räknare har nyckeln byggts fel, oftast för att ett fält saknas eller alltid returnerar samma standardvärde.
Steg 11-12: Testa, logga och övervaka rate limiting i produktion
Innan du sätter rate limiting i produktion behöver du verifiera att gränserna faktiskt utlöses. Använd autocannon, som du redan installerade, för att skicka en trafiktopp mot ditt test-endpoint:
npx autocannon -c 20 -d 10 http://localhost:3000/api/status
Med 20 samtidiga anslutningar under 10 sekunder mot en gräns på 100 anrop/minut bör du se en blandning av 200- och 429-svar i outputen:
Stat 2.5% 50% 97.5% 99% Avg
Req/Sec 180 412 498 501 398.4
200 responses: 412
429 responses: 1988
Errors: 0
Ser du enbart 200-svar har din middleware inte kopplats in korrekt, kontrollera ordningen på dina app.use()-anrop. Lägg sedan till strukturerad loggning så att du kan se mönster över tid, inte bara enskilda 429-svar:
function logRateLimitHit(req, key) {
console.log(JSON.stringify({
event: 'rate_limit_exceeded',
path: req.path,
key,
timestamp: new Date().toISOString()
}));
}
Skicka den loggen vidare till ditt SIEM eller din observability-plattform, och sätt upp en varning som triggar om en enskild nyckel genererar ovanligt många 429-svar under kort tid, det är ofta första tecknet på en pågående attack snarare än en enskild trött användare.
Om du redan kör Prometheus och Grafana för övervakning är det värt att exponera rate limit-träffar som en egen metrik, istället för att gräva i textloggar när något går fel. En enkel räknare per endpoint och statuskod ger dig ett dashboard-panel som visar attacktrender över tid:
const client = require('prom-client');
const rateLimitCounter = new client.Counter({
name: 'rate_limit_exceeded_total',
help: 'Antal forfragningar som blockerats av rate limiting',
labelNames: ['route']
});
// anropas i catch-blocket nar en granska overskrids
rateLimitCounter.inc({ route: req.path });
Med den metriken på plats kan du sätta en Grafana-varning som skickar en Slack-notis om antalet 429-svar mot /api/login plötsligt tiodubblas jämfört med föregående timme, ett betydligt snabbare sätt att upptäcka en pågående attack än att vänta på att en användare hör av sig till supporten.
Rate limiting-headers: så tolkar klienten svaret
En del av god API-design är att tala om för klienten hur mycket kvot som återstår, istället för att den ska gissa sig fram genom att räkna 429-svar. Standardiserade headers gör det möjligt för välskrivna klienter, och för egna interna tjänster, att automatiskt backa av innan gränsen ens nås. express-rate-limit stödjer både det nyare, standardiserade RateLimit-*-formatet och de äldre X-RateLimit-*-headrarna för bakåtkompatibilitet.
| Header | Betyder | Exempelvärde |
|---|---|---|
| RateLimit-Limit | Maximalt antal anrop i det aktuella fönstret | 100 |
| RateLimit-Remaining | Antal anrop som återstår i fönstret | 37 |
| RateLimit-Reset | Sekunder tills fönstret återställs | 42 |
| Retry-After | Sekunder klienten bör vänta innan nästa försök (skickas vid 429) | 30 |
| X-RateLimit-Limit | Äldre motsvarighet till RateLimit-Limit, för bakåtkompatibilitet | 100 |
Skicka alltid Retry-After på 429-svar, oavsett vilket bibliotek du använder. Det är den enda headern som direkt talar om för klienten vad den ska göra näst, och många HTTP-klientbibliotek, inklusive moderna fetch-wrappers, respekterar den automatiskt vid återförsök.
Vanliga fallgropar vid rate limiting i Node.js
Även med rätt bibliotek på plats är det lätt att göra misstag som gör skyddet nästan verkningslöst. Här är de fem vanligaste, baserade på hur incidenter faktiskt har uppstått.
- Att bara begränsa per IP-adress. Som fintech-fallet visade kringgås det trivialt med proxynät eller botnät. Kombinera alltid IP med ett annat attribut när det går, till exempel kontots ID eller ett enhetsfingeravtryck.
- Att lagra räknare i processminne i en flerinstans-miljö. Varje instans får sin egen räkning, så den faktiska gränsen blir (din gräns) x (antal instanser). Flytta alltid till Redis eller motsvarande delat lager innan du skalar horisontellt.
- Samma gräns för alla endpoints. Ett publikt statusendpoint och ett inloggningsendpoint har helt olika riskprofiler och bör inte dela samma tak.
- Fel ordning på middleware. Om rate limiting läggs efter tunga operationer som databasfrågor eller filuppladdning hinner skadan redan ske innan gränsen slår till. Placera den tidigt i middleware-kedjan.
- Att glömma
trust proxybakom en load balancer. Utan den inställningen ser Express samma IP (load balancerns) för all trafik, vilket antingen blockerar alla användare samtidigt eller gör IP-baserad begränsning helt verkningslös.
Felsökning: 8 vanliga problem och hur du löser dem
Så här löser du de problem som oftast dyker upp när rate limiting rullas ut i en verklig miljö.
- Legitima användare får 429 direkt, ofta redan vid första anropet. Symptomet syns tydligast i loggarna: samma IP-adress, load balancerns interna IP, dyker upp för alla klienter. Kontrollera att
app.set('trust proxy', 1)är satt om appen körs bakom en load balancer eller CDN, annars räknas all trafik som samma klient och gränsen tar slut på sekunder. - Redis-anslutningen kraschar hela appen istället för att bara blockera rate limiting-funktionen. Lyssna alltid på
redisClient.on('error', ...)och undvikenableOfflineQueue: truei produktion, annars köas förfrågningar i oändlighet vid nätverksfel och minnet växer okontrollerat tills processen kraschar. - Gränsen återställs inte som förväntat, användare blockeras längre eller kortare än konfigurerat. Kontrollera serverns systemklocka och NTP-synkronisering, ett fönster baserat på systemtid driftar om servrarna inte är tidssynkade, vilket är särskilt vanligt i containermiljöer där klockan ibland inte synkas korrekt vid uppstart.
- Olika beteende i dev och produktion gör att buggar smyger sig igenom testningen. Se till att
REDIS_URLpekar mot rätt instans i varje miljö, ett vanligt misstag är att staging av misstag delar Redis-instans med produktion, vilket kan göra att en utvecklares lasttest råkar blockera riktiga kunder. - CORS-preflight (OPTIONS) räknas mot gränsen och äter upp kvoten i onödan. Undanta
OPTIONS-anrop från rate limiting, annars kan en enda POST-förfrågan med anpassade headers förbruka två enheter av kvoten, ett preflight-anrop plus själva POST-anropet, vilket halverar den effektiva gränsen utan att du märker det förrän trafiken ökar. - express-rate-limit delar inte state mellan Node.js-cluster-workers. Standardlagret är per-process. Kör du flera workers via Node.js
cluster-modulen eller PM2 i klusterläge ser varje worker bara sin egen del av trafiken. Byt till en Redis-baserad store (till exempelrate-limit-redis) så fort du kör mer än en worker-process. - RateLimit-headers saknas i svaret trots att koden ser rätt ut. Kontrollera att
standardHeaders: trueär satt i express-rate-limit, och att ingen proxy längre fram, till exempel en CDN eller API-gateway, filtrerar bort anpassade headers innan de når klienten. - Websocket- eller SSE-anslutningar kringgår helt din HTTP-middleware. Rate limiting på Express-nivå gäller bara vanliga HTTP-request/response-anrop, eftersom en websocket-uppkoppling bara passerar middleware-kedjan en gång vid handskakningen. Långlivade anslutningar behöver egen logik, till exempel en gräns på antal meddelanden per anslutning och tidsenhet, implementerad direkt i din meddelandehanterare.
- Testmiljön visar aldrig 429, även vid extrem belastning. Kontrollera att lasttestverktyget inte återanvänder samma connection pool på ett sätt som gör att alla anrop tolkas som en enda uthållig TCP-session, och att din reverse proxy inte redan begränsar eller köar trafiken innan den når Node.js-processen.
Avancerade tips för rate limiting i skala
När din trafik växer räcker Express-middleware sällan hela vägen. Här är några tekniker värda att växa in i:
- Flytta begränsningen till API-gatewayen. Verktyg som Kong, NGINX eller en molnleverantörs API-gateway kan stoppa missbruk innan trafiken ens når din Node.js-process, vilket sparar beräkningskraft under en attack. Det ger dig också ett enda ställe att justera gränser över flera bakomliggande tjänster, istället för att uppdatera samma konfiguration i varje mikrotjänst för sig.
- Kostnadsbaserad begränsning för GraphQL. En enda GraphQL-fråga kan vara billig eller extremt dyr beroende på hur djupt den nästlar sig och hur många relaterade fält den drar med sig. Räkna frågekostnad snarare än antal anrop för att undvika att en “artig” klient med en tung, djupt nästlad fråga slår ut databasen lika effektivt som ett rent missbruksförsök.
- Distribuerad rate limiting över Redis Cluster. Vid mycket hög trafik bör Redis själv vara kluster- och replikerat, annars blir det gemensamma lagret din nya flaskhals eller en enda felpunkt. Planera för det innan trafiken tvingar fram en akut migrering mitt i en incident.
- Adaptiv begränsning baserad på anomalidetektering. API-trafik saknar ofta cookies och JavaScript-exekvering, så traditionellt bot-skydd i webbläsaren fungerar inte. Maskininlärningsbaserad avvikelsedetektering som automatiskt sänker gränser för misstänkta mönster, till exempel plötsliga toppar från ett enda geografiskt område eller ovanliga user-agent-strängar, är ett naturligt nästa steg efter statiska regler.
- Default-deny för nya endpoints. Bygg in en policy där varje nytt endpoint automatiskt ärver en försiktig, begränsande profil vid driftsättning, så att ingen ny route av misstag hamnar helt oskyddad i produktion. Låt utvecklare aktivt höja gränsen när de vet att endpointen är säker att öppna upp, istället för att behöva komma ihåg att lägga till skydd i efterhand.
- Skilj på server-till-server-trafik och trafik från slutanvändare. Interna tjänster som anropar varandra med API-nycklar kan ofta ha högre, mer förutsägbara gränser än öppna publika endpoints, eftersom du redan vet vilka klienter som är legitima. Blanda inte ihop de två kategorierna under samma regelverk, då blir gränserna antingen för snäva för interna flöden eller för generösa för publik trafik.
Rate limiting för olika typer av API-klienter
Inte alla klienter bör behandlas lika, även om de träffar samma endpoint. En mobilapp som synkroniserar data i bakgrunden beter sig helt annorlunda än en webbläsare som gör enstaka anrop när en användare klickar runt, och båda skiljer sig radikalt från en server-till-server-integration eller en webhook-mottagare.
För mobilappar är token bucket ofta ett bättre val än strikt sliding window, eftersom appen kan behöva skicka en burst av anrop efter att ha varit offline en stund, till exempel för att synka flera timmars aktivitet på en gång. Ett för strikt fönster straffar då helt legitim användning bara för att den råkar komma i en klump.
För ensidiga webbappar (SPA) som pratar direkt med ditt API från webbläsaren räcker vanligtvis en enklare, generös sliding window-gräns per session eller inloggat konto, kombinerat med striktare gränser specifikt på formulär som inloggning, registrering och lösenordsåterställning.
Server-till-server-integrationer, till exempel en partner som hämtar data via en API-nyckel, bör i regel ha egna, högre gränser knutna till just den nyckeln snarare än till en IP-adress, eftersom trafik ofta kommer från delade utgående IP-adresser i molnmiljöer. Håll koll på varje API-nyckels förbrukning separat så att en missbrukad eller läckt nyckel går att strypa utan att påverka alla andra partners.
Webhooks som du själv skickar ut till kunders system, eller tar emot från tredjepartstjänster, förtjänar ett eget spår helt separat från ditt vanliga API, med egen kö och egen begränsning. På så sätt riskerar en trög eller överbelastad mottagare inte att dra ner prestandan för resten av API-trafiken.
Jämförelse: de vanligaste rate limiting-biblioteken för Node.js
Vilket bibliotek du väljer beror på hur mycket kontroll du behöver och hur din infrastruktur ser ut. Så här skiljer sig de tre vi nämnt i guiden åt:
| Bibliotek | Version (aug 2026) | Standardlager | Redis-stöd | Bäst för |
|---|---|---|---|---|
| express-rate-limit | 8.6.2 | Processminne | Ja, via tilläggspaket | Snabb start, enkla API:er |
| rate-limiter-flexible | 11.2.0 | Processminne eller extern store | Ja, inbyggt (RateLimiterRedis) | Distribuerade system, känsliga flöden |
| express-slow-down | Aktuell version i express-rate-limit-familjen | Processminne | Ja, via tilläggspaket | Progressiv fördröjning istället för hård blockering |
En vanlig kombination i produktion är express-rate-limit för det generella, lätta API-skyddet, och rate-limiter-flexible med Redis-backend för de känsliga flödena som inloggning och betalning, precis som vi byggt upp i stegen ovan. Behöver du bara ett enda bibliotek och vill hålla beroendena få räcker rate-limiter-flexible på egen hand gott och väl, eftersom det klarar både enkla och avancerade scenarier. express-rate-limit vinner istället på enkelhet och en mindre inlärningskurva om du bara ska sätta upp ett generellt skydd på en helg.
Det kompletta projektet
Så här ser hela server.js ut när alla steg är sammanfogade till ett fungerande projekt:
// server.js - komplett exempel
require('dotenv').config();
const express = require('express');
const rateLimit = require('express-rate-limit');
const { apiLimiter, loginLimiter, redisClient } = require('./middleware/rateLimiters');
const app = express();
app.set('trust proxy', 1);
app.use(express.json());
redisClient.on('error', (err) => console.error('Redis-fel:', err.message));
// Generellt API-skydd (sliding window via express-rate-limit)
app.use('/api/', rateLimit({
windowMs: 60 * 1000,
limit: 100,
standardHeaders: true,
legacyHeaders: false
}));
// Strangare, Redis-baserat skydd for inloggning
app.post('/api/login', async (req, res, next) => {
const key = `${req.ip}:${req.body?.email || 'anonymous'}`;
try {
await loginLimiter.consume(key);
next();
} catch (rejRes) {
const secs = Math.ceil((rejRes.msBeforeNext || 1000) / 1000);
res.set('Retry-After', String(secs));
res.status(429).json({ error: `For manga forsok. Forsok igen om ${secs} sekunder.` });
}
}, (req, res) => {
res.json({ message: 'Inloggning mottagen' });
});
app.get('/api/status', (req, res) => {
res.json({ status: 'ok', time: new Date().toISOString() });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Servern kor pa port ${PORT}`));
Med den här grunden på plats har du sliding window-begränsning på det generella API:et, en striktare, sammansatt gräns på inloggning, delat state via Redis så att den fungerar oavsett hur många instanser du kör, och headers som talar om för klienten hur mycket kvot som återstår. Härifrån är nästa naturliga steg att lägga till composite-key-logiken och circuit breaker-mönstret från stegen ovan direkt i middleware-filen, samt koppla på Prometheus-metriken om du redan har ett observability-stack på plats. Projektet är litet nog att förstå i sin helhet på några minuter, men täcker samtidigt de byggstenar som skalar upp till en produktionsmiljö med flera instanser och verklig attacktrafik.
Vanliga frågor om rate limiting i Node.js
Räcker express-rate-limit utan Redis för ett litet API?
Ja, om du kör en enda serverinstans utan horisontell skalning. Så fort du lägger till fler instanser, oavsett om det sker via Kubernetes, PM2-kluster eller flera containrar bakom en load balancer, behöver du ett delat lager som Redis för att gränserna ska gälla korrekt över alla instanser.
Vilken HTTP-statuskod ska ett API returnera vid överskriden gräns?
Standarden är 429 Too Many Requests, tillsammans med en Retry-After-header som talar om hur många sekunder klienten bör vänta innan nästa försök. Det gör det möjligt för välskrivna klienter att backa av automatiskt istället för att fortsätta trycka på.
Ska rate limiting ersätta captcha och tvåfaktorsautentisering?
Nej. Rate limiting är ett lager bland flera. Kombinera det gärna med captcha efter ett par misslyckade försök och med tvåfaktorsautentisering för känsliga konton. De tre täcker olika delar av hotbilden: volym, automatisering respektive kontokapning.
Hur sätter jag rätt gräns utan att veta exakt trafikmönster i förväg?
Börja generöst, logga hur ofta gränsen faktiskt träffas under en till två veckors verklig trafik, och skruva sedan ner successivt. Det är säkrare att börja för löst och strama åt baserat på data än att gissa en hård gräns som blockerar riktiga användare från dag ett.
Fungerar samma upplägg för GraphQL-API:er?
Delvis. Ett enkelt anropstak fungerar som ett första lager även för GraphQL, men eftersom en enskild fråga kan variera enormt i kostnad bör du komplettera med kostnadsbaserad begränsning som räknar frågedjup och fältkomplexitet, inte bara antal HTTP-anrop.
Hur testar jag rate limiting utan att skada produktionsmiljön?
Kör lasttester som autocannon mot en stagingmiljö med samma Redis-konfiguration som produktion, aldrig direkt mot skarp drift. Verifiera att du ser både 200- och 429-svar i utdata, och att gränsen återställs korrekt efter tidsfönstrets slut.
Vad händer om Redis går ner mitt i en attack?
Det beror på hur du konfigurerat felhanteringen. Med fail-closed-strategin som visas i guiden nekas nya förfrågningar till känsliga endpoints tills Redis är tillbaka, vilket ger kortvarig otillgänglighet men behåller skyddet. Med fail-open riskerar du att stå helt oskyddad precis när en attack pågår.
Bör jag begränsa per IP eller per användarkonto?
Helst båda, som en sammansatt nyckel. Ren IP-begränsning kringgås av botnät och proxynät, medan ren kontobegränsning inte gör något för oautentiserade endpoints, till exempel själva inloggningsformuläret innan kontot är känt. Kombinationen ger bättre täckning mot fler typer av missbruk, och gör det svårare för en angripare att hitta en enda svag punkt att slå igenom.
Kostar det prestanda att lägga rate limiting mot Redis på varje anrop?
Kostnaden är normalt försumbar. Ett enskilt Redis-anrop tar typiskt under en millisekund inom samma nätverkszon, och både express-rate-limit och rate-limiter-flexible är byggda för att göra en enda, effektiv operation per kontroll. Den verkliga kostnaden uppstår om Redis-instansen ligger i en annan region än din API-server, vilket lägger till nätverkslatens för varje anrop. Håll rate limit-lagret i samma region som din applikation.




