
Commerce Api Gateway
- 56 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Aggregate multiple commerce microservices behind a single API gateway using GraphQL federation, rate limiting, and unified authentication.
About
A skill for building a commerce API gateway that fronts catalog, cart, search, and CMS services with GraphQL federation and cross-cutting concerns. A developer uses it in composable commerce architectures to give the storefront a single entry point.
- Apollo Router GraphQL federation and REST aggregation BFF
- Gateway-layer auth, rate limiting, caching, and observability
Commerce Api Gateway by the numbers
- 56 all-time installs (skills.sh)
- Ranked #3,200 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill commerce-api-gatewayAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 56 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Aggregate multiple commerce microservices behind a single API gateway using GraphQL federation, rate limiting, and unified authentication.
Files
Commerce API Gateway
Overview
An API gateway sits between storefront clients and the set of backend commerce services, providing a single entry point for authentication, rate limiting, caching, and request routing. In composable commerce architectures, the gateway aggregates APIs from disparate services (catalog, cart, search, CMS) so the frontend makes one or a few calls rather than dozens. This skill covers building a GraphQL Federation gateway with Apollo Router, a REST aggregation BFF, and applying cross-cutting concerns (auth, rate limiting, observability) at the gateway layer.
When to Use This Skill
- When your storefront makes 10+ API calls per page load from different services
- When you need to enforce authentication and authorization consistently across all commerce APIs
- When different teams own different services and you need a contract between the frontend and backend
- When you want to apply rate limiting, circuit breakers, or caching without modifying each service
- When you need a single GraphQL schema that spans catalog, inventory, CMS, and personalization data
Prerequisites & Platform Notes
This skill is written for custom/headless storefronts (Node.js, Python, or similar backend). The code examples use TypeScript/Node.js and can be adapted to any stack.
Shopify: Shopify Hydrogen is Shopify's headless framework. MACH/composable patterns apply when using Shopify as the commerce backend with a custom frontend, or when mixing Shopify with other best-of-breed services. WooCommerce: WooCommerce can serve as a headless backend via its REST API and WPGraphQL. These patterns apply when decoupling the frontend from WordPress. Magento: Magento's GraphQL API and PWA Studio support headless architectures. These composable patterns apply to Magento as a backend service in a MACH stack.
You'll need:
- Node.js 18+ (or adapt to your backend language)
- Redis for caching/queues
Core Instructions
1. Set up Apollo Router for GraphQL Federation
Apollo Federation composes multiple GraphQL subgraphs into a unified supergraph. Each service owns a slice of the schema.
# Install Apollo Router (the high-performance Rust gateway)
curl -sSL https://router.apollo.dev/download/nix/latest | sh
# Install Rover CLI for schema management
npm install -g @apollo/roverDefine the supergraph config:
# supergraph.yaml
federation_version: =2.5.0
subgraphs:
catalog:
routing_url: http://catalog-service:4001/graphql
schema:
subgraph_url: http://catalog-service:4001/graphql
inventory:
routing_url: http://inventory-service:4002/graphql
schema:
subgraph_url: http://inventory-service:4002/graphql
cart:
routing_url: http://cart-service:4003/graphql
schema:
subgraph_url: http://cart-service:4003/graphqlCompose and run:
rover supergraph compose --config supergraph.yaml > supergraph.graphql
./router --supergraph supergraph.graphql --config router.yaml2. Define federated subgraph schemas
Each service defines its slice of the schema and can extend types owned by other services:
# catalog-service/schema.graphql
type Query {
product(id: ID!): Product
products(first: Int, after: String): ProductConnection
}
type Product @key(fields: "id") {
id: ID!
name: String!
slug: String!
description: String
price: Money!
images: [Image!]!
}
# inventory-service/schema.graphql — extends Product from catalog
type Product @key(fields: "id") @extends {
id: ID! @external
inventory: InventoryStatus!
}
type InventoryStatus {
available: Boolean!
quantity: Int
warehouseLocations: [String!]!
}The gateway resolves the Product.inventory field by calling the inventory service with the product IDs gathered from the catalog response — automatically, without any client-side orchestration.
3. Build a REST BFF (Backend-for-Frontend) with Fastify
For storefronts that prefer REST over GraphQL:
import Fastify from 'fastify';
import {catalogClient} from './services/catalog';
import {inventoryClient} from './services/inventory';
import {cmsClient} from './services/cms';
const app = Fastify({logger: true});
// Composite endpoint for Product Detail Page
app.get<{Params: {id: string}}>('/api/pdp/:id', async (request, reply) => {
const {id} = request.params;
const customerId = request.headers['x-customer-id'] as string | undefined;
const [product, inventory, content] = await Promise.allSettled([
catalogClient.getProduct(id),
inventoryClient.getStock(id),
cmsClient.getProductContent(id),
]);
if (product.status === 'rejected') {
return reply.status(404).send({error: 'Product not found'});
}
return {
product: product.value,
inventory: inventory.status === 'fulfilled' ? inventory.value : {available: true, quantity: null},
content: content.status === 'fulfilled' ? content.value : null,
};
});
// Composite endpoint for Cart Page
app.get<{Params: {cartId: string}}>('/api/cart/:cartId', {
preHandler: [requireAuth],
}, async (request, reply) => {
const cart = await cartClient.getCart(request.params.cartId);
const productIds = cart.lines.map((l: any) => l.productId);
const inventoryMap = await inventoryClient.getBulkStock(productIds);
return {
...cart,
lines: cart.lines.map((line: any) => ({
...line,
inventory: inventoryMap[line.productId] ?? {available: true},
})),
};
});
await app.listen({port: 3000, host: '0.0.0.0'});4. Apply authentication at the gateway
The gateway validates JWTs and forwards the decoded identity to subgraphs:
// middleware/auth.ts
import {FastifyRequest, FastifyReply} from 'fastify';
import {verify, JwtPayload} from 'jsonwebtoken';
export async function requireAuth(request: FastifyRequest, reply: FastifyReply) {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) return reply.status(401).send({error: 'Authentication required'});
try {
const payload = verify(token, process.env.JWT_PUBLIC_KEY!, {algorithms: ['RS256']}) as JwtPayload;
request.user = {id: payload.sub!, email: payload.email, roles: payload.roles ?? []};
} catch {
return reply.status(401).send({error: 'Invalid token'});
}
}
// Apollo Router auth via coprocessor (Rust plugin alternative)
// router.yaml # router.yaml
authentication:
router:
jwt:
jwks:
- url: https://your-auth-provider/.well-known/jwks.json
authorization:
require_authentication: false # Allow public queries; subgraphs enforce per-field auth5. Implement rate limiting and response caching
// Rate limiting with Redis token bucket
import {RateLimiterRedis} from 'rate-limiter-flexible';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
const rateLimiter = new RateLimiterRedis({
storeClient: redis,
keyPrefix: 'rl_gateway',
points: 100, // 100 requests
duration: 60, // per 60 seconds
blockDuration: 60, // block for 60s when exceeded
});
app.addHook('preHandler', async (request, reply) => {
const key = request.user?.id ?? request.ip;
try {
await rateLimiter.consume(key);
} catch {
reply.header('Retry-After', '60');
return reply.status(429).send({error: 'Too many requests'});
}
});
// Response caching with stale-while-revalidate
import {fastifyCaching} from '@fastify/caching';
app.register(fastifyCaching, {privacy: fastifyCaching.privacy.PUBLIC, expiresIn: 60});6. Add distributed tracing with OpenTelemetry
// tracing.ts — initialize before importing app modules
import {NodeSDK} from '@opentelemetry/sdk-node';
import {OTLPTraceExporter} from '@opentelemetry/exporter-trace-otlp-http';
import {HttpInstrumentation} from '@opentelemetry/instrumentation-http';
const sdk = new NodeSDK({
serviceName: 'commerce-gateway',
traceExporter: new OTLPTraceExporter({url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT}),
instrumentations: [new HttpInstrumentation()],
});
sdk.start();
// Propagate trace context to downstream services
// OpenTelemetry auto-injects W3C traceparent headers into all outgoing HTTP requestsExamples
Apollo Router configuration with caching and CORS
# router.yaml
server:
listen: 0.0.0.0:4000
cors:
origins:
- https://www.mystore.com
- https://staging.mystore.com
supergraph:
listen: 0.0.0.0:4000
traffic_shaping:
router:
timeout: 30s
all:
timeout: 10s
retry:
enabled: true
min_per_sec: 10
ttl: 10s
retry_on_http_statuses: [500, 502, 503]
telemetry:
tracing:
otlp:
endpoint: http://otel-collector:4317
protocol: grpcSchema stitching for legacy REST services
import {wrapSchema, RenameTypes, FilterTypes} from '@graphql-tools/wrap';
import {fetch} from 'undici';
// Wrap a legacy REST API as a virtual GraphQL subgraph
const legacyProductSchema = buildSchema(`
type Query {
legacyProduct(sku: String!): LegacyProduct
}
type LegacyProduct {
sku: String!
name: String!
wholesalePrice: Float!
}
`);
const legacyProductSubschema = {
schema: legacyProductSchema,
executor: async ({document, variables}: any) => {
const sku = variables.sku;
const res = await fetch(`${process.env.LEGACY_API_URL}/products/${sku}`);
const product = await res.json();
return {data: {legacyProduct: product}};
},
};Best Practices
- Keep the gateway thin — the gateway handles cross-cutting concerns (auth, rate limiting, caching, tracing); business logic belongs in the services
- Use Apollo Federation for GraphQL — schema stitching works but federation is the standard; it enforces clear ownership and enables schema registry checks in CI
- Set per-service timeouts — a slow inventory service should not hold up a product page; set independent timeouts for each subgraph and use
@deferfor non-critical data - Cache at the gateway, not the client — use
Cache-Controland Fastify/Apollo caching plugins to serve repeated queries from memory; this reduces load on all downstream services - Version the gateway API, not the subgraphs — expose
/api/v1and/api/v2prefixes at the gateway level; individual subgraph schemas evolve independently under federation - Use health check endpoints for each subgraph — configure the router to poll
/healthon each service and remove unhealthy subgraphs from routing automatically - Monitor gateway latency as a system-level SLO — the gateway p99 latency is the ceiling on every user interaction; alert when it exceeds your page performance budget
Common Pitfalls
| Problem | Solution |
|---|---|
| N+1 queries when resolving entity references | Use Apollo Federation's @key and batch-resolving (DataLoader pattern) to fetch all referenced entities in one call per service |
| Auth token not forwarded to subgraphs | Configure Apollo Router's headers.all propagation to forward Authorization and x-customer-id headers to all subgraphs |
| Gateway becomes a bottleneck under load | Deploy Apollo Router as a horizontally scaled stateless service behind a load balancer; it has a Rust core and handles thousands of RPS per instance |
| Subgraph schema conflict blocks deployment | Use rover subgraph check in CI to validate schema changes against the registry before merging |
| Cross-origin requests blocked from the SPA | Set CORS origins explicitly to your storefront domains; never use wildcard * on an authenticated gateway |
Related Skills
- @composable-commerce
- @webhook-architecture
- @monitoring-alerting-commerce
- @edge-commerce
- @load-testing-commerce
{
"context": "Tests whether the agent uses Apollo Router with Rover CLI for federation setup, writes correct federated schemas with @key/@extends/@external, configures JWKS auth, sets explicit CORS origins (no wildcard), applies correct traffic shaping timeouts and retries, and configures header propagation for subgraphs.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Rover CLI install command",
"max_score": 5,
"description": "SETUP.md or documentation includes `npm install -g @apollo/rover` to install the Rover CLI"
},
{
"name": "Apollo Router install command",
"max_score": 5,
"description": "SETUP.md or documentation includes the curl-based install command for Apollo Router (referencing router.apollo.dev)"
},
{
"name": "federation_version in supergraph.yaml",
"max_score": 10,
"description": "supergraph.yaml specifies federation_version as =2.5.0 (with the = prefix, not just 2.5.0)"
},
{
"name": "@key directive on Product",
"max_score": 10,
"description": "The catalog subgraph schema defines `type Product @key(fields: \"id\")` (entity key directive present)"
},
{
"name": "@extends on inventory Product",
"max_score": 8,
"description": "The inventory subgraph schema uses `type Product @key(fields: \"id\") @extends` to extend the Product type from catalog"
},
{
"name": "@external on id field",
"max_score": 8,
"description": "The inventory subgraph marks the `id` field as `@external` since it is owned by the catalog subgraph"
},
{
"name": "JWKS auth configuration",
"max_score": 8,
"description": "router.yaml configures authentication.router.jwt.jwks with a URL pointing to a /.well-known/jwks.json endpoint"
},
{
"name": "require_authentication: false",
"max_score": 7,
"description": "router.yaml sets authorization.require_authentication: false to allow public queries"
},
{
"name": "No wildcard CORS",
"max_score": 8,
"description": "router.yaml CORS configuration does NOT use wildcard '*'; instead lists specific origin URLs"
},
{
"name": "Router timeout 30s",
"max_score": 6,
"description": "traffic_shaping.router.timeout is set to 30s in router.yaml"
},
{
"name": "Subgraph timeout 10s",
"max_score": 6,
"description": "traffic_shaping.all.timeout is set to 10s in router.yaml"
},
{
"name": "Retry on 5xx statuses",
"max_score": 7,
"description": "traffic_shaping retries are enabled with retry_on_http_statuses including [500, 502, 503]"
},
{
"name": "Header propagation config",
"max_score": 8,
"description": "router.yaml includes headers configuration to propagate Authorization and/or x-customer-id to all subgraphs"
},
{
"name": "Rover compose command",
"max_score": 4,
"description": "SETUP.md or documentation includes the `rover supergraph compose --config supergraph.yaml` command to build the supergraph"
}
]
}
GraphQL Federation Gateway: Schema Design and Router Configuration
Problem/Feature Description
Meridian Commerce is rebuilding their platform around a composable architecture. They have three backend teams: the Catalog team owns products and categories, the Inventory team owns stock levels and warehouse locations, and the Cart team owns shopping carts and line items. Each team wants to own their GraphQL schema independently, but the storefront needs a single unified GraphQL endpoint so it can query products, inventory, and cart data in one request.
The platform team has been asked to set up a federated GraphQL gateway using industry-standard tooling. They need: (1) a supergraph composition configuration that registers all three subgraphs, (2) subgraph schema files for each service showing how the Product type is shared across services — the Catalog team defines it, the Inventory team extends it with stock data, (3) a production-ready router configuration with proper authentication via JWKS (the company uses Auth0), explicit CORS allowed origins for the storefront (https://shop.meridian.com and https://staging.meridian.com), traffic shaping with independent timeouts, and retry logic.
The gateway should also be configured to propagate the customer's identity headers downstream so subgraphs can perform per-field authorization.
Output Specification
Produce the following configuration and schema files in a gateway/ directory:
gateway/supergraph.yaml— supergraph composition config listing all three subgraphsgateway/router.yaml— Apollo Router configuration (auth, CORS, traffic shaping, header propagation)gateway/subgraphs/catalog/schema.graphql— catalog subgraph schema defining the Product typegateway/subgraphs/inventory/schema.graphql— inventory subgraph schema extending Product with inventory datagateway/subgraphs/cart/schema.graphql— cart subgraph schema
Also produce gateway/SETUP.md documenting the commands needed to: install the required CLI tools, compose the supergraph schema, and start the router.
{
"context": "Tests whether the agent instruments tracing with the correct OpenTelemetry packages and service name, initializes tracing before other imports, uses DataLoader to solve N+1 queries, versions the gateway API at the route prefix level (not subgraphs), and wraps a legacy REST API using @graphql-tools/wrap.",
"type": "weighted_checklist",
"checklist": [
{
"name": "@opentelemetry/sdk-node package",
"max_score": 8,
"description": "tracing.ts imports NodeSDK from '@opentelemetry/sdk-node' (not a different OTel package or SDK)"
},
{
"name": "OTLP HTTP exporter",
"max_score": 8,
"description": "tracing.ts imports OTLPTraceExporter from '@opentelemetry/exporter-trace-otlp-http' (not grpc or another exporter)"
},
{
"name": "HttpInstrumentation",
"max_score": 7,
"description": "tracing.ts includes HttpInstrumentation from '@opentelemetry/instrumentation-http' in the instrumentations array"
},
{
"name": "serviceName commerce-gateway",
"max_score": 8,
"description": "NodeSDK is configured with serviceName: 'commerce-gateway'"
},
{
"name": "Tracing initialized first",
"max_score": 8,
"description": "DECISIONS.md or code comments/structure indicate that tracing.ts must be imported/initialized before other application modules"
},
{
"name": "DataLoader for inventory batching",
"max_score": 10,
"description": "inventory-resolver.ts uses DataLoader (from the 'dataloader' package or similar) to batch multiple product ID lookups into a single API call"
},
{
"name": "No per-item inventory calls",
"max_score": 8,
"description": "inventory-resolver.ts does NOT fetch inventory one product at a time (no loop with individual awaits per product ID)"
},
{
"name": "API versioning at gateway prefixes",
"max_score": 9,
"description": "server.ts or DECISIONS.md shows versioned routes using path prefixes (e.g. /api/v1/... and /api/v2/...) at the gateway level, not via separate subgraph schemas"
},
{
"name": "@graphql-tools/wrap for legacy service",
"max_score": 10,
"description": "executor.ts or pricing schema setup imports wrapSchema from '@graphql-tools/wrap' to wrap the legacy REST API"
},
{
"name": "OTEL_EXPORTER_OTLP_ENDPOINT env var",
"max_score": 7,
"description": "tracing.ts reads the OTLP endpoint from the OTEL_EXPORTER_OTLP_ENDPOINT environment variable (not hardcoded)"
},
{
"name": "Dependencies in package.json",
"max_score": 7,
"description": "package.json lists @opentelemetry/sdk-node, @opentelemetry/exporter-trace-otlp-http, and @opentelemetry/instrumentation-http as dependencies"
},
{
"name": "sdk.start() invoked",
"max_score": 5,
"description": "tracing.ts calls sdk.start() after creating the NodeSDK instance to activate the instrumentation"
},
{
"name": "Batch resolver receives ID array",
"max_score": 5,
"description": "The DataLoader batch function in inventory-resolver.ts accepts an array of IDs and makes a single bulk/batch API call for all of them"
}
]
}
Commerce Gateway: Distributed Tracing, Performance, and Versioning
Problem/Feature Description
TradeFlow, a B2B commerce platform, has a working API gateway but it is struggling in production: the platform team is getting reports of slow pages, mysterious timeouts, and a critical performance bug — when the storefront fetches a product list with 50 items, the gateway is making 50 individual inventory API calls (one per product). Engineering has also received a complaint from a mobile app team: they added distributed tracing to their service but the gateway is not propagating trace context, making it impossible to see end-to-end traces. Finally, the commerce team is about to release a breaking change to the product API and needs a versioning strategy so the mobile app on the old API version is not disrupted.
A second initiative: the platform has acquired a legacy pricing service that only exposes a REST API. The team wants to include its data in the new unified GraphQL schema without rewriting the legacy system.
The gateway is built in TypeScript. Your task is to address these four concerns: add distributed tracing instrumentation, fix the N+1 query problem in the inventory subgraph, design an API versioning approach for the REST endpoints, and wrap the legacy pricing REST API as a GraphQL subgraph.
Output Specification
Produce a gateway/ directory with the following files:
gateway/src/tracing.ts— OpenTelemetry setup for the gateway servicegateway/src/resolvers/inventory-resolver.ts— inventory entity resolver that solves the N+1 performance problem (stub the actual upstream API call)gateway/src/subgraphs/pricing/schema.graphql— GraphQL schema for the legacy pricing subgraphgateway/src/subgraphs/pricing/executor.ts— executor that integrates the legacy pricing REST API as a virtual GraphQL subgraphgateway/src/server.ts— gateway server showing how the REST API versioning strategy is implemented in routinggateway/package.json— with all required dependenciesgateway/DECISIONS.md— documenting: how tracing is initialized relative to other imports, which observability packages are used and the service name chosen, how the N+1 problem is solved, how API versioning is handled at the gateway layer, and which approach integrates the legacy REST API into GraphQL
The code does not need to connect to real services. Use stubs/mocks for the actual upstream HTTP calls. The code should be syntactically correct TypeScript.
{
"context": "Tests whether the agent builds a REST BFF using Fastify with correct resilience patterns: Promise.allSettled for parallel calls, graceful fallbacks for non-critical services, JWT RS256 auth on protected routes, RateLimiterRedis with correct config, and @fastify/caching for response caching.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Fastify framework",
"max_score": 8,
"description": "Uses Fastify as the HTTP framework (imports from 'fastify', not express, hapi, koa, or other frameworks)"
},
{
"name": "Promise.allSettled for parallel calls",
"max_score": 10,
"description": "Uses Promise.allSettled (not Promise.all or sequential awaits) to call multiple upstream services in parallel on the PDP endpoint"
},
{
"name": "Graceful inventory fallback",
"max_score": 8,
"description": "When inventory service fails/rejects, returns a fallback object with `available: true` and `quantity: null` instead of erroring"
},
{
"name": "Graceful CMS fallback",
"max_score": 7,
"description": "When CMS/content service fails/rejects, returns null (not an error) for the content field"
},
{
"name": "Auth on cart route only",
"max_score": 8,
"description": "Authentication middleware is applied via preHandler on the cart route (not globally to all routes, not missing from cart)"
},
{
"name": "RS256 JWT algorithm",
"max_score": 8,
"description": "JWT verification specifies RS256 algorithm (e.g. `algorithms: ['RS256']` in the verify options), not HS256 or omitted"
},
{
"name": "RateLimiterRedis package",
"max_score": 9,
"description": "Rate limiting uses RateLimiterRedis from the 'rate-limiter-flexible' package (not express-rate-limit, fastify-rate-limit, or custom logic)"
},
{
"name": "Rate limiter config values",
"max_score": 9,
"description": "RateLimiterRedis is configured with keyPrefix 'rl_gateway', points: 100, duration: 60, and blockDuration: 60"
},
{
"name": "Rate limit key strategy",
"max_score": 8,
"description": "Rate limit key uses `request.user?.id ?? request.ip` (user ID for authenticated requests, IP for anonymous)"
},
{
"name": "Retry-After header on 429",
"max_score": 8,
"description": "When rate limit is exceeded, sets `Retry-After: 60` header before responding with 429"
},
{
"name": "@fastify/caching plugin",
"max_score": 8,
"description": "Uses @fastify/caching plugin (not a custom cache or different caching library) registered via app.register()"
},
{
"name": "Caching config: PUBLIC privacy",
"max_score": 7,
"description": "@fastify/caching is configured with privacy set to PUBLIC (fastifyCaching.privacy.PUBLIC or equivalent string)"
},
{
"name": "Fastify logger enabled",
"max_score": 2,
"description": "Fastify is instantiated with logger: true (not false or omitted)"
}
]
}
Commerce BFF: Product Detail and Cart Aggregation Service
Problem/Feature Description
StyleHub, a mid-sized fashion retailer, is migrating from a monolithic storefront to a composable architecture. Their new backend consists of three independent microservices: a catalog service (product data), an inventory service (stock levels), and a CMS service (rich product descriptions and media). The existing storefront was making 8–12 API calls per page load and engineering has decided to introduce a Backend-for-Frontend layer to consolidate these into single-endpoint responses.
The engineering team wants a TypeScript BFF that handles two critical pages: the Product Detail Page (PDP) and the Cart Page. The PDP aggregates data from all three services — the CMS and inventory are considered "enrichment" data, meaning the page can still render if they are temporarily unavailable. The Cart Page is account-sensitive and must only be accessible to authenticated customers. The team also wants to protect the service from abuse with per-user and per-IP rate limiting backed by Redis, and to cache public responses to reduce load on the microservices. The BFF must log all requests.
Output Specification
Produce a TypeScript implementation of the BFF. The deliverable should be a set of source files in a bff/ directory. Your implementation should include:
bff/src/server.ts— the main server application with both/api/pdp/:idand/api/cart/:cartIdroutesbff/src/middleware/auth.ts— the JWT authentication middlewarebff/src/tracing.tsor similar if you add observability (optional)bff/package.json— with all required dependencies listed
The actual microservice clients do not need to be implemented (stub them or use placeholder functions). Focus on the BFF structure, middleware wiring, and resilience patterns. Include inline comments explaining any key decisions.
Do not implement a working Redis connection or live JWT verification — use mock/placeholder values where environment variables would normally be required. The code should be syntactically correct and runnable with npm install.
Also produce a bff/DECISIONS.md file documenting: which HTTP framework was chosen and why, how parallel upstream calls are handled, the rate limiting approach and key selection strategy, and the caching strategy.
{
"name": "finsi/commerce-api-gateway",
"version": "0.1.0",
"summary": "API gateway patterns for aggregating multiple commerce microservices",
"skills": {
"commerce-api-gateway": {
"path": "SKILL.md"
}
}
}