API Gateway Pattern in Production: Kong, AWS, Azure, Spring and NestJS
Why I use a gateway
I have used several API gateway approaches across different projects. I do not believe there is one gateway product that is automatically correct for every system.
In financial-services work, I used Kong where centralized traffic policy, authentication, throttling and auditability were important. In a Lululemon product/location data-management program, I worked with AWS API Gateway as part of the integration boundary. In an RFx/e-auction environment associated with Reliance/Tech Mahindra, I used Azure API Management-style gateway capabilities around procurement workflows. In travel-booking work at Thomas Cook, I used Spring-based gateway/service layers in front of flight, hotel and other booking capabilities. I have also built lighter gateway/BFF layers in Node.js, Express and NestJS.
The architecture details in this article are intentionally generalized. I am describing patterns and decisions from my own work, not publishing confidential client internals.
A gateway gives clients one controlled entry point:
Mobile / Web / Partner
|
v
API Gateway
/ | \
Auth Rate Limit Routing
|
v
-----------------------
| Booking | Payment |
| Vendor | Auction |
| Product | Customer |
-----------------------The gateway is valuable because every backend service should not independently reinvent token validation, quota logic, correlation IDs, TLS policy and public route structure.
But there is an equally important rule: I do not put core business logic into the gateway. When the gateway becomes the place where workflows, database lookups and domain decisions accumulate, it becomes another monolith.
Kong in financial-services style workloads
For a financial-service boundary I normally care about more than routing:
- consumer/client identity;
- OAuth/JWT scopes;
- per-client quotas;
- burst control;
- IP/network restrictions;
- correlation identifiers;
- audit metadata;
- safe request-size limits.
A small Kong declarative example:
_format_version: "3.0"
services:
- name: payment-service
url: http://payment-service:8080
routes:
- name: payment-api
paths:
- /api/payments
plugins:
- name: correlation-id
service: payment-service
config:
header_name: X-Request-ID
generator: uuid
- name: rate-limiting
service: payment-service
config:
minute: 120
policy: localFor sensitive operations I still enforce authorization inside the business service. The gateway can establish identity; the service must decide whether that identity can perform a particular business action.
AWS API Gateway in enterprise integration
In product/location master-data integration, the gateway can front stable enterprise APIs while backend services and event flows evolve independently.
Enterprise / Partner Systems
|
v
AWS API Gateway
|
VPC / private link
|
Domain Services
|
Kafka
|
Downstream consumersA simplified OpenAPI integration:
paths:
/products/{id}:
get:
responses:
"200":
description: Product
x-amazon-apigateway-integration:
type: http_proxy
httpMethod: GET
uri: "http://product-service.internal/products/{id}"
connectionType: VPC_LINK
connectionId: "${stageVariables.vpcLinkId}"
timeoutInMillis: 2900I keep the gateway timeout aligned with the complete request budget. If the client SLA is three seconds, allowing a downstream integration to wait for twenty seconds is already an architecture defect.
Azure API Management for RFx / e-auction
Auction and procurement systems have unusual traffic characteristics: bid windows, bursts near close time, supplier identity, duplicate submissions and audit requirements.
A conceptual route is:
Supplier / Buyer
|
v
Azure API Management
|
--------------
| RFx API |
| Bid API |
| Vendor API |
| Audit API |
--------------The gateway can throttle or authenticate, but duplicate bid protection belongs in the application through an idempotency/business-operation key.
A lightweight NestJS gateway
Sometimes a managed or self-hosted gateway product is unnecessary. A small NestJS edge/BFF can be enough.
@Injectable()
export class GatewayProxyService {
constructor(private readonly http: HttpService) {}
async forward<T>(
targetUrl: string,
requestId: string,
payload: unknown,
): Promise<T> {
const result = await firstValueFrom(
this.http.post<T>(targetUrl, payload, {
timeout: 2500,
headers: { 'x-request-id': requestId },
}).pipe(
retry({
count: 2,
delay: (error, retryCount) => {
if (!isTransient(error)) {
throw error;
}
const base = 100 * 2 ** (retryCount - 1);
const jitter = Math.floor(Math.random() * 80);
return timer(base + jitter);
},
}),
),
);
return result.data;
}
}I would not copy this retry policy blindly. The operation must be safe to retry and the entire attempt sequence must remain inside the caller's deadline.
The failure case I care about most: retry multiplication
Imagine:
Client retries 2 times
Gateway retries 2 times
Service SDK retries 3 timesOne user request can create many downstream attempts. During an outage that is exactly when the failing system least needs extra traffic.
I therefore assign one retry owner for each dependency call.
Gateway observability
At minimum I want:
request_id
consumer/client
route
HTTP status
upstream status
gateway latency
upstream latency
rate-limit rejection
timeout count
request size
response sizeGateway latency and upstream latency must be separate. Otherwise an operator cannot tell whether the gateway itself is slow or the backend is slow.
When I would not use a heavyweight API gateway
For one small internal service, Kubernetes Ingress or a simple reverse proxy may be enough.
For a frontend-specific aggregation layer, a BFF may be better.
For internal service-to-service traffic, I normally do not route every request back through the public edge gateway.
Production checklist
- Keep the gateway stateless where possible.
- Run multiple replicas or use a managed HA gateway.
- Bound connect and request timeouts.
- Define exactly where retries happen.
- Propagate trace and correlation IDs.
- Apply rate limits by the correct consumer identity.
- Keep domain authorization in owning services.
- Load-test burst behavior.
- Monitor upstream latency separately.
- Test the gateway itself as a failure domain.
FAQ
Is Kong better than AWS API Gateway?
Not universally. Kong gives strong self-hosted/plugin flexibility. AWS API Gateway removes gateway-cluster operations and integrates deeply with AWS. I choose based on platform, policy, traffic and operating model.
Should internal services call each other through the edge gateway?
Usually no. Platform-native service discovery or internal routing is normally better.
Should authentication live only in the gateway?
The gateway can validate identity/token shape. Business authorization still belongs close to the protected business capability.
Can Spring Cloud Gateway replace Kong?
For many application-owned gateway scenarios, yes. But Kong and managed gateways provide operational and policy ecosystems that may be useful when many teams/APIs share the boundary.
Related architecture guides
- [Microservices Design Patterns I Have Used in Real Enterprise Projects](/articles/microservices-design-patterns-real-enterprise-projects)
- [Service Discovery in Microservices](/articles/service-discovery-kubernetes-dns-consul-cloud)
-
Idempotency in Payments, Bookings and Bids
Architect's final take
An API Gateway is a boundary, not the architecture itself. I select the gateway technology after I understand the trust boundary, traffic policy, cloud platform and failure behavior.
Your feedback helps prioritize deeper technical content.






