Performance Middleware

Rate Limiting

Protect your APIs from abuse. Configure limits per route, user, or IP. Simple setup, powerful protection.

Overview

Add rate limiting to any route with one line. Protect against abuse, ensure fair usage, and maintain performance.

Add rate limiting with one line
1app.post('/users')
2  .rateLimit({ requests: 10, window: 60000 }) // 10 requests per minute
3  .handler(() => {
4    return createUser();
5  });

Without rate limiting, your API is vulnerable to abuse, DDoS attacks, and unfair usage. With rate limiting, you control access and protect resources. Traditional rate limiting requires middleware setup, storage configuration, and manual tracking. We handle that automatically.

Without Rate Limiting

  • Vulnerable to abuse and DDoS
  • Unfair resource usage
  • Manual tracking required
  • Complex middleware setup

With MoroJS

  • Automatic abuse protection
  • Fair usage enforcement
  • One-line setup
  • Flexible per-route configuration

Configure limits per route. Different limits for different endpoints.

Different limits for different endpoints
1// Authentication - strict limits
2app.post('/auth/login')
3  .rateLimit({ requests: 5, window: 900000 }) // 5 attempts per 15 minutes
4  .handler(() => authenticateUser());
5
6// Public API - higher limits
7app.get('/api/public')
8  .rateLimit({ requests: 1000, window: 3600000 }) // 1000 per hour
9  .handler(() => getPublicData());

Protection

Protect against abuse, DDoS attacks, and resource exhaustion.

Fair Usage

Ensure fair resource distribution. Different limits for different users.

Simple

One line per route. Automatic tracking. Custom keys supported.

How It Works

MoroJS rate limiting tracks requests based on a key (IP, user ID, or custom) within a time window. When the limit is exceeded, requests are automatically rejected with a 429 status code and helpful headers.

Configuration

Basic Rate Limiting
1import { createApp, z } from '@morojs/moro';
2
3const app = await createApp();
4
5// Rate limiting with chainable API
6app.post('/users')
7  .body(z.object({
8    name: z.string().min(2),
9    email: z.string().email()
10  }))
11  .rateLimit({ requests: 10, window: 60000 }) // 10 requests per minute
12  .handler((req) => {
13    return { success: true, data: createUser(req.body) };
14  });
Different Limits for Different Endpoints
1// Authentication - strict limits
2app.post('/auth/login')
3  .rateLimit({ requests: 5, window: 900000 }) // 5 attempts per 15 minutes
4  .handler(() => authenticateUser());
5
6// Public API - higher limits
7app.get('/api/public')
8  .rateLimit({ requests: 1000, window: 3600000 }) // 1000 per hour
9  .handler(() => getPublicData());
10
11// File uploads - very strict
12app.post('/api/upload')
13  .rateLimit({ requests: 10, window: 3600000 }) // 10 uploads per hour
14  .handler(() => uploadFile());

Advanced Configuration

Apply a limit to the whole app with the middleware form, and let successful traffic go uncounted where only failures should burn the budget.

Application-Wide Limits
1import { createApp, middleware } from '@morojs/moro';
2
3const app = await createApp();
4
5app.use(middleware.rateLimit({
6  requests: 100,        // or max: 100
7  window: 60000,        // or windowMs: 60000
8  message: 'Too many requests',
9  statusCode: 429       // default
10}));
11
12// Only count requests that failed — a login endpoint that
13// throttles brute force without punishing successful sign-ins
14app.post('/auth/login')
15  .rateLimit({
16    requests: 5,
17    window: 900000,
18    skipSuccessfulRequests: true
19  })
20  .handler(() => authenticateUser());
What a Rejected Request Looks Like
1HTTP/1.1 429 Too Many Requests
2Retry-After: 47
3Content-Type: application/json
4
5{
6  "success": false,
7  "error": "Too many requests",
8  "retryAfter": 47
9}

How limits are tracked

  • Requests are keyed by client IP and route (METHOD:path) in an in-memory store
  • The store is per process, so a clustered deployment limits per worker — size limits accordingly or rate limit at your proxy
  • skipSuccessfulRequests counts up front and refunds once the response finishes below 400, so a burst cannot slip past mid-flight
  • There is no keyGenerator, skip, or external store option

Next Steps