Back to Technology

API Development Series Part 10: API Architecture Patterns

January 31, 2026 Wasil Zafar 40 min read

Master API architecture patterns including Gateway pattern, Backend for Frontend (BFF), Aggregator, API Composition, microservices API design, and domain-driven API boundaries.

Table of Contents

  1. API Gateway Pattern
  2. Backend for Frontend
  3. Aggregator Pattern
  4. API Composition
  5. Microservices API Design
  6. Domain-Driven Boundaries
Series Navigation: This is Part 10 of the 17-part API Development Series. Review Part 9: GCP Apigee first.

API Gateway Pattern

What is an API Gateway?

An API Gateway is a single entry point for all client requests, handling cross-cutting concerns like authentication, rate limiting, and routing.

API Gateway pattern showing single entry point routing requests to multiple backend microservices
The API Gateway pattern provides a single entry point that handles routing, authentication, rate limiting, and protocol translation for backend services.

Gateway Responsibilities

Cross-Cutting Concerns
Function Description
Request Routing Route /users/* to User Service, /orders/* to Order Service
Authentication Validate JWT tokens before forwarding requests
Rate Limiting Throttle excessive requests per client/IP
Protocol Translation Convert REST to gRPC for internal services
Response Caching Cache responses at edge to reduce backend load
SSL Termination Decrypt HTTPS at gateway, use HTTP internally
// Express.js API Gateway example
const express = require('express');
const httpProxy = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const jwt = require('express-jwt');

const app = express();

// Global rate limiting
const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100,
  standardHeaders: true
});
app.use(limiter);

// JWT authentication middleware
const authenticate = jwt({
  secret: process.env.JWT_SECRET,
  algorithms: ['HS256']
}).unless({ path: ['/health', '/auth/login'] });
app.use(authenticate);

// Route to User Service
app.use('/api/users', httpProxy({
  target: 'http://user-service:3001',
  changeOrigin: true,
  pathRewrite: { '^/api/users': '/users' }
}));

// Route to Order Service  
app.use('/api/orders', httpProxy({
  target: 'http://order-service:3002',
  changeOrigin: true,
  pathRewrite: { '^/api/orders': '/orders' }
}));

// Route to Product Service
app.use('/api/products', httpProxy({
  target: 'http://product-service:3003',
  changeOrigin: true,
  pathRewrite: { '^/api/products': '/products' }
}));

app.listen(3000, () => console.log('Gateway on port 3000'));

Backend for Frontend

BFF Pattern

The Backend for Frontend pattern creates dedicated API layers optimized for specific client types (web, mobile, IoT).

Backend for Frontend pattern with separate API layers for web, mobile, and IoT clients
The BFF pattern creates dedicated backend services tailored to each client type, optimizing data shape and payload size per platform.
When to Use BFF:
  • Different clients need different data shapes
  • Mobile needs minimal payloads, web needs rich data
  • Clients have different authentication flows
  • Teams can work independently per client type
// Mobile BFF - Optimized for minimal bandwidth
// mobile-bff/routes/products.js
const express = require('express');
const router = express.Router();
const productService = require('../services/product');
const imageService = require('../services/image');

// Mobile-optimized product list
router.get('/products', async (req, res) => {
  const products = await productService.getProducts({
    limit: 20,
    fields: ['id', 'name', 'price', 'thumbnailUrl']
  });
  
  // Return compact response for mobile
  res.json({
    items: products.map(p => ({
      id: p.id,
      name: p.name,
      price: p.price,
      thumb: imageService.getOptimizedUrl(p.thumbnailUrl, '150x150')
    })),
    nextCursor: products.nextCursor
  });
});

module.exports = router;
// Web BFF - Rich data for desktop experience
// web-bff/routes/products.js
const express = require('express');
const router = express.Router();
const productService = require('../services/product');
const reviewService = require('../services/review');
const inventoryService = require('../services/inventory');

// Web-optimized product details with aggregation
router.get('/products/:id', async (req, res) => {
  const [product, reviews, inventory] = await Promise.all([
    productService.getProduct(req.params.id),
    reviewService.getReviews(req.params.id, { limit: 10 }),
    inventoryService.getStock(req.params.id)
  ]);
  
  // Rich response for web
  res.json({
    ...product,
    images: product.images.map(img => ({
      thumbnail: img.url + '?w=200',
      medium: img.url + '?w=600',
      large: img.url + '?w=1200'
    })),
    reviews: {
      average: reviews.average,
      count: reviews.total,
      recent: reviews.items
    },
    availability: {
      inStock: inventory.quantity > 0,
      quantity: inventory.quantity,
      warehouses: inventory.locations
    }
  });
});

module.exports = router;

Aggregator Pattern

API Aggregation

The Aggregator pattern combines data from multiple microservices into a single response, reducing client roundtrips.

Aggregator service collecting data from user, order, notification, and recommendation services in parallel
The Aggregator pattern issues parallel requests to multiple microservices and merges their responses into a single unified payload.
// Aggregator Service
const express = require('express');
const axios = require('axios');
const app = express();

// Aggregate user dashboard data
app.get('/dashboard/:userId', async (req, res) => {
  const { userId } = req.params;
  
  try {
    // Parallel requests to multiple services
    const [user, orders, notifications, recommendations] = await Promise.all([
      axios.get(`http://user-service/users/${userId}`),
      axios.get(`http://order-service/users/${userId}/orders?limit=5`),
      axios.get(`http://notification-service/users/${userId}/unread`),
      axios.get(`http://recommendation-service/users/${userId}/products`)
    ]);
    
    // Aggregate into single response
    res.json({
      user: {
        id: user.data.id,
        name: user.data.name,
        avatar: user.data.avatarUrl,
        memberSince: user.data.createdAt
      },
      recentOrders: orders.data.items.map(o => ({
        id: o.id,
        status: o.status,
        total: o.totalAmount,
        date: o.createdAt
      })),
      notifications: {
        unreadCount: notifications.data.count,
        items: notifications.data.items.slice(0, 3)
      },
      recommendations: recommendations.data.products.slice(0, 4)
    });
  } catch (error) {
    // Graceful degradation
    res.json({
      user: null,
      error: 'Some data unavailable',
      partial: true
    });
  }
});

app.listen(3000);

API Composition

Composition vs Aggregation

Pattern Comparison

Design Choice
Aspect Aggregation Composition
Data Merging Combines data from services Orchestrates workflows across services
Use Case Dashboard views, reports Multi-step transactions
Complexity Read-only, parallel Write operations, sequential
// API Composition - Order creation workflow
async function createOrder(userId, cartId, paymentDetails) {
  const saga = new Saga();
  
  try {
    // Step 1: Validate cart
    const cart = await saga.step(
      () => cartService.getCart(cartId),
      () => {} // No compensation needed
    );
    
    // Step 2: Reserve inventory
    const reservation = await saga.step(
      () => inventoryService.reserve(cart.items),
      () => inventoryService.release(reservation.id)
    );
    
    // Step 3: Process payment
    const payment = await saga.step(
      () => paymentService.charge(userId, cart.total, paymentDetails),
      () => paymentService.refund(payment.id)
    );
    
    // Step 4: Create order
    const order = await saga.step(
      () => orderService.create({ userId, cart, payment, reservation }),
      () => orderService.cancel(order.id)
    );
    
    // Step 5: Send confirmation
    await notificationService.sendOrderConfirmation(userId, order);
    
    return { success: true, orderId: order.id };
  } catch (error) {
    await saga.compensate(); // Rollback all steps
    throw error;
  }
}

Microservices API Design

Service Communication Patterns

Sync vs Async Communication:
  • Synchronous (REST/gRPC): Real-time queries, immediate response needed
  • Asynchronous (Events): Decoupled updates, eventual consistency acceptable
Synchronous REST/gRPC vs asynchronous event-driven microservice communication patterns
Microservices communicate via synchronous protocols (REST, gRPC) for real-time queries or asynchronous events for decoupled, eventually consistent updates.
// Event-Driven Communication
const { EventEmitter } = require('events');
const amqp = require('amqplib');

class OrderService {
  constructor(messageBroker) {
    this.messageBroker = messageBroker;
  }
  
  async createOrder(orderData) {
    // Create order in database
    const order = await this.db.orders.create(orderData);
    
    // Publish event (async communication)
    await this.messageBroker.publish('orders', 'order.created', {
      orderId: order.id,
      userId: order.userId,
      items: order.items,
      total: order.total,
      createdAt: new Date().toISOString()
    });
    
    return order;
  }
}

// Inventory Service subscribes to order events
class InventoryService {
  constructor(messageBroker) {
    messageBroker.subscribe('orders', 'order.created', 
      this.handleOrderCreated.bind(this)
    );
  }
  
  async handleOrderCreated(event) {
    const { orderId, items } = event;
    
    for (const item of items) {
      await this.db.inventory.decrement(item.productId, item.quantity);
    }
    
    console.log(`Inventory updated for order ${orderId}`);
  }
}

Domain-Driven Boundaries

Bounded Contexts

Domain-Driven Design helps define clear API boundaries based on business domains.

Domain-Driven Design bounded contexts map for e-commerce showing catalog, ordering, fulfillment, and customer domains
Bounded contexts define clear API ownership boundaries, with each domain maintaining its own entities, APIs, and service implementations.

E-Commerce Bounded Contexts

DDD Example
# Context Map for E-Commerce
contexts:
  catalog:
    entities: [Product, Category, Brand]
    apis: [/products, /categories, /search]
    owns: product-service
    
  ordering:
    entities: [Order, OrderItem, Cart]
    apis: [/orders, /cart]
    owns: order-service
    
  fulfillment:
    entities: [Shipment, Warehouse, Inventory]
    apis: [/shipments, /inventory]
    owns: fulfillment-service
    
  customer:
    entities: [Customer, Address, PaymentMethod]
    apis: [/customers, /addresses]
    owns: customer-service
    
# Relationships
relationships:
  - from: ordering
    to: catalog
    type: customer-supplier  # Catalog provides product data
    
  - from: fulfillment
    to: ordering
    type: conformist  # Fulfillment conforms to order structure
// Anti-Corruption Layer between contexts
class OrderingAntiCorruptionLayer {
  constructor(catalogClient) {
    this.catalogClient = catalogClient;
  }
  
  // Translate Catalog Product to Ordering domain
  async getProductForOrder(productId) {
    const catalogProduct = await this.catalogClient.getProduct(productId);
    
    // Map to Order context's view of Product
    return {
      productId: catalogProduct.id,
      name: catalogProduct.title,
      price: catalogProduct.pricing.currentPrice,
      sku: catalogProduct.inventory.sku,
      available: catalogProduct.inventory.quantity > 0
    };
  }
}

Practice Exercises

Exercise 1: Build API Gateway

Beginner 1 hour
  • Create Express gateway with proxy middleware
  • Add JWT authentication middleware
  • Implement rate limiting per client

Exercise 2: BFF for Mobile & Web

Intermediate 2 hours
  • Create mobile BFF with compact responses
  • Create web BFF with rich aggregations
  • Share service clients between BFFs

Exercise 3: Saga Pattern Implementation

Advanced 3 hours
  • Implement order creation saga
  • Add compensation logic for failures
  • Test partial failure scenarios
Technology