opticore-api-gateway 1.0.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 ADDED
@@ -0,0 +1,618 @@
1
+ # opticore-api-gateway
2
+
3
+ A flexible, production-ready API Gateway built in TypeScript, powered by the [Opticore](https://github.com/guyzoum77) ecosystem. It provides dynamic routing, load balancing, middleware chaining, circuit breaking, and service registry out of the box.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Features](#features)
10
+ - [Installation](#installation)
11
+ - [Quick Start — Standalone mode](#quick-start--standalone-mode)
12
+ - [Integration with opticore-webapp](#integration-with-opticore-webapp)
13
+ - [Configuration](#configuration)
14
+ - [`IGatewayConfig`](#igatewayconfig-interface)
15
+ - [`IRouteConfig`](#irouteconfig-interface)
16
+ - [`IServiceConfig`](#iserviceconfig-interface)
17
+ - [`ICircuitBreakerConfig`](#icircuitbreakerconfig-interface)
18
+ - [Load Balancing Strategies](#load-balancing-strategies)
19
+ - [Middleware](#middleware)
20
+ - [Global Middlewares](#global-middlewares)
21
+ - [Route-Level Middlewares](#route-level-middlewares)
22
+ - [Built-in Middlewares](#built-in-middlewares)
23
+ - [Creating Custom Middlewares](#creating-custom-middlewares)
24
+ - [API Reference](#api-reference)
25
+ - [Types & Interfaces](#types--interfaces)
26
+ - [Advanced Usage](#advanced-usage)
27
+ - [Error Handling](#error-handling)
28
+ - [License](#license)
29
+
30
+ ---
31
+
32
+ ## Features
33
+
34
+ - **Dynamic Routing** — Define routes with HTTP method, path, and target service(s)
35
+ - **Load Balancing** — Four strategies: `round-robin`, `weighted`, `random`, `least-connections`
36
+ - **Service Registry** — Automatic service health tracking and instance management
37
+ - **Circuit Breaker** — Per-route circuit breaker with configurable thresholds and reset timeouts
38
+ - **Middleware Chain** — Global and per-route middleware support (Express-compatible signature)
39
+ - **Request Forwarding** — Transparent proxying with header rewriting and tracing headers
40
+ - **Logging** — Built-in `LoggingMiddleware` with timing and status code
41
+ - **Validation** — Built-in `ValidationMiddleware` using `opticore-validator`
42
+ - **opticore-webapp Integration** — Drop-in replacement for `registerRouter()` in any Opticore template project
43
+
44
+ ---
45
+
46
+ **Request lifecycle:**
47
+
48
+ ```
49
+ Incoming request
50
+
51
+
52
+ Global middlewares (auth, rate-limit, logging, …)
53
+
54
+
55
+ Route matched by method + path prefix
56
+
57
+
58
+ Route-level middlewares
59
+
60
+
61
+ Load balancer selects a healthy service instance
62
+
63
+
64
+ Request forwarded to target with tracing headers
65
+
66
+
67
+ Response sent back to client
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Installation
73
+
74
+ ```bash
75
+ npm install opticore-api-gateway
76
+ ```
77
+
78
+ > Requires Node.js >= 16 and TypeScript >= 5.
79
+
80
+ ---
81
+
82
+ ## Quick Start — Standalone mode
83
+
84
+ The gateway creates its own HTTP server and proxies requests to backend services.
85
+
86
+ ```typescript
87
+ import {
88
+ APIGateway,
89
+ AuthMiddleware,
90
+ RateLimitMiddleware,
91
+ LoggingMiddleware
92
+ } from 'opticore-api-gateway';
93
+
94
+ const gateway = new APIGateway({
95
+ port: 3000,
96
+ loadBalancer: 'round-robin',
97
+ enableLogging: true,
98
+
99
+ services: [
100
+ {
101
+ name: 'user-service',
102
+ url: 'http://localhost:4001',
103
+ healthCheck: '/health',
104
+ timeout: 5000,
105
+ retries: 3,
106
+ },
107
+ {
108
+ name: 'order-service',
109
+ url: 'http://localhost:4002',
110
+ healthCheck: '/health',
111
+ },
112
+ ],
113
+
114
+ globalMiddlewares: [
115
+ new LoggingMiddleware('info'),
116
+ new RateLimitMiddleware(100, 60000),
117
+ ],
118
+
119
+ routes: [
120
+ {
121
+ path: '/users',
122
+ method: 'get',
123
+ target: 'http://localhost:4001',
124
+ serviceName: 'user-service',
125
+ timeout: 5000,
126
+ },
127
+ {
128
+ path: '/orders',
129
+ method: 'post',
130
+ target: 'http://localhost:4002',
131
+ serviceName: 'order-service',
132
+ middlewares: [new AuthMiddleware(['my-secret-key'])],
133
+ circuitBreaker: {
134
+ failureThreshold: 5,
135
+ resetTimeout: 30000,
136
+ halfOpenMaxAttempts: 2,
137
+ },
138
+ },
139
+ ],
140
+ });
141
+
142
+ await gateway.start();
143
+ // → API Gateway running on port 3000
144
+ // → Registered 2 routes
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Integration with opticore-webapp
150
+
151
+ When you install `opticore-api-gateway` in an Opticore template project (e.g. [`opticore-api-restfull-template-mysql`](https://github.com/guyzoum77/opticore-api-restfull-template-mysql)), the gateway can **replace** the `registerRouter()` call and manage all routing, middleware, and proxying.
152
+
153
+ ### How it works
154
+
155
+ `gateway.getOpticoreRoutes()` returns a `TFeatureRoutes[]` array that is directly passed to `WebServerCore.onStartServer()`. Each gateway route is mounted on the Express app with its global middlewares already embedded.
156
+
157
+ ### Step-by-step integration
158
+
159
+ **1. Install the package**
160
+
161
+ ```bash
162
+ npm install opticore-api-gateway
163
+ ```
164
+
165
+ **2. Replace `registerRouter()` in `src/bootstrap/server/webApp.server.ts`**
166
+
167
+ ```typescript
168
+ import {
169
+ APIGateway,
170
+ AuthMiddleware,
171
+ RateLimitMiddleware,
172
+ LoggingMiddleware
173
+ } from 'opticore-api-gateway';
174
+
175
+ const gateway = new APIGateway({
176
+ port: 4200, // informational — Express port is managed by WebServerCore
177
+ services: [
178
+ { name: 'user-service', url: 'http://user-service:4001' },
179
+ { name: 'product-service', url: 'http://product-service:4002' },
180
+ ],
181
+ routes: [
182
+ {
183
+ path: '/api/users',
184
+ method: 'get',
185
+ target: 'http://user-service:4001',
186
+ serviceName: 'user-service',
187
+ },
188
+ {
189
+ path: '/api/products',
190
+ method: 'get',
191
+ // Multiple targets → load balancing is activated
192
+ target: ['http://product-service-1:4002', 'http://product-service-2:4002'],
193
+ serviceName: 'product-service',
194
+ },
195
+ ],
196
+ globalMiddlewares: [
197
+ new LoggingMiddleware('info'),
198
+ new RateLimitMiddleware(100, 60000),
199
+ new AuthMiddleware(['my-api-key']),
200
+ ],
201
+ loadBalancer: 'round-robin',
202
+ enableLogging: true,
203
+ });
204
+
205
+ // Pass gateway routes instead of registerRouter()
206
+ webApp.onStartServer(gateway.getOpticoreRoutes(), dbConnection, dependenciesProvider);
207
+ ```
208
+
209
+ **3. (Optional) Apply gateway middlewares globally via Express**
210
+
211
+ If you want gateway middlewares to also run on routes outside the gateway (e.g. static files, error pages), use `getExpressMiddleware()`:
212
+
213
+ ```typescript
214
+ // Before calling onStartServer:
215
+ expressApp.use(gateway.getExpressMiddleware());
216
+ ```
217
+
218
+ ### What `getOpticoreRoutes()` does internally
219
+
220
+ - Creates one Express Router per configured route, mounted at `config.path`
221
+ - Each router handles the `*` wildcard — so `/api/users/123`, `/api/users?page=2` etc. are all captured
222
+ - The handler restores `req.url` to `req.originalUrl` (full path) before proxying so URL rewriting is correct
223
+ - Global middlewares run before the proxy forward for every matched request
224
+
225
+ ---
226
+
227
+ ## Configuration
228
+
229
+ ### `IGatewayConfig` interface
230
+
231
+ | Property | Type | Required | Description |
232
+ |---------------------|--------------------------|----------|-----------------------------------------------|
233
+ | `port` | `number` | ✅ | Port used by `start()` (standalone mode) |
234
+ | `services` | `IServiceConfig[]` | ✅ | Backend services to register |
235
+ | `routes` | `IRouteConfig[]` | ✅ | Route definitions |
236
+ | `globalMiddlewares` | `any[]` | ❌ | Middlewares applied to every request |
237
+ | `loadBalancer` | `TLoadBalancingStrategy` | ❌ | Default: `round-robin` |
238
+ | `enableLogging` | `boolean` | ❌ | Logs request duration when `true` |
239
+ | `enableMetrics` | `boolean` | ❌ | Reserved for future metrics collection |
240
+
241
+ ---
242
+
243
+ ### `IRouteConfig` interface
244
+
245
+ | Property | Type | Required | Description |
246
+ |------------------|-------------------------|----------|---------------------------------------------------------|
247
+ | `path` | `string` | ✅ | URL path prefix (e.g. `/api/users`) |
248
+ | `method` | `HttpMethod` | ✅ | `get`, `post`, `put`, `delete`, `patch`, `options`… |
249
+ | `target` | `string \| string[]` | ✅ | Target URL(s). Array enables load balancing. |
250
+ | `middlewares` | `any[]` | ❌ | Route-specific middlewares (run after global ones) |
251
+ | `timeout` | `number` | ❌ | Request timeout in ms (default: `30000`) |
252
+ | `retries` | `number` | ❌ | Number of retry attempts on failure |
253
+ | `circuitBreaker` | `ICircuitBreakerConfig` | ❌ | Circuit breaker configuration |
254
+ | `serviceName` | `string` | ❌ | Explicit service name override for registry lookup |
255
+
256
+ ---
257
+
258
+ ### `IServiceConfig` interface
259
+
260
+ | Property | Type | Required | Description |
261
+ |---------------|----------|----------|------------------------------------------------|
262
+ | `name` | `string` | ✅ | Unique service identifier |
263
+ | `url` | `string` | ✅ | Base URL of the service |
264
+ | `healthCheck` | `string` | ❌ | Health check endpoint path (e.g. `/health`) |
265
+ | `weight` | `number` | ❌ | Weight for `weighted` load balancing strategy |
266
+ | `timeout` | `number` | ❌ | Default request timeout in ms |
267
+ | `retries` | `number` | ❌ | Default retry count |
268
+
269
+ ---
270
+
271
+ ### `ICircuitBreakerConfig` interface
272
+
273
+ | Property | Type | Description |
274
+ |-----------------------|----------|--------------------------------------------------------|
275
+ | `failureThreshold` | `number` | Failures before the circuit opens |
276
+ | `resetTimeout` | `number` | Time in ms before transitioning to `half-open` |
277
+ | `halfOpenMaxAttempts` | `number` | Max probe requests allowed in `half-open` state |
278
+
279
+ Circuit states:
280
+ - **`closed`** — Normal operation. Requests are forwarded.
281
+ - **`open`** — Service is unhealthy. Requests fail fast (503).
282
+ - **`half-open`** — Limited probing to test recovery.
283
+
284
+ ---
285
+
286
+ ## Load Balancing Strategies
287
+
288
+ | Strategy | Description |
289
+ |---------------------|---------------------------------------------------------------------|
290
+ | `round-robin` | Distributes requests evenly across all healthy instances (default) |
291
+ | `weighted` | Routes more traffic to instances with a higher `weight` value |
292
+ | `random` | Selects a healthy instance at random |
293
+ | `least-connections` | Selects the instance with the fewest active connections |
294
+
295
+ ```typescript
296
+ const gateway = new APIGateway({
297
+ loadBalancer: 'least-connections',
298
+ // ...
299
+ });
300
+ ```
301
+
302
+ Multi-target route (load balancing across instances):
303
+
304
+ ```typescript
305
+ {
306
+ path: '/api',
307
+ method: 'get',
308
+ target: [
309
+ 'http://service-1:4000',
310
+ 'http://service-2:4000',
311
+ 'http://service-3:4000',
312
+ ],
313
+ serviceName: 'my-service',
314
+ }
315
+ ```
316
+
317
+ ---
318
+
319
+ ## Middleware
320
+
321
+ All middlewares follow an Express-compatible signature:
322
+
323
+ ```typescript
324
+ type TMiddlewareFunction = (req: any, res: any, next: () => void) => void | Promise<void>;
325
+ ```
326
+
327
+ ### Global Middlewares
328
+
329
+ Applied to every matched request before the proxy forward:
330
+
331
+ ```typescript
332
+ const gateway = new APIGateway({
333
+ globalMiddlewares: [
334
+ new LoggingMiddleware('info'),
335
+ new RateLimitMiddleware(100, 60000),
336
+ new AuthMiddleware(['key-abc', 'key-xyz']),
337
+ ],
338
+ // ...
339
+ });
340
+ ```
341
+
342
+ Add global middlewares at runtime:
343
+
344
+ ```typescript
345
+ gateway.addMiddleware((req, res, next) => {
346
+ console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
347
+ next();
348
+ });
349
+ ```
350
+
351
+ ### Route-Level Middlewares
352
+
353
+ Applied only to a specific route, after global middlewares:
354
+
355
+ ```typescript
356
+ {
357
+ path: '/admin',
358
+ method: 'get',
359
+ target: 'http://admin-service:5000',
360
+ middlewares: [
361
+ new AuthMiddleware(['admin-key']),
362
+ (req, res, next) => {
363
+ if (!req.headers['x-admin-token']) {
364
+ res.statusCode = 403;
365
+ res.end(JSON.stringify({ error: 'Forbidden' }));
366
+ return;
367
+ }
368
+ next();
369
+ },
370
+ ],
371
+ }
372
+ ```
373
+
374
+ ### Built-in Middlewares
375
+
376
+ #### `AuthMiddleware`
377
+
378
+ Validates `x-api-key` header (or `?apiKey=` query param):
379
+
380
+ ```typescript
381
+ import { AuthMiddleware } from 'opticore-api-gateway';
382
+
383
+ new AuthMiddleware(['secret-key-1', 'secret-key-2'])
384
+ ```
385
+
386
+ - Returns `401` if no key is provided
387
+ - Returns `403` if the key is invalid
388
+
389
+ #### `RateLimitMiddleware`
390
+
391
+ Sliding-window rate limiting per client IP:
392
+
393
+ ```typescript
394
+ import { RateLimitMiddleware } from 'opticore-api-gateway';
395
+
396
+ new RateLimitMiddleware(100, 60000) // 100 requests per 60 seconds
397
+ ```
398
+
399
+ Sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` response headers.
400
+ Returns `429` when the limit is exceeded.
401
+
402
+ #### `LoggingMiddleware`
403
+
404
+ Logs method, URL, status code, and duration on response finish:
405
+
406
+ ```typescript
407
+ import { LoggingMiddleware } from 'opticore-api-gateway';
408
+
409
+ new LoggingMiddleware('info') // 'info' | 'debug' | 'error'
410
+ ```
411
+
412
+ #### `ValidationMiddleware`
413
+
414
+ Validates request body (or query/params) using `opticore-validator`:
415
+
416
+ ```typescript
417
+ import { ValidationMiddleware } from 'opticore-api-gateway';
418
+
419
+ new ValidationMiddleware(mySchema, 'body') // 'body' | 'query' | 'params' | 'all'
420
+ ```
421
+
422
+ Returns `400` with field-level errors if validation fails.
423
+
424
+ ### Creating Custom Middlewares
425
+
426
+ Extend `BaseMiddleware` and implement `handle()`:
427
+
428
+ ```typescript
429
+ import { BaseMiddleware, TMiddlewareFunction } from 'opticore-api-gateway';
430
+
431
+ export class CorsMiddleware extends BaseMiddleware {
432
+ handle(): TMiddlewareFunction {
433
+ return (req, res, next) => {
434
+ res.setHeader('Access-Control-Allow-Origin', '*');
435
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
436
+ next();
437
+ };
438
+ }
439
+ }
440
+ ```
441
+
442
+ ---
443
+
444
+ ## API Reference
445
+
446
+ ### `APIGateway`
447
+
448
+ Main gateway class.
449
+
450
+ **Constructor**
451
+
452
+ ```typescript
453
+ new APIGateway(config: IGatewayConfig)
454
+ ```
455
+
456
+ **Methods**
457
+
458
+ | Method | Description |
459
+ |----------------------------------------------|--------------------------------------------------------------------------|
460
+ | `start(): Promise<void>` | Starts a standalone HTTP server on the configured port |
461
+ | `stop(): Promise<void>` | Gracefully shuts down the HTTP server |
462
+ | `addRoute(config: IRouteConfig)` | Dynamically registers a new route at runtime |
463
+ | `addMiddleware(fn: TMiddlewareFunction)` | Adds a global middleware at runtime |
464
+ | `registerService(service: IServiceConfig)` | Registers a new service in the service registry |
465
+ | `getOpticoreRoutes(): TFeatureRoutes[]` | Returns routes compatible with `WebServerCore.onStartServer()` |
466
+ | `getExpressMiddleware()` | Returns global middlewares as a single Express middleware function |
467
+
468
+ ---
469
+
470
+ ### `GatewayRoute`
471
+
472
+ Encapsulates a single route and its Opticore integration.
473
+
474
+ **Constructor**
475
+
476
+ ```typescript
477
+ new GatewayRoute(config: IRouteConfig, handler: TRouteHandler, localLanguage: string)
478
+ ```
479
+
480
+ **Methods**
481
+
482
+ | Method | Return Type | Description |
483
+ |---------------------------------------------------------------|---------------------------|----------------------------------------------|
484
+ | `getConfig()` | `IRouteConfig` | Returns the route configuration |
485
+ | `getStandaloneRoute(strategy?, options?)` | Express Router | Returns the standalone Express router |
486
+ | `createMultipleRouterConfig(controller)` | `IMultipleRouterConfig` | Generates an Opticore multi-router config |
487
+ | `createCollectionRouter(controller, routes)` | `void` | Initializes a collection router |
488
+ | `getMultipleRouteDefinition()` | `IMultipleRouteDefinition`| Returns the collection router definition |
489
+ | `GatewayRoute.createOpticoreRouteDefinition(path, handler)` | `{ path, handler }` | Static factory for Opticore route entries |
490
+
491
+ ---
492
+
493
+ ### `ServiceRegistry`
494
+
495
+ Manages service instances and health state.
496
+
497
+ | Method | Description |
498
+ |--------------------------------------------|---------------------------------------------------------|
499
+ | `registerService(service: IServiceConfig)` | Registers a new service and starts health check |
500
+ | `getHealthyInstances(serviceName: string)` | Returns healthy (circuit-closed) instances |
501
+ | `recordSuccess(serviceName, url)` | Resets failure counters; may close circuit |
502
+ | `recordFailure(serviceName, url)` | Increments failure count; may open circuit |
503
+ | `deregisterService(serviceName, url)` | Removes an instance and clears its health check |
504
+ | `getAllServices()` | Returns the full service map |
505
+
506
+ ---
507
+
508
+ ### `LoadBalancer`
509
+
510
+ Selects a service instance based on the configured strategy.
511
+
512
+ ```typescript
513
+ const lb = new LoadBalancer('weighted');
514
+ const instance = lb.selectInstance('user-service', healthyInstances);
515
+ lb.setStrategy('round-robin');
516
+ ```
517
+
518
+ ---
519
+
520
+ ### `HttpClient`
521
+
522
+ Low-level HTTP/HTTPS client used internally to forward requests.
523
+
524
+ ```typescript
525
+ const client = new HttpClient();
526
+ const response = await client.request('http://service:4000/api', {
527
+ method: 'POST',
528
+ headers: { 'Content-Type': 'application/json' },
529
+ body: JSON.stringify({ key: 'value' }),
530
+ timeout: 5000,
531
+ });
532
+ // response: { status, headers, body }
533
+ ```
534
+
535
+ Convenience methods: `client.get(url)`, `client.post(url, body)`, `client.put(url, body)`, `client.patch(url, body)`, `client.delete(url)`.
536
+
537
+ ---
538
+
539
+ ## Types & Interfaces
540
+
541
+ | Name | Description |
542
+ |--------------------------|--------------------------------------------------------------|
543
+ | `TMiddlewareFunction` | Express-compatible middleware signature |
544
+ | `TGatewayContext` | Opticore context extended with `serviceUrl`, `serviceInstance`, `gatewayStartTime` |
545
+ | `TLoadBalancingStrategy` | `'round-robin' \| 'weighted' \| 'random' \| 'least-connections'` |
546
+ | `IGatewayConfig` | Root gateway configuration |
547
+ | `IRouteConfig` | Individual route configuration |
548
+ | `IServiceConfig` | Backend service declaration |
549
+ | `IServiceInstance` | Runtime service instance with health state |
550
+ | `ICircuitBreakerConfig` | Circuit breaker thresholds |
551
+ | `IHttpRequestOptions` | Options passed to `HttpClient.request()` |
552
+ | `IHttpResponse` | Response shape returned by `HttpClient` |
553
+
554
+ ---
555
+
556
+ ## Advanced Usage
557
+
558
+ ### Adding a route at runtime
559
+
560
+ ```typescript
561
+ gateway.addRoute({
562
+ path: '/notifications',
563
+ method: 'get',
564
+ target: 'http://notification-service:6000',
565
+ serviceName: 'notification-service',
566
+ timeout: 3000,
567
+ retries: 2,
568
+ });
569
+ ```
570
+
571
+ ### Custom load balancer strategy
572
+
573
+ Implement `ILoadBalancerStrategy` to plug in your own logic:
574
+
575
+ ```typescript
576
+ import { ILoadBalancerStrategy } from 'opticore-api-gateway';
577
+ import { IServiceInstance } from 'opticore-api-gateway';
578
+
579
+ export class StickySessionStrategy implements ILoadBalancerStrategy {
580
+ selectInstance(serviceName: string, instances: IServiceInstance[]): IServiceInstance | null {
581
+ // Custom sticky-session logic based on session ID, user ID, etc.
582
+ return instances[0] ?? null;
583
+ }
584
+ }
585
+ ```
586
+
587
+ ### Accessing the gateway context
588
+
589
+ `TGatewayContext` extends Opticore's `ICustomContext` with gateway-specific fields:
590
+
591
+ ```typescript
592
+ type TGatewayContext = ICustomContext & {
593
+ serviceUrl?: string; // Resolved target URL
594
+ serviceInstance?: IServiceInstance; // Selected backend instance
595
+ gatewayStartTime?: number; // Request start timestamp (ms)
596
+ };
597
+ ```
598
+
599
+ ---
600
+
601
+ ## Error Handling
602
+
603
+ | Scenario | HTTP Status | Response Body |
604
+ |----------------------------|-------------|--------------------------------------------------|
605
+ | No healthy instances | `503` | `{ "error": "Service unavailable" }` |
606
+ | Load balancer returns null | `503` | `{ "error": "No healthy instances available" }` |
607
+ | Forwarding / proxy error | `502` | `{ "error": "Bad Gateway", "message": "..." }` |
608
+ | Middleware error | `500` | `{ "error": "Internal Server Error" }` |
609
+ | Route not found | `404` | `{ "error": "Route not found" }` |
610
+ | Missing API key | `401` | `{ "error": "API key required" }` |
611
+ | Invalid API key | `403` | `{ "error": "Invalid API key" }` |
612
+ | Rate limit exceeded | `429` | `{ "error": "Too many requests", "retryAfter": N }` |
613
+
614
+ ---
615
+
616
+ ## License
617
+
618
+ MIT