Skip to content

Commit dee9d8b

Browse files
committed
feat(api-gateway): scaffold gateway foundation and request routing architecture
1 parent b55b48c commit dee9d8b

8 files changed

Lines changed: 210 additions & 0 deletions

File tree

src/gateway/gateway.controller.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {
2+
All,
3+
Body,
4+
Controller,
5+
Headers,
6+
HttpCode,
7+
Param,
8+
Query,
9+
Req,
10+
} from '@nestjs/common';
11+
import { Request } from 'express';
12+
13+
import { GatewayService } from './gateway.service';
14+
15+
@Controller()
16+
export class GatewayController {
17+
constructor(
18+
private readonly gatewayService: GatewayService,
19+
) {}
20+
21+
@All(':service/*path')
22+
@HttpCode(200)
23+
async proxyRequest(
24+
@Param('service') service: string,
25+
@Param('path') path: string,
26+
@Req() request: Request,
27+
@Body() body: unknown,
28+
@Query() query: Record<string, unknown>,
29+
@Headers() headers: Record<string, string>,
30+
) {
31+
return this.gatewayService.forwardRequest({
32+
service,
33+
path,
34+
method: request.method,
35+
body,
36+
query,
37+
headers,
38+
});
39+
}
40+
}

src/gateway/gateway.module.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
3+
import { GatewayController } from './gateway.controller';
4+
import { GatewayService } from './gateway.service';
5+
import { RouterService } from './router/router.service';
6+
import { RouteResolver } from './router/route-resolver';
7+
8+
@Module({
9+
controllers: [GatewayController],
10+
providers: [GatewayService, RouterService, RouteResolver],
11+
exports: [GatewayService, RouterService],
12+
})
13+
export class GatewayModule {}

src/gateway/gateway.service.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {
2+
Injectable,
3+
NotFoundException,
4+
} from '@nestjs/common';
5+
6+
import { RouterService } from './router/router.service';
7+
8+
export interface GatewayRequest {
9+
service: string;
10+
path: string;
11+
method: string;
12+
body?: unknown;
13+
query?: Record<string, unknown>;
14+
headers?: Record<string, string>;
15+
}
16+
17+
@Injectable()
18+
export class GatewayService {
19+
constructor(
20+
private readonly routerService: RouterService,
21+
) {}
22+
23+
async forwardRequest(
24+
request: GatewayRequest,
25+
): Promise<unknown> {
26+
const destination =
27+
this.routerService.resolveService(
28+
request.service,
29+
);
30+
31+
if (!destination) {
32+
throw new NotFoundException(
33+
`Unknown service: ${request.service}`,
34+
);
35+
}
36+
37+
return this.routerService.forward(
38+
destination,
39+
request,
40+
);
41+
}
42+
}

src/gateway/middleware/logging.middleware.ts

Whitespace-only changes.

src/gateway/middleware/rate-limit.middleware.ts

Whitespace-only changes.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
Injectable,
3+
NotFoundException,
4+
} from '@nestjs/common';
5+
6+
import {
7+
SERVICE_REGISTRY,
8+
ServiceDefinition,
9+
} from './service-registry';
10+
11+
@Injectable()
12+
export class RouteResolver {
13+
resolve(
14+
service: string,
15+
): ServiceDefinition {
16+
const definition =
17+
SERVICE_REGISTRY[service];
18+
19+
if (!definition || !definition.enabled) {
20+
throw new NotFoundException(
21+
`Service "${service}" is unavailable.`,
22+
);
23+
}
24+
25+
return definition;
26+
}
27+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Injectable } from '@nestjs/common';
2+
3+
import {
4+
ServiceDefinition,
5+
} from './service-registry';
6+
import { RouteResolver } from './route-resolver';
7+
import {
8+
GatewayRequest,
9+
} from '../gateway.service';
10+
11+
@Injectable()
12+
export class RouterService {
13+
constructor(
14+
private readonly routeResolver: RouteResolver,
15+
) {}
16+
17+
resolveService(
18+
service: string,
19+
): ServiceDefinition {
20+
return this.routeResolver.resolve(
21+
service,
22+
);
23+
}
24+
25+
async forward(
26+
destination: ServiceDefinition,
27+
request: GatewayRequest,
28+
): Promise<unknown> {
29+
/**
30+
* Phase 2 will replace this with:
31+
* - Authentication
32+
* - Correlation IDs
33+
* - Circuit Breaker
34+
* - Request Transformation
35+
* - Native fetch/HTTP forwarding
36+
*/
37+
38+
return {
39+
gateway: true,
40+
service: destination.name,
41+
destination:
42+
destination.baseUrl,
43+
method: request.method,
44+
path: request.path,
45+
query: request.query,
46+
body: request.body,
47+
status: 'Request routing scaffolded',
48+
};
49+
}
50+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export interface ServiceDefinition {
2+
name: string;
3+
baseUrl: string;
4+
timeout: number;
5+
enabled: boolean;
6+
}
7+
8+
export const SERVICE_REGISTRY: Record<
9+
string,
10+
ServiceDefinition
11+
> = {
12+
auth: {
13+
name: 'Authentication Service',
14+
baseUrl:
15+
process.env.AUTH_SERVICE_URL ??
16+
'http://localhost:3001',
17+
timeout: 5000,
18+
enabled: true,
19+
},
20+
21+
quest: {
22+
name: 'Quest Service',
23+
baseUrl:
24+
process.env.QUEST_SERVICE_URL ??
25+
'http://localhost:3002',
26+
timeout: 5000,
27+
enabled: true,
28+
},
29+
30+
analytics: {
31+
name: 'Analytics Service',
32+
baseUrl:
33+
process.env.ANALYTICS_SERVICE_URL ??
34+
'http://localhost:3003',
35+
timeout: 5000,
36+
enabled: true,
37+
},
38+
};

0 commit comments

Comments
 (0)