0http-bun 1.1.3 → 1.2.0
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.
- package/README.md +72 -3
- package/common.d.ts +37 -2
- package/lib/middleware/README.md +758 -0
- package/lib/middleware/body-parser.js +783 -0
- package/lib/middleware/cors.js +225 -0
- package/lib/middleware/index.d.ts +236 -0
- package/lib/middleware/index.js +45 -0
- package/lib/middleware/jwt-auth.js +406 -0
- package/lib/middleware/logger.js +310 -0
- package/lib/middleware/rate-limit.js +306 -0
- package/lib/router/sequential.js +10 -2
- package/package.json +6 -3
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
# Middleware Documentation
|
|
2
|
+
|
|
3
|
+
0http-bun provides a comprehensive middleware system with built-in middlewares for common use cases. All middleware functions are TypeScript-ready and follow the standard middleware pattern.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Middleware Pattern](#middleware-pattern)
|
|
8
|
+
- [Built-in Middlewares](#built-in-middlewares)
|
|
9
|
+
- [Body Parser](#body-parser)
|
|
10
|
+
- [CORS](#cors)
|
|
11
|
+
- [JWT Authentication](#jwt-authentication)
|
|
12
|
+
- [Logger](#logger)
|
|
13
|
+
- [Rate Limiting](#rate-limiting)
|
|
14
|
+
- [Creating Custom Middleware](#creating-custom-middleware)
|
|
15
|
+
|
|
16
|
+
## Middleware Pattern
|
|
17
|
+
|
|
18
|
+
All middlewares in 0http-bun follow the standard pattern:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import {ZeroRequest, StepFunction} from '0http-bun'
|
|
22
|
+
|
|
23
|
+
type Middleware = (
|
|
24
|
+
req: ZeroRequest,
|
|
25
|
+
next: StepFunction,
|
|
26
|
+
) => Promise<Response> | Response
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### TypeScript Support
|
|
30
|
+
|
|
31
|
+
TypeScript type definitions are available for both the core framework and middleware modules:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Core framework types (from root module)
|
|
35
|
+
import {ZeroRequest, StepFunction, RequestHandler} from '0http-bun'
|
|
36
|
+
|
|
37
|
+
// Middleware-specific types (from middleware module)
|
|
38
|
+
import type {
|
|
39
|
+
LoggerOptions,
|
|
40
|
+
JWTAuthOptions,
|
|
41
|
+
RateLimitOptions,
|
|
42
|
+
RateLimitStore,
|
|
43
|
+
MemoryStore,
|
|
44
|
+
CORSOptions,
|
|
45
|
+
BodyParserOptions,
|
|
46
|
+
} from '0http-bun/lib/middleware'
|
|
47
|
+
|
|
48
|
+
// Import middleware functions
|
|
49
|
+
import {
|
|
50
|
+
createLogger,
|
|
51
|
+
createJWTAuth,
|
|
52
|
+
createRateLimit,
|
|
53
|
+
} from '0http-bun/lib/middleware'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Built-in Middlewares
|
|
57
|
+
|
|
58
|
+
All middleware can be imported from the main middleware module:
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
// Import all middleware from the middleware index
|
|
62
|
+
const {
|
|
63
|
+
createBodyParser,
|
|
64
|
+
createCORS,
|
|
65
|
+
createJWTAuth,
|
|
66
|
+
createLogger,
|
|
67
|
+
createRateLimit,
|
|
68
|
+
} = require('0http-bun/lib/middleware')
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For TypeScript:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Import middleware functions
|
|
75
|
+
import {
|
|
76
|
+
createBodyParser,
|
|
77
|
+
createCORS,
|
|
78
|
+
createJWTAuth,
|
|
79
|
+
createLogger,
|
|
80
|
+
createRateLimit,
|
|
81
|
+
} from '0http-bun/lib/middleware'
|
|
82
|
+
|
|
83
|
+
// Import types
|
|
84
|
+
import type {
|
|
85
|
+
BodyParserOptions,
|
|
86
|
+
CORSOptions,
|
|
87
|
+
JWTAuthOptions,
|
|
88
|
+
LoggerOptions,
|
|
89
|
+
RateLimitOptions,
|
|
90
|
+
} from '0http-bun/lib/middleware'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Body Parser
|
|
94
|
+
|
|
95
|
+
Automatically parses request bodies based on Content-Type header.
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
const {createBodyParser} = require('0http-bun/lib/middleware')
|
|
99
|
+
|
|
100
|
+
const router = http()
|
|
101
|
+
|
|
102
|
+
// Basic usage
|
|
103
|
+
router.use(createBodyParser())
|
|
104
|
+
|
|
105
|
+
// Access parsed body
|
|
106
|
+
router.post('/api/data', (req) => {
|
|
107
|
+
console.log(req.body) // Parsed body content
|
|
108
|
+
return Response.json({received: req.body})
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**TypeScript Usage:**
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import {createBodyParser} from '0http-bun/lib/middleware'
|
|
116
|
+
import type {BodyParserOptions} from '0http-bun/lib/middleware'
|
|
117
|
+
|
|
118
|
+
// With custom configuration
|
|
119
|
+
const bodyParserOptions: BodyParserOptions = {
|
|
120
|
+
json: {
|
|
121
|
+
limit: 10 * 1024 * 1024, // 10MB
|
|
122
|
+
strict: true,
|
|
123
|
+
},
|
|
124
|
+
urlencoded: {
|
|
125
|
+
extended: true,
|
|
126
|
+
limit: 1024 * 1024, // 1MB
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
router.use(createBodyParser(bodyParserOptions))
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Supported Content Types:**
|
|
134
|
+
|
|
135
|
+
- `application/json` - Parsed as JSON
|
|
136
|
+
- `application/x-www-form-urlencoded` - Parsed as form data
|
|
137
|
+
- `multipart/form-data` - Parsed as FormData
|
|
138
|
+
- `text/*` - Parsed as plain text
|
|
139
|
+
- `application/octet-stream` - Parsed as ArrayBuffer
|
|
140
|
+
|
|
141
|
+
### CORS
|
|
142
|
+
|
|
143
|
+
Cross-Origin Resource Sharing middleware with flexible configuration.
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
const {createCORS} = require('0http-bun/lib/middleware')
|
|
147
|
+
|
|
148
|
+
// Basic usage (allows all origins)
|
|
149
|
+
router.use(createCORS())
|
|
150
|
+
|
|
151
|
+
// Custom configuration
|
|
152
|
+
router.use(
|
|
153
|
+
createCORS({
|
|
154
|
+
origin: ['https://example.com', 'https://app.example.com'],
|
|
155
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
156
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
157
|
+
exposedHeaders: ['X-Total-Count'],
|
|
158
|
+
credentials: true,
|
|
159
|
+
maxAge: 86400, // Preflight cache duration (seconds)
|
|
160
|
+
preflightContinue: false,
|
|
161
|
+
optionsSuccessStatus: 204,
|
|
162
|
+
}),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
// Dynamic origin validation
|
|
166
|
+
router.use(
|
|
167
|
+
createCORS({
|
|
168
|
+
origin: (origin, req) => {
|
|
169
|
+
// Custom logic to validate origin
|
|
170
|
+
return (
|
|
171
|
+
origin?.endsWith('.mycompany.com') || origin === 'http://localhost:3000'
|
|
172
|
+
)
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
)
|
|
176
|
+
````
|
|
177
|
+
|
|
178
|
+
**TypeScript Usage:**
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import {createCORS} from '0http-bun/lib/middleware'
|
|
182
|
+
import type {CORSOptions} from '0http-bun/lib/middleware'
|
|
183
|
+
|
|
184
|
+
const corsOptions: CORSOptions = {
|
|
185
|
+
origin: ['https://example.com', 'https://app.example.com'],
|
|
186
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
187
|
+
credentials: true,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
router.use(createCORS(corsOptions))
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### JWT Authentication
|
|
194
|
+
|
|
195
|
+
JSON Web Token authentication and authorization middleware with support for static secrets, JWKS endpoints, and API key authentication.
|
|
196
|
+
|
|
197
|
+
#### Basic JWT with Static Secret
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
const {createJWTAuth} = require('0http-bun/lib/middleware')
|
|
201
|
+
|
|
202
|
+
// Basic JWT verification with static secret
|
|
203
|
+
router.use(
|
|
204
|
+
'/api/protected/*',
|
|
205
|
+
createJWTAuth({
|
|
206
|
+
secret: 'your-secret-key',
|
|
207
|
+
algorithms: ['HS256'],
|
|
208
|
+
}),
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**TypeScript Usage:**
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import {createJWTAuth} from '0http-bun/lib/middleware'
|
|
216
|
+
import type {JWTAuthOptions} from '0http-bun/lib/middleware'
|
|
217
|
+
|
|
218
|
+
const jwtOptions: JWTAuthOptions = {
|
|
219
|
+
secret: 'your-secret-key',
|
|
220
|
+
jwtOptions: {
|
|
221
|
+
algorithms: ['HS256'],
|
|
222
|
+
audience: 'your-api',
|
|
223
|
+
issuer: 'your-service',
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
router.use('/api/protected/*', createJWTAuth(jwtOptions))
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### JWT with JWKS URI (Recommended for Production)
|
|
231
|
+
|
|
232
|
+
For production applications, especially when integrating with identity providers like Auth0, AWS Cognito, or Azure AD, use JWKS URI for automatic key rotation:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// Using JWKS URI (Auth0 example)
|
|
236
|
+
router.use(
|
|
237
|
+
'/api/protected/*',
|
|
238
|
+
createJWTAuth({
|
|
239
|
+
jwksUri: 'https://your-domain.auth0.com/.well-known/jwks.json',
|
|
240
|
+
algorithms: ['RS256'],
|
|
241
|
+
issuer: 'https://your-domain.auth0.com/',
|
|
242
|
+
audience: 'your-api-identifier',
|
|
243
|
+
}),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
// AWS Cognito example
|
|
247
|
+
router.use(
|
|
248
|
+
'/api/protected/*',
|
|
249
|
+
createJWTAuth({
|
|
250
|
+
jwksUri:
|
|
251
|
+
'https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json',
|
|
252
|
+
algorithms: ['RS256'],
|
|
253
|
+
issuer: 'https://cognito-idp.{region}.amazonaws.com/{userPoolId}',
|
|
254
|
+
audience: 'your-client-id',
|
|
255
|
+
}),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
// Azure AD example
|
|
259
|
+
router.use(
|
|
260
|
+
'/api/protected/*',
|
|
261
|
+
createJWTAuth({
|
|
262
|
+
jwksUri: 'https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys',
|
|
263
|
+
algorithms: ['RS256'],
|
|
264
|
+
issuer: 'https://login.microsoftonline.com/{tenant}/v2.0',
|
|
265
|
+
audience: 'your-application-id',
|
|
266
|
+
}),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
// Google Identity example
|
|
270
|
+
router.use(
|
|
271
|
+
'/api/protected/*',
|
|
272
|
+
createJWTAuth({
|
|
273
|
+
jwksUri: 'https://www.googleapis.com/oauth2/v3/certs',
|
|
274
|
+
algorithms: ['RS256'],
|
|
275
|
+
issuer: 'https://accounts.google.com',
|
|
276
|
+
audience: 'your-client-id.apps.googleusercontent.com',
|
|
277
|
+
}),
|
|
278
|
+
)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### Advanced Configuration
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// Complete configuration example
|
|
285
|
+
router.use(
|
|
286
|
+
createJWTAuth({
|
|
287
|
+
// Option 1: Static secret (for development/simple cases)
|
|
288
|
+
secret: process.env.JWT_SECRET,
|
|
289
|
+
|
|
290
|
+
// Option 2: JWKS URI (recommended for production)
|
|
291
|
+
jwksUri: process.env.JWKS_URI,
|
|
292
|
+
|
|
293
|
+
// JWT verification options
|
|
294
|
+
algorithms: ['HS256', 'RS256'],
|
|
295
|
+
issuer: 'your-app',
|
|
296
|
+
audience: 'your-users',
|
|
297
|
+
clockTolerance: 10, // Clock skew tolerance (seconds)
|
|
298
|
+
ignoreExpiration: false,
|
|
299
|
+
ignoreNotBefore: false,
|
|
300
|
+
|
|
301
|
+
// Custom token extraction
|
|
302
|
+
getToken: (req) => {
|
|
303
|
+
// Try multiple sources
|
|
304
|
+
return (
|
|
305
|
+
req.headers.get('x-auth-token') ||
|
|
306
|
+
req.headers.get('authorization')?.replace('Bearer ', '') ||
|
|
307
|
+
new URL(req.url).searchParams.get('token')
|
|
308
|
+
)
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
// Alternative token sources
|
|
312
|
+
tokenHeader: 'x-custom-token', // Custom header name
|
|
313
|
+
tokenQuery: 'access_token', // Query parameter name
|
|
314
|
+
|
|
315
|
+
// Error handling
|
|
316
|
+
onError: (err, req) => {
|
|
317
|
+
console.error('JWT Error:', err)
|
|
318
|
+
return Response.json(
|
|
319
|
+
{
|
|
320
|
+
error: 'Unauthorized',
|
|
321
|
+
code: err.name,
|
|
322
|
+
message:
|
|
323
|
+
process.env.NODE_ENV === 'development' ? err.message : undefined,
|
|
324
|
+
},
|
|
325
|
+
{status: 401},
|
|
326
|
+
)
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
// Custom unauthorized response
|
|
330
|
+
unauthorizedResponse: (error, req) => {
|
|
331
|
+
return Response.json(
|
|
332
|
+
{
|
|
333
|
+
error: 'Access denied',
|
|
334
|
+
requestId: req.headers.get('x-request-id'),
|
|
335
|
+
timestamp: new Date().toISOString(),
|
|
336
|
+
},
|
|
337
|
+
{status: 401},
|
|
338
|
+
)
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
// Optional authentication (proceed even without token)
|
|
342
|
+
optional: false,
|
|
343
|
+
|
|
344
|
+
// Exclude certain paths
|
|
345
|
+
excludePaths: ['/health', '/metrics', '/api/public'],
|
|
346
|
+
}),
|
|
347
|
+
)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### API Key Authentication
|
|
351
|
+
|
|
352
|
+
The JWT middleware also supports API key authentication as an alternative or fallback:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
// API key with static keys
|
|
356
|
+
router.use(
|
|
357
|
+
'/api/*',
|
|
358
|
+
createJWTAuth({
|
|
359
|
+
apiKeys: ['key1', 'key2', 'key3'],
|
|
360
|
+
apiKeyHeader: 'x-api-key', // Default header
|
|
361
|
+
}),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
// API key with custom validation
|
|
365
|
+
router.use(
|
|
366
|
+
'/api/*',
|
|
367
|
+
createJWTAuth({
|
|
368
|
+
apiKeyValidator: async (apiKey, req) => {
|
|
369
|
+
// Custom validation logic
|
|
370
|
+
const user = await validateApiKeyInDatabase(apiKey)
|
|
371
|
+
return user ? {id: user.id, name: user.name, apiKey} : false
|
|
372
|
+
},
|
|
373
|
+
apiKeyHeader: 'x-api-key',
|
|
374
|
+
}),
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
// Combined JWT + API Key authentication
|
|
378
|
+
router.use(
|
|
379
|
+
'/api/*',
|
|
380
|
+
createJWTAuth({
|
|
381
|
+
// JWT configuration
|
|
382
|
+
jwksUri: process.env.JWKS_URI,
|
|
383
|
+
algorithms: ['RS256'],
|
|
384
|
+
|
|
385
|
+
// API Key fallback
|
|
386
|
+
apiKeys: process.env.API_KEYS?.split(','),
|
|
387
|
+
apiKeyHeader: 'x-api-key',
|
|
388
|
+
|
|
389
|
+
// If JWT fails, try API key
|
|
390
|
+
optional: false,
|
|
391
|
+
}),
|
|
392
|
+
)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### Environment-Based Configuration
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
// Dynamic configuration based on environment
|
|
399
|
+
const jwtConfig =
|
|
400
|
+
process.env.NODE_ENV === 'production'
|
|
401
|
+
? {
|
|
402
|
+
// Production: Use JWKS for security and key rotation
|
|
403
|
+
jwksUri: process.env.JWKS_URI,
|
|
404
|
+
algorithms: ['RS256'],
|
|
405
|
+
issuer: process.env.JWT_ISSUER,
|
|
406
|
+
audience: process.env.JWT_AUDIENCE,
|
|
407
|
+
}
|
|
408
|
+
: {
|
|
409
|
+
// Development: Use static secret for simplicity
|
|
410
|
+
secret: process.env.JWT_SECRET || 'dev-secret-key',
|
|
411
|
+
algorithms: ['HS256'],
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
router.use('/api/protected/*', createJWTAuth(jwtConfig))
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
#### Access Decoded Token Data
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// Access decoded token in route handlers
|
|
421
|
+
router.get('/api/profile', (req) => {
|
|
422
|
+
// Multiple ways to access user data
|
|
423
|
+
console.log(req.user) // Decoded JWT payload
|
|
424
|
+
console.log(req.ctx.user) // Same as req.user
|
|
425
|
+
console.log(req.jwt) // Full JWT info (payload, header, token)
|
|
426
|
+
console.log(req.ctx.jwt) // Same as req.jwt
|
|
427
|
+
|
|
428
|
+
// API key authentication data (if used)
|
|
429
|
+
console.log(req.apiKey) // API key value
|
|
430
|
+
console.log(req.ctx.apiKey) // Same as req.apiKey
|
|
431
|
+
|
|
432
|
+
return Response.json({
|
|
433
|
+
user: req.user,
|
|
434
|
+
tokenInfo: {
|
|
435
|
+
issuer: req.jwt?.payload.iss,
|
|
436
|
+
audience: req.jwt?.payload.aud,
|
|
437
|
+
expiresAt: new Date(req.jwt?.payload.exp * 1000),
|
|
438
|
+
issuedAt: new Date(req.jwt?.payload.iat * 1000),
|
|
439
|
+
},
|
|
440
|
+
})
|
|
441
|
+
})
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Logger
|
|
445
|
+
|
|
446
|
+
Request logging middleware with customizable output formats.
|
|
447
|
+
|
|
448
|
+
```javascript
|
|
449
|
+
const {createLogger, simpleLogger} = require('0http-bun/lib/middleware')
|
|
450
|
+
|
|
451
|
+
// Simple logging
|
|
452
|
+
router.use(simpleLogger())
|
|
453
|
+
|
|
454
|
+
// Detailed logging with custom format
|
|
455
|
+
router.use(
|
|
456
|
+
createLogger({
|
|
457
|
+
pinoOptions: {
|
|
458
|
+
level: 'info',
|
|
459
|
+
transport: {
|
|
460
|
+
target: 'pino-pretty',
|
|
461
|
+
options: {colorize: true},
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
logBody: false,
|
|
465
|
+
excludePaths: ['/health', '/metrics'],
|
|
466
|
+
}),
|
|
467
|
+
)
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**TypeScript Usage:**
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
import {createLogger, simpleLogger} from '0http-bun/lib/middleware'
|
|
474
|
+
import type {LoggerOptions} from '0http-bun/lib/middleware'
|
|
475
|
+
|
|
476
|
+
const loggerOptions: LoggerOptions = {
|
|
477
|
+
pinoOptions: {
|
|
478
|
+
level: 'info',
|
|
479
|
+
transport: {
|
|
480
|
+
target: 'pino-pretty',
|
|
481
|
+
options: {colorize: true},
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
logBody: true,
|
|
485
|
+
excludePaths: ['/health', '/ping'],
|
|
486
|
+
serializers: {
|
|
487
|
+
req: (req) => ({
|
|
488
|
+
method: req.method,
|
|
489
|
+
url: req.url,
|
|
490
|
+
userAgent: req.headers.get('user-agent'),
|
|
491
|
+
}),
|
|
492
|
+
},
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
router.use(createLogger(loggerOptions))
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Available Formats:**
|
|
499
|
+
|
|
500
|
+
- `combined` - Apache Combined Log Format
|
|
501
|
+
- `common` - Apache Common Log Format
|
|
502
|
+
- `short` - Shorter than common, includes response time
|
|
503
|
+
- `tiny` - Minimal output
|
|
504
|
+
- `dev` - Development-friendly colored output
|
|
505
|
+
|
|
506
|
+
### Rate Limiting
|
|
507
|
+
|
|
508
|
+
Configurable rate limiting middleware with multiple store options.
|
|
509
|
+
|
|
510
|
+
```javascript
|
|
511
|
+
const {createRateLimit, MemoryStore} = require('0http-bun/lib/middleware')
|
|
512
|
+
|
|
513
|
+
// Basic rate limiting
|
|
514
|
+
router.use(
|
|
515
|
+
createRateLimit({
|
|
516
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
517
|
+
max: 100, // Max 100 requests per windowMs
|
|
518
|
+
}),
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
// Advanced configuration
|
|
522
|
+
router.use(
|
|
523
|
+
createRateLimit({
|
|
524
|
+
windowMs: 60 * 1000, // 1 minute
|
|
525
|
+
max: 20, // Max requests
|
|
526
|
+
keyGenerator: (req) => {
|
|
527
|
+
// Custom key generation (default: IP address)
|
|
528
|
+
return req.headers.get('x-user-id') || req.headers.get('x-forwarded-for')
|
|
529
|
+
},
|
|
530
|
+
skip: (req) => {
|
|
531
|
+
// Skip rate limiting for certain requests
|
|
532
|
+
return req.url.startsWith('/health')
|
|
533
|
+
},
|
|
534
|
+
handler: (req, totalHits, max, resetTime) => {
|
|
535
|
+
// Custom rate limit exceeded response
|
|
536
|
+
return Response.json(
|
|
537
|
+
{
|
|
538
|
+
error: 'Rate limit exceeded',
|
|
539
|
+
resetTime: resetTime.toISOString(),
|
|
540
|
+
retryAfter: Math.ceil((resetTime.getTime() - Date.now()) / 1000),
|
|
541
|
+
},
|
|
542
|
+
{status: 429},
|
|
543
|
+
)
|
|
544
|
+
},
|
|
545
|
+
standardHeaders: true, // Send X-RateLimit-* headers
|
|
546
|
+
excludePaths: ['/health', '/metrics'],
|
|
547
|
+
}),
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
// Custom store (for distributed systems)
|
|
551
|
+
router.use(
|
|
552
|
+
createRateLimit({
|
|
553
|
+
store: new MemoryStore(), // Built-in memory store
|
|
554
|
+
// Or implement custom store with increment() method
|
|
555
|
+
}),
|
|
556
|
+
)
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**TypeScript Usage:**
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
import {createRateLimit, MemoryStore} from '0http-bun/lib/middleware'
|
|
563
|
+
import type {RateLimitOptions, RateLimitStore} from '0http-bun/lib/middleware'
|
|
564
|
+
|
|
565
|
+
const rateLimitOptions: RateLimitOptions = {
|
|
566
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
567
|
+
max: 100,
|
|
568
|
+
keyGenerator: (req) => {
|
|
569
|
+
return (
|
|
570
|
+
req.headers.get('x-user-id') ||
|
|
571
|
+
req.headers.get('x-forwarded-for') ||
|
|
572
|
+
'anonymous'
|
|
573
|
+
)
|
|
574
|
+
},
|
|
575
|
+
standardHeaders: true,
|
|
576
|
+
excludePaths: ['/health', '/ping'],
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
router.use(createRateLimit(rateLimitOptions))
|
|
580
|
+
|
|
581
|
+
// Custom store implementation
|
|
582
|
+
class CustomStore implements RateLimitStore {
|
|
583
|
+
async increment(
|
|
584
|
+
key: string,
|
|
585
|
+
windowMs: number,
|
|
586
|
+
): Promise<{totalHits: number; resetTime: Date}> {
|
|
587
|
+
// Custom implementation
|
|
588
|
+
return {totalHits: 1, resetTime: new Date(Date.now() + windowMs)}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
````
|
|
594
|
+
|
|
595
|
+
**Rate Limit Headers:**
|
|
596
|
+
|
|
597
|
+
- `X-RateLimit-Limit` - Request limit
|
|
598
|
+
- `X-RateLimit-Remaining` - Remaining requests
|
|
599
|
+
- `X-RateLimit-Reset` - Reset time (Unix timestamp)
|
|
600
|
+
- `X-RateLimit-Used` - Used requests
|
|
601
|
+
|
|
602
|
+
## Creating Custom Middleware
|
|
603
|
+
|
|
604
|
+
### Basic Middleware
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import {ZeroRequest, StepFunction} from '0http-bun'
|
|
608
|
+
|
|
609
|
+
const customMiddleware = (req: ZeroRequest, next: StepFunction) => {
|
|
610
|
+
// Pre-processing
|
|
611
|
+
req.ctx = req.ctx || {}
|
|
612
|
+
req.ctx.startTime = Date.now()
|
|
613
|
+
|
|
614
|
+
// Continue to next middleware/handler
|
|
615
|
+
const response = next()
|
|
616
|
+
|
|
617
|
+
// Post-processing (if needed)
|
|
618
|
+
return response
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
router.use(customMiddleware)
|
|
622
|
+
````
|
|
623
|
+
|
|
624
|
+
### Async Middleware
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
const asyncMiddleware = async (req: ZeroRequest, next: StepFunction) => {
|
|
628
|
+
// Async pre-processing
|
|
629
|
+
const user = await validateUserSession(req)
|
|
630
|
+
req.ctx = {user}
|
|
631
|
+
|
|
632
|
+
// Continue
|
|
633
|
+
const response = await next()
|
|
634
|
+
|
|
635
|
+
// Async post-processing
|
|
636
|
+
await logUserActivity(user, req.url)
|
|
637
|
+
|
|
638
|
+
return response
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Error Handling in Middleware
|
|
643
|
+
|
|
644
|
+
```typescript
|
|
645
|
+
const errorHandlingMiddleware = async (
|
|
646
|
+
req: ZeroRequest,
|
|
647
|
+
next: StepFunction,
|
|
648
|
+
) => {
|
|
649
|
+
try {
|
|
650
|
+
return await next()
|
|
651
|
+
} catch (error) {
|
|
652
|
+
console.error('Middleware error:', error)
|
|
653
|
+
|
|
654
|
+
// Return error response
|
|
655
|
+
return Response.json(
|
|
656
|
+
{
|
|
657
|
+
error: 'Internal server error',
|
|
658
|
+
message:
|
|
659
|
+
process.env.NODE_ENV === 'development' ? error.message : undefined,
|
|
660
|
+
},
|
|
661
|
+
{status: 500},
|
|
662
|
+
)
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
## Middleware Execution Order
|
|
668
|
+
|
|
669
|
+
Middlewares execute in the order they are registered:
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
router.use(middleware1) // Executes first
|
|
673
|
+
router.use(middleware2) // Executes second
|
|
674
|
+
router.use(middleware3) // Executes third
|
|
675
|
+
|
|
676
|
+
router.get('/test', handler) // Final handler
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
## Path-Specific Middleware
|
|
680
|
+
|
|
681
|
+
Apply middleware only to specific paths:
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
// API-only middleware
|
|
685
|
+
router.use('/api/*', jwtAuth({secret: 'api-secret'}))
|
|
686
|
+
router.use('/api/*', rateLimit({max: 1000}))
|
|
687
|
+
|
|
688
|
+
// Admin-only middleware
|
|
689
|
+
router.use('/admin/*', adminAuthMiddleware)
|
|
690
|
+
router.use('/admin/*', auditLogMiddleware)
|
|
691
|
+
|
|
692
|
+
// Public paths (no auth required)
|
|
693
|
+
router.get('/health', healthCheckHandler)
|
|
694
|
+
router.get('/metrics', metricsHandler)
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
## Best Practices
|
|
698
|
+
|
|
699
|
+
1. **Order Matters**: Place security middleware (CORS, auth) before business logic
|
|
700
|
+
2. **Error Handling**: Always handle errors in async middleware
|
|
701
|
+
3. **Performance**: Use `skip` functions to avoid unnecessary processing
|
|
702
|
+
4. **Context**: Use `req.ctx` to pass data between middlewares
|
|
703
|
+
5. **Immutability**: Don't modify the original request object directly
|
|
704
|
+
6. **Logging**: Log middleware errors for debugging
|
|
705
|
+
7. **Testing**: Test middleware in isolation with mock requests
|
|
706
|
+
|
|
707
|
+
## Examples
|
|
708
|
+
|
|
709
|
+
### Complete Middleware Stack
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
const {
|
|
713
|
+
createCORS,
|
|
714
|
+
createLogger,
|
|
715
|
+
createBodyParser,
|
|
716
|
+
createJWTAuth,
|
|
717
|
+
createRateLimit,
|
|
718
|
+
} = require('0http-bun/lib/middleware')
|
|
719
|
+
|
|
720
|
+
const router = http()
|
|
721
|
+
|
|
722
|
+
// 1. CORS (handle preflight requests first)
|
|
723
|
+
router.use(
|
|
724
|
+
createCORS({
|
|
725
|
+
origin: process.env.ALLOWED_ORIGINS?.split(','),
|
|
726
|
+
credentials: true,
|
|
727
|
+
}),
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
// 2. Logging (log all requests)
|
|
731
|
+
router.use(createLogger({format: 'combined'}))
|
|
732
|
+
|
|
733
|
+
// 3. Rate limiting (protect against abuse)
|
|
734
|
+
router.use(
|
|
735
|
+
createRateLimit({
|
|
736
|
+
windowMs: 15 * 60 * 1000,
|
|
737
|
+
max: 1000,
|
|
738
|
+
}),
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
// 4. Body parsing (parse request bodies)
|
|
742
|
+
router.use(createBodyParser({limit: '10mb'}))
|
|
743
|
+
|
|
744
|
+
// 5. Authentication (protect API routes)
|
|
745
|
+
router.use(
|
|
746
|
+
'/api/*',
|
|
747
|
+
createJWTAuth({
|
|
748
|
+
secret: process.env.JWT_SECRET,
|
|
749
|
+
skip: (req) => req.url.includes('/api/public/'),
|
|
750
|
+
}),
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
// Routes
|
|
754
|
+
router.get('/api/public/status', () => Response.json({status: 'ok'}))
|
|
755
|
+
router.get('/api/protected/data', (req) => Response.json({user: req.user}))
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
This middleware stack provides a solid foundation for most web applications with security, logging, and performance features built-in.
|