commerce-api-gateway
Aggregate multiple commerce microservices behind a single API gateway with GraphQL federation, rate limiting, and unified authentication
What this skill does
# 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.
```bash
# 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/rover
```
Define the supergraph config:
```yaml
# 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/graphql
```
Compose and run:
```bash
rover supergraph compose --config supergraph.yaml > supergraph.graphql
./router --supergraph supergraph.graphql --config router.yaml
```
2. **Define federated subgraph schemas**
Each service defines its slice of the schema and can extend types owned by other services:
```graphql
# 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:
```typescript
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:
```typescript
// 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
```
```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 auth
```
5. **Implement rate limiting and response caching**
```typescript
// 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.PUBLRelated in headless-modern
medusa-development
IncludedExtend the open-source Medusa commerce platform with custom services, event subscribers, and API endpoints for unique business requirements
pwa-storefront
IncludedTurn your store into an installable Progressive Web App with offline product browsing, push notifications, and home screen access for mobile shoppers
commerce-js-integration
IncludedBuild a lightweight headless store using the Commerce.js SDK for product display, cart management, and checkout without a heavy backend
composable-commerce
IncludedArchitect a modern store using MACH principles — independent microservices, API-first integrations, cloud-native hosting, and headless frontend
saleor-development
IncludedBuild and extend Saleor's GraphQL-based headless commerce platform with custom apps, webhook handlers, and dashboard UI customizations
shopify-hydrogen
IncludedBuild a custom Shopify storefront using the Hydrogen React framework with Remix routing and deploy it to Shopify's Oxygen edge hosting