engineering docs

Microservices - Implementation Guide

Version: 1.0 Date: August 4, 2026 Purpose: Step-by-step implementation instructions and code templates


Table of Contents

  1. Phase 1: Setup & Foundation
  2. Phase 2: Micro-Frontend Extraction
  3. Phase 3: Core Services
  4. Phase 4: Business Services
  5. Phase 5: Final Services
  6. Code Templates
  7. Testing Strategies
  8. Deployment Guides

Phase 1: Setup & Foundation

Step 1.1: Directory Structure

Create the infrastructure directory structure:

mkdir -p services/{api-gateway,auth,user,org,product,order,analytics,settings,notify}
mkdir -p micro-frontends/{shell,auth,user,org,product,order,analytics,admin}
mkdir -p infrastructure/{docker,kubernetes,github,terraform}
mkdir -p shared/{http-client,validators,constants,types,utils}

Step 1.2: Create Base Dockerfile

File: services/Dockerfile.base

FROM node:20-alpine
 
WORKDIR /app
 
# Install dependencies
COPY package*.json bun.lock* ./
RUN npm install -g bun && bun install --frozen-lockfile
 
# Copy source
COPY . .
 
# Build
RUN bun run build
 
# Expose
EXPOSE 3000
 
# Start
CMD ["bun", "start"]

Step 1.3: Create API Gateway Service

File: services/api-gateway/src/index.ts

import express, { Express, Request, Response } from 'express';
import { createProxyMiddleware } from 'express-http-proxy';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import jwt from 'jsonwebtoken';
 
const app: Express = express();
const PORT = process.env.PORT || 3000;
 
// ─────────────────────────────────────────────────────────────
// Middleware Setup
// ─────────────────────────────────────────────────────────────
 
app.use(
  cors({
    origin: process.env.CORS_ORIGIN || ['http://localhost:3100', 'http://localhost:3101'],
    credentials: true
  })
);
 
app.use(express.json());
 
// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // 100 requests per windowMs
});
app.use(limiter);
 
// ─────────────────────────────────────────────────────────────
// Auth Middleware
// ─────────────────────────────────────────────────────────────
 
interface AuthRequest extends Request {
  user?: {
    id: string;
    email: string;
    role: string;
  };
}
 
const authMiddleware = (req: AuthRequest, res: Response, next: Function) => {
  const token = req.headers.authorization?.split(' ')[1];
 
  if (!token) {
    return next(); // Public endpoint
  }
 
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret');
    req.user = decoded as any;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
};
 
app.use(authMiddleware);
 
// ─────────────────────────────────────────────────────────────
// Service Routes (Proxies)
// ─────────────────────────────────────────────────────────────
 
// Auth Service
app.use(
  '/api/auth',
  createProxyMiddleware({
    target: process.env.AUTH_SERVICE_URL || 'http://localhost:3001',
    changeOrigin: true,
    pathRewrite: {
      '^/api/auth': ''
    },
    onError: (err, req, res) => {
      console.error('Auth Service Error:', err);
      res.status(503).json({ error: 'Auth service unavailable' });
    },
    // Forward auth header to service
    onProxyReq: (proxyReq, req: AuthRequest) => {
      if (req.headers.authorization) {
        proxyReq.setHeader('Authorization', req.headers.authorization);
      }
    }
  })
);
 
// User Service
app.use(
  '/api/users',
  createProxyMiddleware({
    target: process.env.USER_SERVICE_URL || 'http://localhost:3002',
    changeOrigin: true,
    pathRewrite: {
      '^/api/users': ''
    },
    onError: (err, req, res) => {
      console.error('User Service Error:', err);
      res.status(503).json({ error: 'User service unavailable' });
    },
    onProxyReq: (proxyReq, req: AuthRequest) => {
      if (req.headers.authorization) {
        proxyReq.setHeader('Authorization', req.headers.authorization);
      }
    }
  })
);
 
// Organization Service
app.use(
  '/api/orgs',
  createProxyMiddleware({
    target: process.env.ORG_SERVICE_URL || 'http://localhost:3003',
    changeOrigin: true,
    pathRewrite: {
      '^/api/orgs': ''
    },
    onProxyReq: (proxyReq, req: AuthRequest) => {
      if (req.headers.authorization) {
        proxyReq.setHeader('Authorization', req.headers.authorization);
      }
    }
  })
);
 
// Product Service
app.use(
  '/api/products',
  createProxyMiddleware({
    target: process.env.PRODUCT_SERVICE_URL || 'http://localhost:3004',
    changeOrigin: true,
    pathRewrite: {
      '^/api/products': ''
    }
  })
);
 
// Order Service
app.use(
  '/api/orders',
  createProxyMiddleware({
    target: process.env.ORDER_SERVICE_URL || 'http://localhost:3005',
    changeOrigin: true,
    pathRewrite: {
      '^/api/orders': ''
    },
    onProxyReq: (proxyReq, req: AuthRequest) => {
      if (req.headers.authorization) {
        proxyReq.setHeader('Authorization', req.headers.authorization);
      }
    }
  })
);
 
// Analytics Service
app.use(
  '/api/analytics',
  createProxyMiddleware({
    target: process.env.ANALYTICS_SERVICE_URL || 'http://localhost:3006',
    changeOrigin: true,
    pathRewrite: {
      '^/api/analytics': ''
    },
    onProxyReq: (proxyReq, req: AuthRequest) => {
      if (req.headers.authorization) {
        proxyReq.setHeader('Authorization', req.headers.authorization);
      }
    }
  })
);
 
// Settings Service
app.use(
  '/api/settings',
  createProxyMiddleware({
    target: process.env.SETTINGS_SERVICE_URL || 'http://localhost:3007',
    changeOrigin: true,
    pathRewrite: {
      '^/api/settings': ''
    },
    onProxyReq: (proxyReq, req: AuthRequest) => {
      if (req.headers.authorization) {
        proxyReq.setHeader('Authorization', req.headers.authorization);
      }
    }
  })
);
 
// Notification Service
app.use(
  '/api/notify',
  createProxyMiddleware({
    target: process.env.NOTIFY_SERVICE_URL || 'http://localhost:3008',
    changeOrigin: true,
    pathRewrite: {
      '^/api/notify': ''
    },
    onProxyReq: (proxyReq, req: AuthRequest) => {
      if (req.headers.authorization) {
        proxyReq.setHeader('Authorization', req.headers.authorization);
      }
    }
  })
);
 
// ─────────────────────────────────────────────────────────────
// Health Checks
// ─────────────────────────────────────────────────────────────
 
app.get('/health', (req, res) => {
  res.json({ status: 'healthy' });
});
 
app.get('/api/health', (req, res) => {
  res.json({
    gateway: 'ok',
    timestamp: new Date().toISOString()
  });
});
 
// ─────────────────────────────────────────────────────────────
// Start Server
// ─────────────────────────────────────────────────────────────
 
app.listen(PORT, () => {
  console.log(`🚀 API Gateway running on port ${PORT}`);
  console.log(`   Auth Service: ${process.env.AUTH_SERVICE_URL}`);
  console.log(`   User Service: ${process.env.USER_SERVICE_URL}`);
  console.log(`   Product Service: ${process.env.PRODUCT_SERVICE_URL}`);
  console.log(`   Order Service: ${process.env.ORDER_SERVICE_URL}`);
});
 
export default app;

Phase 2: Micro-Frontend Extraction

Step 2.1: Create Shell Application

File: micro-frontends/shell/src/app/layout.tsx

import React from 'react';
import { ReactNode } from 'react';
import { Suspense } from 'react';
 
export const metadata = {
  title: 'Aori - Business Management Platform',
  description: 'Centralized business management solution',
};
 
export default function RootLayout({
  children,
}: {
  children: ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Suspense fallback={<div>Loading...</div>}>
          <div id="root">
            <Navigation />
            <main>{children}</main>
            <Footer />
          </div>
        </Suspense>
      </body>
    </html>
  );
}
 
function Navigation() {
  return (
    <nav className="navbar">
      <div className="logo">Aori</div>
      <ul>
        <li><a href="/products">Products</a></li>
        <li><a href="/orders">Orders</a></li>
        <li><a href="/account">Account</a></li>
        <li><a href="/settings">Settings</a></li>
      </ul>
    </nav>
  );
}
 
function Footer() {
  return (
    <footer>
      <p>&copy; 2026 Aori Labs. All rights reserved.</p>
    </footer>
  );
}

Step 2.2: Module Federation Setup

File: micro-frontends/shell/next.config.ts

import { NextConfig } from 'next';
import { NextFederationPlugin } from '@module-federation/nextjs-mf';
 
const nextConfig: NextConfig = {
  compiler: {
    styledComponents: true
  },
  webpack: (config, options) => {
    const { isServer } = options;
 
    config.plugins.push(
      new NextFederationPlugin({
        name: 'shell',
        filename: 'static/chunks/remoteEntry.js',
        remotes: {
          auth_module: !isServer
            ? new URL('http://localhost:3101/_next/static/chunks/remoteEntry.js', import.meta.url).href
            : 'auth_module@http://auth-module:3000/_next/static/chunks/remoteEntry.js',
          product_module: !isServer
            ? new URL('http://localhost:3104/_next/static/chunks/remoteEntry.js', import.meta.url).href
            : 'product_module@http://product-module:3000/_next/static/chunks/remoteEntry.js',
          order_module: !isServer
            ? new URL('http://localhost:3105/_next/static/chunks/remoteEntry.js', import.meta.url).href
            : 'order_module@http://order-module:3000/_next/static/chunks/remoteEntry.js'
        },
        exposes: {
          './hooks': './src/hooks/index.ts',
          './context': './src/context/index.ts',
          './ui': './src/ui/index.ts'
        },
        shared: ['react', 'react-dom', 'zustand', 'swr']
      })
    );
 
    return config;
  }
};
 
export default nextConfig;

Step 2.3: Module Loader Component

File: micro-frontends/shell/src/components/ModuleLoader.tsx

'use client';
 
import React, { Suspense, lazy, ComponentType } from 'react';
 
interface ModuleLoaderProps {
  scope: string;
  module: string;
  fallback?: React.ReactNode;
}
 
export function ModuleLoader({
  scope,
  module,
  fallback = <div>Loading module...</div>,
}: ModuleLoaderProps) {
  const [Component, setComponent] = React.useState<ComponentType | null>(null);
  const [error, setError] = React.useState<Error | null>(null);
 
  React.useEffect(() => {
    async function loadModule() {
      try {
        const container = await import(/* webpackIgnore: true */ `http://localhost:${getPortForScope(scope)}/_next/static/chunks/remoteEntry.js`);
        await __webpack_share_scopes__.default.init(container.default);
 
        const factory = await container.get(module);
        const Module = factory().default;
 
        setComponent(Module);
      } catch (err) {
        console.error(`Failed to load module ${module} from ${scope}:`, err);
        setError(err as Error);
      }
    }
 
    loadModule();
  }, [scope, module]);
 
  if (error) {
    return <div>Error loading module: {error.message}</div>;
  }
 
  if (!Component) {
    return fallback;
  }
 
  return <Component />;
}
 
function getPortForScope(scope: string): number {
  const ports: Record<string, number> = {
    auth_module: 3101,
    product_module: 3104,
    order_module: 3105,
    user_module: 3102,
    org_module: 3103,
    analytics_module: 3106,
    admin_module: 3107,
  };
  return ports[scope] || 3000;
}

Phase 3: Core Services

Step 3.1: Auth Service Template

File: services/auth/src/index.ts

import express, { Express, Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
 
const prisma = new PrismaClient();
const app: Express = express();
 
// ─────────────────────────────────────────────────────────────
// Middleware
// ─────────────────────────────────────────────────────────────
 
app.use(express.json());
 
// ─────────────────────────────────────────────────────────────
// Schemas
// ─────────────────────────────────────────────────────────────
 
const RegisterSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
});
 
const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string()
});
 
// ─────────────────────────────────────────────────────────────
// Routes
// ─────────────────────────────────────────────────────────────
 
// Register
app.post('/register', async (req: Request, res: Response) => {
  try {
    const { email, password } = RegisterSchema.parse(req.body);
 
    const existingUser = await prisma.user.findUnique({
      where: { email }
    });
 
    if (existingUser) {
      return res.status(409).json({ error: 'Email already exists' });
    }
 
    const hashedPassword = await bcrypt.hash(password, 10);
 
    const user = await prisma.user.create({
      data: {
        email,
        password: hashedPassword
      }
    });
 
    const token = jwt.sign({ id: user.id, email: user.email }, process.env.JWT_SECRET || 'secret', { expiresIn: '7d' });
 
    res.status(201).json({
      user: { id: user.id, email: user.email },
      token
    });
  } catch (error) {
    res.status(400).json({ error: (error as Error).message });
  }
});
 
// Login
app.post('/login', async (req: Request, res: Response) => {
  try {
    const { email, password } = LoginSchema.parse(req.body);
 
    const user = await prisma.user.findUnique({
      where: { email }
    });
 
    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
 
    const passwordMatch = await bcrypt.compare(password, user.password);
 
    if (!passwordMatch) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
 
    const token = jwt.sign({ id: user.id, email: user.email }, process.env.JWT_SECRET || 'secret', { expiresIn: '7d' });
 
    res.json({
      user: { id: user.id, email: user.email },
      token
    });
  } catch (error) {
    res.status(400).json({ error: (error as Error).message });
  }
});
 
// Verify Token
app.get('/verify', (req: Request, res: Response) => {
  const token = req.headers.authorization?.split(' ')[1];
 
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
 
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret');
    res.json({ valid: true, user: decoded });
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
});
 
// Health check
app.get('/health', (req: Request, res: Response) => {
  res.json({ status: 'healthy' });
});
 
// ─────────────────────────────────────────────────────────────
// Start
// ─────────────────────────────────────────────────────────────
 
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
  console.log(`🔐 Auth Service running on port ${PORT}`);
});

Step 3.2: User Service Template

File: services/user/src/index.ts

import express, { Express, Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import axios from 'axios';
 
const prisma = new PrismaClient();
const app: Express = express();
 
// ─────────────────────────────────────────────────────────────
// Middleware
// ─────────────────────────────────────────────────────────────
 
app.use(express.json());
 
// Auth verification middleware
const verifyToken = async (req: Request, res: Response, next: Function) => {
  const token = req.headers.authorization?.split(' ')[1];
 
  if (!token) {
    return res.status(401).json({ error: 'No token' });
  }
 
  try {
    const response = await axios.get('http://auth:3001/verify', {
      headers: { Authorization: `Bearer ${token}` }
    });
 
    (req as any).user = response.data.user;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
};
 
// ─────────────────────────────────────────────────────────────
// Routes
// ─────────────────────────────────────────────────────────────
 
// Get user profile
app.get('/:id', verifyToken, async (req: Request, res: Response) => {
  try {
    const user = await prisma.user.findUnique({
      where: { id: req.params.id },
      include: { profile: true }
    });
 
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }
 
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: (error as Error).message });
  }
});
 
// Update user profile
app.put('/:id', verifyToken, async (req: Request, res: Response) => {
  try {
    const user = await prisma.user.update({
      where: { id: req.params.id },
      data: req.body
    });
 
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: (error as Error).message });
  }
});
 
// Delete user
app.delete('/:id', verifyToken, async (req: Request, res: Response) => {
  try {
    await prisma.user.delete({
      where: { id: req.params.id }
    });
 
    res.json({ message: 'User deleted' });
  } catch (error) {
    res.status(500).json({ error: (error as Error).message });
  }
});
 
// Health check
app.get('/health', (req: Request, res: Response) => {
  res.json({ status: 'healthy' });
});
 
// ─────────────────────────────────────────────────────────────
// Start
// ─────────────────────────────────────────────────────────────
 
const PORT = process.env.PORT || 3002;
app.listen(PORT, () => {
  console.log(`👤 User Service running on port ${PORT}`);
});

Phase 4: Business Services

Step 4.1: Product Service Template

File: services/product/src/index.ts

import express, { Express, Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import Redis from 'ioredis';
 
const prisma = new PrismaClient();
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
const app: Express = express();
 
// ─────────────────────────────────────────────────────────────
// Routes
// ─────────────────────────────────────────────────────────────
 
// Get all products
app.get('/', async (req: Request, res: Response) => {
  try {
    // Check cache
    const cached = await redis.get('products:all');
    if (cached) {
      return res.json(JSON.parse(cached));
    }
 
    const products = await prisma.product.findMany({
      include: { category: true, images: true }
    });
 
    // Cache for 1 hour
    await redis.setex('products:all', 3600, JSON.stringify(products));
 
    res.json(products);
  } catch (error) {
    res.status(500).json({ error: (error as Error).message });
  }
});
 
// Get single product
app.get('/:id', async (req: Request, res: Response) => {
  try {
    const cached = await redis.get(`products:${req.params.id}`);
    if (cached) {
      return res.json(JSON.parse(cached));
    }
 
    const product = await prisma.product.findUnique({
      where: { id: req.params.id },
      include: { category: true, images: true }
    });
 
    if (!product) {
      return res.status(404).json({ error: 'Product not found' });
    }
 
    // Cache for 1 hour
    await redis.setex(`products:${req.params.id}`, 3600, JSON.stringify(product));
 
    res.json(product);
  } catch (error) {
    res.status(500).json({ error: (error as Error).message });
  }
});
 
// Create product (admin only)
app.post('/', async (req: Request, res: Response) => {
  try {
    const product = await prisma.product.create({
      data: req.body
    });
 
    // Invalidate cache
    await redis.del('products:all');
 
    res.status(201).json(product);
  } catch (error) {
    res.status(500).json({ error: (error as Error).message });
  }
});
 
// Update product
app.put('/:id', async (req: Request, res: Response) => {
  try {
    const product = await prisma.product.update({
      where: { id: req.params.id },
      data: req.body
    });
 
    // Invalidate cache
    await redis.del('products:all');
    await redis.del(`products:${req.params.id}`);
 
    res.json(product);
  } catch (error) {
    res.status(500).json({ error: (error as Error).message });
  }
});
 
// Health check
app.get('/health', (req: Request, res: Response) => {
  res.json({ status: 'healthy' });
});
 
// ─────────────────────────────────────────────────────────────
// Start
// ─────────────────────────────────────────────────────────────
 
const PORT = process.env.PORT || 3004;
app.listen(PORT, () => {
  console.log(`📦 Product Service running on port ${PORT}`);
});

Code Templates

Shared HTTP Client

File: shared/http-client/src/client.ts

import axios, { AxiosInstance, AxiosError } from 'axios';
 
export class HttpClient {
  private instance: AxiosInstance;
 
  constructor(baseURL: string, token?: string) {
    this.instance = axios.create({
      baseURL,
      headers: {
        'Content-Type': 'application/json',
        ...(token && { Authorization: `Bearer ${token}` })
      }
    });
 
    // Add response interceptor for error handling
    this.instance.interceptors.response.use(
      response => response,
      (error: AxiosError) => {
        if (error.response?.status === 401) {
          // Handle token refresh
          window.location.href = '/login';
        }
        return Promise.reject(error);
      }
    );
  }
 
  async get<T>(path: string, config?: any): Promise<T> {
    const response = await this.instance.get<T>(path, config);
    return response.data;
  }
 
  async post<T>(path: string, data?: any, config?: any): Promise<T> {
    const response = await this.instance.post<T>(path, data, config);
    return response.data;
  }
 
  async put<T>(path: string, data?: any, config?: any): Promise<T> {
    const response = await this.instance.put<T>(path, data, config);
    return response.data;
  }
 
  async delete<T>(path: string, config?: any): Promise<T> {
    const response = await this.instance.delete<T>(path, config);
    return response.data;
  }
}

Micro-Frontend Product Module Example

File: micro-frontends/product/src/app/page.tsx

'use client';
 
import { useEffect, useState } from 'react';
import useSWR from 'swr';
 
const fetcher = (url: string) =>
  fetch(url).then((res) => res.json());
 
export default function ProductsPage() {
  const { data: products, error, isLoading } = useSWR(
    '/api/products',
    fetcher
  );
 
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error loading products</div>;
 
  return (
    <div className="products-grid">
      {products?.map((product: any) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}
 
function ProductCard({ product }: { product: any }) {
  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button>Add to Cart</button>
    </div>
  );
}

Testing Strategies

Unit Tests

File: services/auth/__tests__/auth.test.ts

import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
import request from 'supertest';
import app from '../src/index';
 
describe('Auth Service', () => {
  describe('POST /register', () => {
    it('should register a new user', async () => {
      const response = await request(app).post('/register').send({
        email: 'test@example.com',
        password: 'SecurePassword123'
      });
 
      expect(response.status).toBe(201);
      expect(response.body.user).toHaveProperty('id');
      expect(response.body).toHaveProperty('token');
    });
 
    it('should reject duplicate email', async () => {
      // First registration
      await request(app).post('/register').send({
        email: 'duplicate@example.com',
        password: 'SecurePassword123'
      });
 
      // Second registration with same email
      const response = await request(app).post('/register').send({
        email: 'duplicate@example.com',
        password: 'AnotherPassword123'
      });
 
      expect(response.status).toBe(409);
    });
  });
 
  describe('POST /login', () => {
    it('should login with correct credentials', async () => {
      // Register first
      await request(app).post('/register').send({
        email: 'login@example.com',
        password: 'SecurePassword123'
      });
 
      // Login
      const response = await request(app).post('/login').send({
        email: 'login@example.com',
        password: 'SecurePassword123'
      });
 
      expect(response.status).toBe(200);
      expect(response.body).toHaveProperty('token');
    });
  });
});

Integration Tests

File: __tests__/integration/order-flow.test.ts

import { describe, it, expect } from '@jest/globals';
import axios from 'axios';
 
const API_GATEWAY = 'http://localhost:3000/api';
 
describe('Order Flow Integration', () => {
  let authToken: string;
  let userId: string;
  let productId: string;
 
  it('should complete full order flow', async () => {
    // 1. Register user
    const registerRes = await axios.post(`${API_GATEWAY}/auth/register`, {
      email: 'test@example.com',
      password: 'SecurePassword123'
    });
    authToken = registerRes.data.token;
    userId = registerRes.data.user.id;
 
    // 2. Fetch products
    const productsRes = await axios.get(`${API_GATEWAY}/products`);
    productId = productsRes.data[0].id;
 
    // 3. Create order
    const orderRes = await axios.post(
      `${API_GATEWAY}/orders`,
      {
        userId,
        items: [{ productId, quantity: 2 }]
      },
      { headers: { Authorization: `Bearer ${authToken}` } }
    );
 
    expect(orderRes.status).toBe(201);
    expect(orderRes.data.status).toBe('pending');
 
    // 4. Check order history
    const historyRes = await axios.get(`${API_GATEWAY}/users/${userId}/orders`, { headers: { Authorization: `Bearer ${authToken}` } });
 
    expect(historyRes.data).toContainEqual(expect.objectContaining({ id: orderRes.data.id }));
  });
});

Deployment Guides

Local Development Setup

File: README_SETUP.md

# Local Development Setup
 
## Prerequisites
 
- Docker & Docker Compose
- Node.js 20+
- Bun 1.2.19+
 
## Quick Start
 
1. Clone repository:
   \`\`\`bash
   git clone https://github.com/ilkhoeri/aori-apps.git
   cd aori-apps
   \`\`\`
 
2. Start Docker services:
   \`\`\`bash
   docker-compose -f docker-compose.yml up -d
   \`\`\`
 
3. Install dependencies:
   \`\`\`bash
   bun install
   \`\`\`
 
4. Run development environment:
   \`\`\`bash
   bun dev
   \`\`\`
 
5. Access applications:
   - Shell: http://localhost:3100
   - API Gateway: http://localhost:3000
   - Auth Service: http://localhost:3001
   - MongoDB: mongodb://root:password@localhost:27017
 
## Docker Compose Commands
 
\`\`\`bash
 
# Start all services
 
docker-compose up -d
 
# Stop all services
 
docker-compose down
 
# View logs
 
docker-compose logs -f service-name
 
# Restart specific service
 
docker-compose restart service-name
\`\`\`
 
## Service Ports
 
| Service        | Port  |
| -------------- | ----- |
| API Gateway    | 3000  |
| Auth           | 3001  |
| User           | 3002  |
| Organization   | 3003  |
| Product        | 3004  |
| Order          | 3005  |
| Analytics      | 3006  |
| Settings       | 3007  |
| Notification   | 3008  |
| Shell (MFE)    | 3100  |
| Auth Module    | 3101  |
| Product Module | 3104  |
| Order Module   | 3105  |
| MongoDB        | 27017 |
| Redis          | 6379  |

Kubernetes Deployment

File: infrastructure/kubernetes/deployment-auth-service.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: auth-service
  labels:
    app: auth-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: auth-service
  template:
    metadata:
      labels:
        app: auth-service
    spec:
      containers:
        - name: auth-service
          image: YOUR_REGISTRY/aori-auth:latest
          ports:
            - containerPort: 3001
          env:
            - name: NODE_ENV
              value: 'production'
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: auth-db
                  key: connection-string
            - name: JWT_SECRET
              valueFrom:
                secretKeyRef:
                  name: auth-secrets
                  key: jwt-secret
            - name: REDIS_URL
              value: 'redis://redis-service:6379'
          livenessProbe:
            httpGet:
              path: /health
              port: 3001
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /health
              port: 3001
            initialDelaySeconds: 10
            periodSeconds: 5
          resources:
            requests:
              memory: '256Mi'
              cpu: '250m'
            limits:
              memory: '512Mi'
              cpu: '500m'
 
---
apiVersion: v1
kind: Service
metadata:
  name: auth-service
spec:
  selector:
    app: auth-service
  ports:
    - protocol: TCP
      port: 3001
      targetPort: 3001
  type: ClusterIP

Monitoring & Observability

Health Check Setup

File: services/shared/health-check.ts

import express from 'express';
 
export function setupHealthChecks(app: express.Application) {
  app.get('/health', (req, res) => {
    res.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      uptime: process.uptime()
    });
  });
 
  app.get('/readiness', async (req, res) => {
    // Check database connection
    try {
      await prisma.$queryRaw`SELECT 1`;
      res.json({ status: 'ready' });
    } catch (error) {
      res.status(503).json({ status: 'not ready' });
    }
  });
 
  app.get('/metrics', (req, res) => {
    // Prometheus metrics
    res.set('Content-Type', 'text/plain');
    res.send(`
      # HELP request_total Total requests
      # TYPE request_total counter
      request_total{service="auth"} 1000
 
      # HELP response_time_seconds Response time
      # TYPE response_time_seconds histogram
      response_time_seconds_bucket{service="auth",le="0.1"} 950
    `);
  });
}

Success Metrics Checklist

Phase 1 Complete When:

  • API Gateway routing all requests correctly
  • All services responding on correct ports
  • Docker Compose fully operational
  • Local development setup documented

Phase 2 Complete When:

  • Shell application running
  • Module Federation working
  • Modules loading dynamically
  • Error boundaries functioning

Phase 3 Complete When:

  • Auth Service fully operational
  • User Service fully operational
  • Organization Service fully operational
  • Cross-service communication working

Phase 4 Complete When:

  • Product Service operational
  • Order Service operational
  • Analytics Service operational
  • Event bus messaging working

Phase 5 Complete When:

  • All remaining services operational
  • All micro-frontends running
  • Monolithic API decommissioned
  • Performance benchmarks met

Document Version: 1.0 Last Updated: August 4, 2026