@gagandeep023/api-gateway 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/GUIDE.md +422 -0
  2. package/package.json +3 -2
package/GUIDE.md ADDED
@@ -0,0 +1,422 @@
1
+ # @gagandeep023/api-gateway - Complete Guide
2
+
3
+ A type-safe Express API gateway with IP-based rate limiting, real-time analytics, device authentication, and a React dashboard. No database required.
4
+
5
+ - **npm**: `npm install @gagandeep023/api-gateway`
6
+ - **GitHub**: [github.com/Gagandeep023/api-gateway](https://github.com/Gagandeep023/api-gateway)
7
+ - **npm page**: [npmjs.com/package/@gagandeep023/api-gateway](https://www.npmjs.com/package/@gagandeep023/api-gateway)
8
+
9
+ ## Table of Contents
10
+
11
+ - [Why This Package](#why-this-package)
12
+ - [Setup Guide](#setup-guide)
13
+ - [Installation](#installation)
14
+ - [Backend Setup](#backend-setup)
15
+ - [Frontend Dashboard](#frontend-dashboard)
16
+ - [Custom Configuration](#custom-configuration)
17
+ - [Device Authentication (TOTP)](#device-authentication-totp)
18
+ - [Architecture](#architecture)
19
+ - [Rate Limiting Algorithms](#rate-limiting-algorithms)
20
+ - [Technical Challenges](#technical-challenges)
21
+ - [Learnings](#learnings)
22
+ - [API Reference](#api-reference)
23
+ - [Theming](#theming)
24
+ - [TypeScript Support](#typescript-support)
25
+ - [Contributing](#contributing)
26
+
27
+ ## Why This Package
28
+
29
+ Most rate limiting solutions require Redis, Memcached, or some external store. That is the right call for horizontally scaled production systems. But many Express apps run as a single process, where external dependencies add operational complexity without proportional benefit.
30
+
31
+ This package gives you:
32
+
33
+ - **Three rate limiting algorithms** (Token Bucket, Sliding Window, Fixed Window) with configurable tiers, so you can pick the right algorithm for each use case instead of settling for one.
34
+ - **Zero external dependencies** beyond Express. Rate limit state, analytics, and device registry all run in-memory. No Redis, no database, no cache layer to manage.
35
+ - **A real-time dashboard** out of the box. SSE-powered analytics with request charts, error rates, and API key management. Drop in a React component and you have a monitoring panel.
36
+ - **Device-level TOTP authentication** for browser-based access control without manual API key entry.
37
+ - **Full TypeScript support** with exported types, subpath exports, and strict type checking.
38
+
39
+ **Use this if:**
40
+ - You run a single-instance Express app and want rate limiting without Redis
41
+ - You want a drop-in monitoring dashboard for your API
42
+ - You are learning system design and want to see real implementations of rate limiting algorithms
43
+ - You need configurable per-tier rate limits with different algorithms per tier
44
+ - You want device-based auth without a full OAuth setup
45
+
46
+ **Do not use this if:**
47
+ - You run multiple server instances behind a load balancer (rate limit state is per-process)
48
+ - You need persistent analytics that survive server restarts
49
+ - You need sub-millisecond rate limiting at massive scale (use Redis + Lua scripts)
50
+
51
+ ## Setup Guide
52
+
53
+ ### Installation
54
+
55
+ ```bash
56
+ npm install @gagandeep023/api-gateway
57
+ ```
58
+
59
+ **Peer dependencies** (install the ones you need):
60
+
61
+ ```bash
62
+ # Required for backend
63
+ npm install express
64
+
65
+ # Required for frontend dashboard
66
+ npm install react react-dom recharts
67
+ ```
68
+
69
+ **TypeScript requirement**: Your `tsconfig.json` must use `module: "Node16"` or `"NodeNext"` for subpath exports to resolve correctly.
70
+
71
+ ```json
72
+ {
73
+ "compilerOptions": {
74
+ "module": "Node16",
75
+ "moduleResolution": "Node16"
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### Backend Setup
81
+
82
+ Minimal setup with default configuration:
83
+
84
+ ```typescript
85
+ import express from 'express';
86
+ import { createGatewayMiddleware, createGatewayRoutes } from '@gagandeep023/api-gateway/backend';
87
+
88
+ const app = express();
89
+ app.use(express.json());
90
+
91
+ // Create gateway with defaults (free tier: 100 req/min token bucket)
92
+ const gateway = createGatewayMiddleware();
93
+
94
+ // Mount management routes FIRST (bypasses rate limiting)
95
+ app.use('/api/gateway', createGatewayRoutes({
96
+ rateLimiterService: gateway.rateLimiterService,
97
+ analyticsService: gateway.analyticsService,
98
+ config: gateway.config,
99
+ }));
100
+
101
+ // Apply rate limiting to all /api routes
102
+ app.use('/api', gateway.middleware);
103
+
104
+ // Your routes go after the middleware
105
+ app.get('/api/hello', (req, res) => {
106
+ res.json({ message: 'Hello, world!' });
107
+ });
108
+
109
+ app.listen(3001, () => {
110
+ console.log('Server running on port 3001');
111
+ });
112
+ ```
113
+
114
+ The management routes (`/api/gateway/*`) are mounted before the middleware intentionally. If the rate limiter blocked the analytics endpoint, the dashboard would become unusable during a rate limit storm.
115
+
116
+ ### Frontend Dashboard
117
+
118
+ ```tsx
119
+ import { GatewayDashboard } from '@gagandeep023/api-gateway/frontend';
120
+ import '@gagandeep023/api-gateway/frontend/styles.css';
121
+
122
+ function App() {
123
+ return (
124
+ <GatewayDashboard
125
+ apiBaseUrl="http://localhost:3001/api"
126
+ apiKey="optional-api-key"
127
+ />
128
+ );
129
+ }
130
+ ```
131
+
132
+ The dashboard connects via SSE for live updates every 5 seconds. It shows:
133
+ - Requests per minute (line chart, last 20 data points)
134
+ - Top endpoints (bar chart)
135
+ - Stats grid: total requests, RPM, error rate, response time, rate limit hits, active IPs, key sessions
136
+ - Recent requests table (paginated, 20 per page)
137
+ - API key management: create, revoke, copy keys
138
+ - Gateway configuration display
139
+
140
+ ### Custom Configuration
141
+
142
+ ```typescript
143
+ const gateway = createGatewayMiddleware({
144
+ rateLimits: {
145
+ tiers: {
146
+ // Token Bucket: smooth, burst-friendly
147
+ free: {
148
+ algorithm: 'tokenBucket',
149
+ maxRequests: 100,
150
+ windowMs: 60000,
151
+ refillRate: 10,
152
+ },
153
+ // Sliding Window: accurate, no boundary exploits
154
+ pro: {
155
+ algorithm: 'slidingWindow',
156
+ maxRequests: 1000,
157
+ windowMs: 60000,
158
+ },
159
+ // No rate limiting
160
+ unlimited: {
161
+ algorithm: 'none',
162
+ },
163
+ },
164
+ defaultTier: 'free',
165
+ globalLimit: {
166
+ maxRequests: 10000,
167
+ windowMs: 60000,
168
+ },
169
+ },
170
+ ipRules: {
171
+ allowlist: [],
172
+ blocklist: ['10.0.0.1', '192.168.1.100'],
173
+ mode: 'blocklist', // or 'allowlist'
174
+ },
175
+ apiKeys: {
176
+ keys: [
177
+ {
178
+ id: 'key_001',
179
+ key: 'gw_live_your_secret_key',
180
+ name: 'Production App',
181
+ tier: 'pro',
182
+ createdAt: new Date().toISOString(),
183
+ active: true,
184
+ },
185
+ ],
186
+ },
187
+ });
188
+ ```
189
+
190
+ ### Device Authentication (TOTP)
191
+
192
+ Enable browser-level authentication without manual API key entry:
193
+
194
+ ```typescript
195
+ import { createGatewayMiddleware, createGatewayRoutes, createDeviceAuthRoutes } from '@gagandeep023/api-gateway/backend';
196
+
197
+ // Mount device auth routes
198
+ app.use('/api/auth', createDeviceAuthRoutes({
199
+ rateLimiterService: gateway.rateLimiterService,
200
+ }));
201
+ ```
202
+
203
+ **How it works:**
204
+
205
+ 1. Browser generates a UUID (`browserId`) and sends `POST /api/auth/register`
206
+ 2. Server stores the device and returns a shared secret
207
+ 3. Browser computes TOTP codes using HMAC-SHA256 with 1-hour windows
208
+ 4. Requests include `X-API-Key: totp_<browserId>_<code>`
209
+ 5. Server validates the code against current and previous window (clock skew tolerance)
210
+
211
+ **Security features:**
212
+ - Timing-safe comparison (crypto.timingSafeEqual) prevents timing attacks
213
+ - Registration rate limiting: 10 per minute per IP, max 30 per IP total
214
+ - Automatic device expiration after 1 week
215
+ - Debounced file persistence (2-second batching)
216
+
217
+ ## Architecture
218
+
219
+ ### Middleware Pipeline
220
+
221
+ Every request passes through four stages sequentially. Each stage can short-circuit with an HTTP error.
222
+
223
+ ```
224
+ Request --> [Logger] --> [API Key Auth] --> [IP Filter] --> [Rate Limiter] --> Route Handler
225
+ | | | |
226
+ | 401 403 429
227
+ | (bad key) (blocked IP) (limit hit)
228
+ |
229
+ v
230
+ [res.finish callback] --> Analytics circular buffer
231
+ ```
232
+
233
+ **Why this order matters:**
234
+ 1. **Logger first**: Every request is captured, including rejected ones
235
+ 2. **Auth second**: Rate limiter needs client identity and tier
236
+ 3. **IP filter third**: Reject blocked IPs before consuming rate limit tokens
237
+ 4. **Rate limiter last**: Needs all context from prior stages
238
+
239
+ ### State Architecture
240
+
241
+ ```
242
+ IN-MEMORY (ephemeral) CONFIGURABLE
243
+ +----------------------------+ +----------------------------+
244
+ | Rate Limiter State | | Tier definitions |
245
+ | Per-IP token counts | | Algorithm per tier |
246
+ | Sliding window logs | | Global limit settings |
247
+ | Fixed window counters | | API keys + tiers |
248
+ | | | IP allowlist/blocklist |
249
+ | Analytics Circular Buffer | +----------------------------+
250
+ | 10,000 entries max |
251
+ | ~2 MB memory cap | FILE-BASED (persistent)
252
+ | | +----------------------------+
253
+ | Device Registry | | devices.json |
254
+ | Active browser devices | | Debounced writes (2s) |
255
+ +----------------------------+ +----------------------------+
256
+ ```
257
+
258
+ Rate limit state resets on server restart. This is by design: a restart gives every client a fresh allowance.
259
+
260
+ ## Rate Limiting Algorithms
261
+
262
+ ### Token Bucket (recommended for most use cases)
263
+
264
+ - Tokens refill at a constant rate; each request consumes one token
265
+ - Allows controlled bursts up to bucket capacity
266
+ - O(1) memory per client (16 bytes: tokens + lastRefill)
267
+ - Lazy evaluation: zero CPU between requests
268
+ - Used by: AWS API Gateway, Stripe, GitHub API
269
+
270
+ ### Sliding Window Log (highest accuracy)
271
+
272
+ - Stores timestamp of every request in a rolling window
273
+ - No boundary problem: true rolling count
274
+ - O(N) memory per client where N = max requests per window
275
+ - Best for: billing systems, compliance-sensitive APIs
276
+ - Trade-off: higher memory usage at scale
277
+
278
+ ### Fixed Window Counter (used for global limits)
279
+
280
+ - Simple counter that resets at fixed intervals
281
+ - O(1) memory per client (16 bytes: count + windowStart)
282
+ - Known boundary problem: 2x burst possible at window edges
283
+ - Best for: global rate limits where approximate enforcement is acceptable
284
+
285
+ ### Two-Level Enforcement
286
+
287
+ Every request must pass both a global limit (Fixed Window, shared across all clients) and a per-tier limit (algorithm depends on tier config). The global limit prevents infrastructure overload; tier limits enforce SLA boundaries.
288
+
289
+ ## Technical Challenges
290
+
291
+ ### TypeScript Subpath Exports
292
+
293
+ The package uses Node.js subpath exports (`./backend`, `./frontend`, `./types`) to separate concerns. TypeScript only resolves these correctly with `module: "Node16"` or `"NodeNext"` in the consumer's tsconfig. Older module settings silently fail to find types. This is a common pain point with npm packages that use subpath exports and is documented as a requirement.
294
+
295
+ ### SSE Through Nginx
296
+
297
+ Server-Sent Events work perfectly in development but break behind Nginx with default buffering. Events accumulate in the proxy buffer instead of streaming. The fix requires specific Nginx configuration:
298
+
299
+ ```nginx
300
+ location /api/gateway/analytics/live {
301
+ proxy_pass http://localhost:3001;
302
+ proxy_buffering off;
303
+ proxy_set_header X-Accel-Buffering no;
304
+ proxy_set_header Cache-Control no-cache;
305
+ proxy_set_header Connection '';
306
+ proxy_http_version 1.1;
307
+ }
308
+ ```
309
+
310
+ ### EventSource Cannot Send Headers
311
+
312
+ The browser's native `EventSource` API does not support custom headers. This breaks API key authentication on SSE connections. The fix was replacing EventSource with a fetch-based SSE reader using `ReadableStream` and `TextDecoder` to manually parse the `text/event-stream` format while maintaining full header control.
313
+
314
+ ### Circular Buffer Read Order
315
+
316
+ When a circular buffer wraps around, the oldest entry is at the head pointer, not index 0. Reading in chronological order requires slicing the buffer into two segments (head-to-end, then 0-to-head) and concatenating them. Off-by-one errors in this logic caused out-of-order display in the dashboard's recent requests table.
317
+
318
+ ### TOTP Timing Safety
319
+
320
+ Standard string comparison (`===`) leaks timing information. An attacker can brute-force TOTP codes character by character by measuring response times. The fix uses `crypto.timingSafeEqual`, which always compares all bytes regardless of mismatch position. Both inputs must be normalized to fixed-length Buffers before comparison.
321
+
322
+ ### Peer Dependency Version Ranges
323
+
324
+ React 18 and 19, and Recharts 2 and 3, have different internal APIs. The package supports both major versions (`react ^18.0.0 || ^19.0.0`, `recharts ^2.0.0 || ^3.0.0`) by avoiding APIs that changed between versions.
325
+
326
+ ## Learnings
327
+
328
+ ### Algorithms Are Simple, Systems Are Hard
329
+
330
+ Token Bucket is two numbers and a time delta. Sliding Window is an array filter. Fixed Window is a counter with a timestamp. The real complexity is middleware ordering, state isolation per client, HTTP header conventions, and making all pieces work together.
331
+
332
+ ### Memory Bounds Matter
333
+
334
+ Without a circular buffer, the analytics service would grow linearly with traffic until the Node.js process runs out of memory. The 10,000-entry cap guarantees ~2 MB regardless of request volume. This same principle applies to rate limit state: Token Bucket's O(1) per client scales to 100k clients in 1.6 MB, while Sliding Window's O(N) would consume 80 MB for 10k pro-tier clients.
335
+
336
+ ### Package Distribution Is Its Own Skill
337
+
338
+ Writing code that works in your own project is different from writing code that works in someone else's project. Subpath exports, peer dependencies, TypeScript module resolution, CSS bundling, dual CJS/ESM output, and version compatibility all require careful configuration that has nothing to do with the actual business logic.
339
+
340
+ ### SSE > WebSockets for Unidirectional Streams
341
+
342
+ SSE is simpler, works over standard HTTP, and has browser-native reconnection. Unless you need bidirectional communication, SSE is the better choice. The only gotcha is proxy buffering and the EventSource header limitation, both solvable.
343
+
344
+ ### Fail Open, Not Closed
345
+
346
+ A broken rate limiter should not become a denial-of-service against your own users. If the rate limiting logic throws an error, the middleware should allow the request through, log the error, and alert. Failing closed means your own bug takes down your service.
347
+
348
+ ## API Reference
349
+
350
+ ### Management Endpoints
351
+
352
+ | Method | Path | Description |
353
+ |--------|------|-------------|
354
+ | GET | `/gateway/analytics` | Current analytics snapshot |
355
+ | GET | `/gateway/analytics/live` | SSE stream (5s updates) |
356
+ | GET | `/gateway/config` | Current gateway configuration |
357
+ | GET | `/gateway/logs?limit=20&offset=0` | Paginated request logs |
358
+ | POST | `/gateway/keys` | Create API key (body: `{name, tier}`) |
359
+ | DELETE | `/gateway/keys/:keyId` | Revoke an API key |
360
+
361
+ ### Device Auth Endpoints
362
+
363
+ | Method | Path | Description |
364
+ |--------|------|-------------|
365
+ | POST | `/auth/register` | Register browser device (body: `{browserId}`) |
366
+ | GET | `/auth/status/:browserId` | Check device status |
367
+ | DELETE | `/auth/:browserId` | Revoke device |
368
+ | GET | `/auth/stats` | Device registry statistics |
369
+
370
+ ### Rate Limit Response Headers
371
+
372
+ | Header | Description |
373
+ |--------|-------------|
374
+ | `X-RateLimit-Limit` | Request quota for the tier |
375
+ | `X-RateLimit-Remaining` | Remaining requests in window |
376
+ | `X-RateLimit-Reset` | Seconds until limit resets |
377
+
378
+ ## Theming
379
+
380
+ Override CSS custom properties to match your app's theme:
381
+
382
+ ```css
383
+ .gw-dashboard {
384
+ --gw-bg-card: #1a1a2e;
385
+ --gw-accent: #00d4ff;
386
+ --gw-text-primary: #ffffff;
387
+ --gw-text-muted: #8888aa;
388
+ --gw-border: #2a2a4a;
389
+ }
390
+ ```
391
+
392
+ ## TypeScript Support
393
+
394
+ All types are exported from the `./types` subpath:
395
+
396
+ ```typescript
397
+ import type {
398
+ RateLimitConfig,
399
+ GatewayAnalytics,
400
+ GatewayConfig,
401
+ RequestLog,
402
+ ApiKey,
403
+ IpRules,
404
+ TierConfig,
405
+ } from '@gagandeep023/api-gateway/types';
406
+ ```
407
+
408
+ ## Contributing
409
+
410
+ Issues and PRs are welcome at [github.com/Gagandeep023/api-gateway](https://github.com/Gagandeep023/api-gateway).
411
+
412
+ ```bash
413
+ git clone https://github.com/Gagandeep023/api-gateway.git
414
+ cd api-gateway
415
+ npm install
416
+ npm run build
417
+ npm test
418
+ ```
419
+
420
+ ## License
421
+
422
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gagandeep023/api-gateway",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Type-safe Express API gateway with IP-based rate limiting, analytics, and React dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -29,7 +29,8 @@
29
29
  }
30
30
  },
31
31
  "files": [
32
- "dist"
32
+ "dist",
33
+ "GUIDE.md"
33
34
  ],
34
35
  "scripts": {
35
36
  "build": "tsup && cp src/frontend/GatewayDashboard.css dist/frontend/GatewayDashboard.css",