# API Gateway Architecture & Security Implementation Plan **Created**: October 3, 2025 **Status**: Planning Phase **Priority**: High - Security & Scalability --- ## πŸ“‹ Executive Summary ### Current State - **Backend API**: Directly exposed to internet at `https://maternal-api.noru1.ro` - **Frontend**: Next.js web app making direct API calls from browser - **Security Risk**: All API endpoints publicly accessible - **Architecture**: Monolithic - single NestJS backend serving REST, GraphQL, and WebSocket ### Problem Statement With the current architecture: 1. Backend API is fully exposed to the internet (security risk) 2. No rate limiting at infrastructure level 3. Direct API access bypasses Next.js middleware/auth checks 4. Future mobile apps will need direct API access 5. WebSocket connections need persistent connection handling 6. GraphQL endpoint requires different security model than REST ### Proposed Solution Implement an **API Gateway pattern** with: - Next.js API routes as BFF (Backend-for-Frontend) for web app - Direct backend access for mobile apps (with API key + JWT) - Nginx/Kong as reverse proxy for rate limiting and SSL termination - WebSocket gateway for real-time features - Separate security policies for REST vs GraphQL --- ## πŸ—οΈ Architecture Overview ### Option 1: Next.js API Routes as BFF (Recommended for MVP) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Internet (HTTPS) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Nginx/Cloudflareβ”‚ β”‚ (SSL, WAF, DDoS) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Next.js Frontend β”‚ β”‚ Mobile Apps β”‚ β”‚ (Port 3030) β”‚ β”‚ (iOS/Android) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ /api/* Routes β”‚ β”‚ β”‚ β”‚ β”‚ (BFF Layer) β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ (Internal Network) β”‚ (Public API) β”‚ localhost:3020 β”‚ api.maternal.com β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ NestJS Backend β”‚ β”‚ (Port 3020) β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ REST API β”‚ β”‚ β”‚ β”‚ /api/v1/* β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ GraphQL β”‚ β”‚ β”‚ β”‚ /graphql β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ WebSocket β”‚ β”‚ β”‚ β”‚ /ws β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PostgreSQL β”‚ β”‚ Redis β”‚ β”‚ MongoDB β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Option 2: Kong API Gateway (Production-Ready) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Internet (HTTPS) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Kong API Gateway β”‚ β”‚ (Port 443/8000) β”‚ β”‚ β”‚ β”‚ Plugins: β”‚ β”‚ - Rate Limiting β”‚ β”‚ - JWT Auth β”‚ β”‚ - CORS β”‚ β”‚ - Request Transform β”‚ β”‚ - Response Cache β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Next.js Frontend β”‚ β”‚ Mobile Apps β”‚ β”‚ (Internal) β”‚ β”‚ (External) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ NestJS Backend β”‚ β”‚ (Internal Network) β”‚ β”‚ Not exposed to web β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## 🎯 Recommended Architecture: Hybrid Approach ### Phase 1: MVP (Current State + BFF) 1. **Web App**: Next.js API routes as BFF (proxy to backend) 2. **Mobile Apps**: Direct backend access with API keys 3. **Backend**: Exposed only to mobile, hidden from web 4. **Timeline**: 1-2 weeks ### Phase 2: Production (Kong Gateway) 1. **All Clients**: Route through Kong API Gateway 2. **Backend**: Completely internal, not exposed 3. **Security**: Centralized auth, rate limiting, logging 4. **Timeline**: 4-6 weeks (post-MVP) --- ## πŸ“ Implementation Plan: Phase 1 (BFF Pattern) ### Step 1: Create Next.js API Routes as BFF **Goal**: Proxy all backend requests through Next.js to hide backend URL **Files to Create**: ``` maternal-web/ β”œβ”€β”€ app/api/ β”‚ β”œβ”€β”€ proxy/ β”‚ β”‚ β”œβ”€β”€ route.ts # Generic proxy handler β”‚ β”‚ └── [...path]/route.ts # Catch-all proxy β”‚ β”œβ”€β”€ graphql/ β”‚ β”‚ └── route.ts # GraphQL proxy β”‚ └── ws/ β”‚ └── route.ts # WebSocket upgrade handler ``` **Implementation**: #### 1.1 Generic REST API Proxy **File**: `maternal-web/app/api/proxy/[...path]/route.ts` ```typescript import { NextRequest, NextResponse } from 'next/server'; const BACKEND_URL = process.env.BACKEND_API_URL || 'http://localhost:3020'; export async function GET( request: NextRequest, { params }: { params: { path: string[] } } ) { return proxyRequest(request, params.path, 'GET'); } export async function POST( request: NextRequest, { params }: { params: { path: string[] } } ) { return proxyRequest(request, params.path, 'POST'); } export async function PATCH( request: NextRequest, { params }: { params: { path: string[] } } ) { return proxyRequest(request, params.path, 'PATCH'); } export async function DELETE( request: NextRequest, { params }: { params: { path: string[] } } ) { return proxyRequest(request, params.path, 'DELETE'); } async function proxyRequest( request: NextRequest, pathSegments: string[], method: string ) { const path = pathSegments.join('/'); const url = new URL(request.url); const backendUrl = `${BACKEND_URL}/api/v1/${path}${url.search}`; // Forward headers (excluding host) const headers = new Headers(); request.headers.forEach((value, key) => { if (key.toLowerCase() !== 'host' && key.toLowerCase() !== 'connection') { headers.set(key, value); } }); // Add internal API key for backend authentication headers.set('X-Internal-API-Key', process.env.INTERNAL_API_KEY || ''); try { const body = method !== 'GET' ? await request.text() : undefined; const response = await fetch(backendUrl, { method, headers, body, }); // Forward response headers const responseHeaders = new Headers(); response.headers.forEach((value, key) => { responseHeaders.set(key, value); }); const responseBody = await response.text(); return new NextResponse(responseBody, { status: response.status, statusText: response.statusText, headers: responseHeaders, }); } catch (error) { console.error('Proxy error:', error); return NextResponse.json( { error: 'Internal proxy error' }, { status: 502 } ); } } ``` #### 1.2 GraphQL Proxy **File**: `maternal-web/app/api/graphql/route.ts` ```typescript import { NextRequest, NextResponse } from 'next/server'; const BACKEND_URL = process.env.BACKEND_API_URL || 'http://localhost:3020'; export async function POST(request: NextRequest) { const backendUrl = `${BACKEND_URL}/graphql`; const headers = new Headers(); headers.set('Content-Type', 'application/json'); headers.set('Authorization', request.headers.get('Authorization') || ''); headers.set('X-Internal-API-Key', process.env.INTERNAL_API_KEY || ''); try { const body = await request.text(); const response = await fetch(backendUrl, { method: 'POST', headers, body, }); const responseBody = await response.text(); return new NextResponse(responseBody, { status: response.status, headers: { 'Content-Type': 'application/json', }, }); } catch (error) { console.error('GraphQL proxy error:', error); return NextResponse.json( { errors: [{ message: 'GraphQL proxy error' }] }, { status: 502 } ); } } // Support GraphQL Playground in development export async function GET(request: NextRequest) { if (process.env.NODE_ENV !== 'production') { const backendUrl = `${BACKEND_URL}/graphql`; const response = await fetch(backendUrl); const html = await response.text(); return new NextResponse(html, { headers: { 'Content-Type': 'text/html' }, }); } return NextResponse.json( { error: 'GraphQL Playground disabled in production' }, { status: 403 } ); } ``` #### 1.3 WebSocket Proxy (Next.js Limitation Workaround) **Note**: Next.js API routes don't support WebSocket upgrades directly. We need to use a custom server or keep WebSocket on backend. **Option A**: Keep WebSocket endpoint on backend (simpler) **Option B**: Use Next.js custom server with `ws` library (complex) **Recommended**: Keep WebSocket on backend, add authentication layer **File**: `maternal-app-backend/src/families/families.gateway.ts` (modify) ```typescript import { WebSocketGateway, WebSocketServer, OnGatewayConnection } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway({ cors: { origin: [ 'http://localhost:3030', 'https://maternal.noru1.ro', 'https://maternal-web.noru1.ro', ], credentials: true, }, }) export class FamiliesGateway implements OnGatewayConnection { @WebSocketServer() server: Server; async handleConnection(client: Socket) { // Verify internal API key or JWT token const apiKey = client.handshake.headers['x-internal-api-key']; const token = client.handshake.auth.token; if (!apiKey && !token) { client.disconnect(); return; } // Authenticate based on mobile (token) or web (API key) const isValid = await this.validateConnection(apiKey, token); if (!isValid) { client.disconnect(); } } } ``` --- ### Step 2: Update Frontend to Use BFF **Files to Modify**: - `lib/api/client.ts` - `lib/apollo-client.ts` - All API utility files **Changes**: ```typescript // lib/api/client.ts (before) const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3020'; // lib/api/client.ts (after) const API_BASE_URL = '/api/proxy'; // Use Next.js BFF // lib/apollo-client.ts (before) uri: process.env.NEXT_PUBLIC_GRAPHQL_URL || 'http://localhost:3020/graphql', // lib/apollo-client.ts (after) uri: '/api/graphql', // Use Next.js GraphQL proxy ``` **Environment Variables**: ```bash # maternal-web/.env.local # Remove NEXT_PUBLIC_API_URL (security - don't expose backend URL to browser) # NEXT_PUBLIC_API_URL=https://maternal-api.noru1.ro # DELETE THIS # Add backend URL for server-side only BACKEND_API_URL=http://localhost:3020 INTERNAL_API_KEY=your-secret-internal-key-12345 # For WebSocket, keep exposed for now (will secure later) NEXT_PUBLIC_WS_URL=https://maternal-api.noru1.ro ``` --- ### Step 3: Add Internal API Key Authentication to Backend **Goal**: Backend validates requests from Next.js BFF using internal API key **File**: `maternal-app-backend/src/common/guards/internal-api-key.guard.ts` (new) ```typescript import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @Injectable() export class InternalApiKeyGuard implements CanActivate { constructor(private configService: ConfigService) {} canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const apiKey = request.headers['x-internal-api-key']; const expectedKey = this.configService.get('INTERNAL_API_KEY'); if (!expectedKey) { // If not configured, allow (development mode) return true; } if (apiKey !== expectedKey) { throw new UnauthorizedException('Invalid internal API key'); } return true; } } ``` **Usage** (apply to all controllers): ```typescript import { Controller, UseGuards } from '@nestjs/common'; import { InternalApiKeyGuard } from '../common/guards/internal-api-key.guard'; @Controller('api/v1/children') @UseGuards(InternalApiKeyGuard) // Add this to all controllers export class ChildrenController { // ... } ``` --- ### Step 4: Configure Mobile App Direct Access **Goal**: Mobile apps bypass BFF and call backend directly with API key + JWT **Backend Configuration**: ```typescript // app.module.ts app.enableCors({ origin: [ 'http://localhost:3030', // Next.js dev 'https://maternal.noru1.ro', // Production web 'capacitor://localhost', // iOS mobile 'http://localhost', // Android mobile 'ionic://localhost', // Ionic mobile ], credentials: true, }); ``` **Mobile App Configuration** (future): ```typescript // mobile-app/src/config/api.ts const API_CONFIG = { baseUrl: 'https://api.maternal.noru1.ro', // Direct backend access graphqlUrl: 'https://api.maternal.noru1.ro/graphql', wsUrl: 'wss://api.maternal.noru1.ro', headers: { 'X-API-Key': process.env.MOBILE_API_KEY, // Different from internal key }, }; ``` --- ### Step 5: Nginx Configuration for Production **Goal**: Use Nginx as reverse proxy for SSL termination and basic security **File**: `/etc/nginx/sites-available/maternal-app` ```nginx # Upstream backends upstream nextjs_backend { server 127.0.0.1:3030; } upstream nestjs_backend { server 127.0.0.1:3020; } # Redirect HTTP to HTTPS server { listen 80; server_name maternal.noru1.ro; return 301 https://$server_name$request_uri; } # Main web application (Next.js) server { listen 443 ssl http2; server_name maternal.noru1.ro; ssl_certificate /etc/letsencrypt/live/maternal.noru1.ro/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/maternal.noru1.ro/privkey.pem; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Rate limiting limit_req_zone $binary_remote_addr zone=web_limit:10m rate=100r/m; limit_req zone=web_limit burst=20 nodelay; location / { proxy_pass http://nextjs_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # API backend (for mobile apps only - optional) server { listen 443 ssl http2; server_name api.maternal.noru1.ro; ssl_certificate /etc/letsencrypt/live/api.maternal.noru1.ro/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.maternal.noru1.ro/privkey.pem; # Stricter rate limiting for API limit_req_zone $binary_remote_addr zone=api_limit:10m rate=60r/m; limit_req zone=api_limit burst=10 nodelay; # Only allow mobile app user agents (optional) if ($http_user_agent !~* (Maternal-iOS|Maternal-Android|curl)) { return 403; } location / { proxy_pass http://nestjs_backend; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # WebSocket support location /ws { proxy_pass http://nestjs_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } } ``` --- ## πŸ”’ Security Enhancements ### 1. API Key Management **Environment Variables**: ```bash # Backend (.env.production) INTERNAL_API_KEY= MOBILE_API_KEY= # Next.js (.env.local) INTERNAL_API_KEY= BACKEND_API_URL=http://localhost:3020 # or internal network IP ``` **Generation**: ```bash # Generate secure API keys openssl rand -base64 32 ``` --- ### 2. Rate Limiting Strategy | Client Type | Rate Limit | Enforcement | |-------------|------------|-------------| | Web (BFF) | 100 req/min per IP | Nginx | | Mobile (Direct) | 60 req/min per API key | Nginx + NestJS | | GraphQL | 30 queries/min | NestJS middleware | | WebSocket | 10 connections per user | NestJS gateway | **Implementation** (NestJS): ```typescript // src/common/middleware/rate-limit.middleware.ts import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { Redis } from 'ioredis'; @Injectable() export class ApiKeyRateLimitMiddleware implements NestMiddleware { constructor(private redis: Redis) {} async use(req: Request, res: Response, next: NextFunction) { const apiKey = req.headers['x-api-key']; if (!apiKey) { return next(); } const key = `rate_limit:api_key:${apiKey}`; const count = await this.redis.incr(key); if (count === 1) { await this.redis.expire(key, 60); // 1 minute window } if (count > 60) { return res.status(429).json({ error: 'Rate limit exceeded', retryAfter: await this.redis.ttl(key), }); } next(); } } ``` --- ### 3. CORS Configuration **Strict CORS for Production**: ```typescript // main.ts app.enableCors({ origin: (origin, callback) => { const allowedOrigins = [ 'https://maternal.noru1.ro', 'https://maternal-web.noru1.ro', ]; // Allow mobile apps (check user agent) if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else if (origin.includes('capacitor://')) { callback(null, true); // Ionic/Capacitor } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowedHeaders: [ 'Content-Type', 'Authorization', 'X-API-Key', 'X-Internal-API-Key', ], }); ``` --- ## πŸ“± Mobile App Integration ### React Native / Expo Configuration ```typescript // mobile-app/src/services/api.ts import axios from 'axios'; import Constants from 'expo-constants'; const API_CONFIG = { baseURL: Constants.expoConfig?.extra?.apiUrl || 'https://api.maternal.noru1.ro', headers: { 'X-API-Key': Constants.expoConfig?.extra?.apiKey, 'User-Agent': `Maternal-${Platform.OS}/${Constants.expoConfig?.version}`, }, }; const apiClient = axios.create(API_CONFIG); // Add JWT token to requests apiClient.interceptors.request.use((config) => { const token = getAuthToken(); // From AsyncStorage if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); ``` **WebSocket Connection** (mobile): ```typescript import io from 'socket.io-client'; const socket = io('wss://api.maternal.noru1.ro', { auth: { token: getAuthToken(), }, transports: ['websocket'], reconnection: true, reconnectionAttempts: 5, }); ``` --- ## πŸ” Monitoring & Logging ### 1. Request Logging (Nginx) ```nginx log_format api_log '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' 'api_key=$http_x_api_key ' 'request_time=$request_time'; access_log /var/log/nginx/maternal_api_access.log api_log; ``` ### 2. Backend Request Tracking ```typescript // src/common/middleware/request-logger.middleware.ts import { Injectable, NestMiddleware, Logger } from '@nestjs/common'; @Injectable() export class RequestLoggerMiddleware implements NestMiddleware { private logger = new Logger('HTTP'); use(req: Request, res: Response, next: NextFunction) { const { method, originalUrl, headers } = req; const userAgent = headers['user-agent']; const apiKey = headers['x-api-key']; const isInternal = headers['x-internal-api-key'] ? 'internal' : 'external'; const start = Date.now(); res.on('finish', () => { const { statusCode } = res; const duration = Date.now() - start; this.logger.log( `${method} ${originalUrl} ${statusCode} ${duration}ms - ${isInternal} - ${apiKey?.substring(0, 8)}...` ); }); next(); } } ``` --- ## πŸš€ Deployment Strategy ### Phase 1: MVP Deployment (Week 1-2) **Week 1**: - [x] Create Next.js API proxy routes - [x] Update frontend to use BFF - [x] Add internal API key guard to backend - [x] Test web app with new architecture **Week 2**: - [x] Configure Nginx reverse proxy - [x] Set up SSL certificates - [x] Deploy to production - [x] Monitor and fix issues ### Phase 2: Mobile App Support (Week 3-4) **Week 3**: - [ ] Create mobile API keys - [ ] Configure CORS for mobile - [ ] Set up mobile-specific rate limits - [ ] Test with React Native/Expo **Week 4**: - [ ] Add mobile user agent detection - [ ] Implement mobile analytics - [ ] Load testing with mobile traffic - [ ] Documentation for mobile devs ### Phase 3: Kong Gateway (Month 2-3) **Month 2**: - [ ] Set up Kong API Gateway - [ ] Configure plugins (rate limit, JWT, logging) - [ ] Migrate Next.js BFF to Kong routes - [ ] Test parallel deployment **Month 3**: - [ ] Full cutover to Kong - [ ] Remove Next.js BFF (optional) - [ ] Advanced features (caching, GraphQL federation) - [ ] Performance optimization --- ## πŸ“Š Performance Considerations ### Latency Impact | Architecture | Latency | Notes | |--------------|---------|-------| | Direct Backend | 50-100ms | Current (baseline) | | Next.js BFF | +20-40ms | Acceptable for web | | Kong Gateway | +10-20ms | Production-optimized | ### Caching Strategy **Next.js Edge Caching**: ```typescript // app/api/proxy/[...path]/route.ts export const revalidate = 60; // Cache for 60 seconds // Or per-route if (path.startsWith('children')) { return NextResponse.json(data, { headers: { 'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600', }, }); } ``` **Redis Caching** (backend): ```typescript // Already implemented in backend @UseInterceptors(CacheInterceptor) @CacheTTL(300) async getChildren() { // ... } ``` --- ## βœ… Acceptance Criteria ### Security - [ ] Backend not directly accessible from browser (except WebSocket for now) - [ ] Internal API key required for BFF β†’ Backend - [ ] Mobile API key required for mobile β†’ Backend - [ ] Rate limiting enforced at Nginx and NestJS levels - [ ] CORS configured for web and mobile origins - [ ] SSL/TLS for all external connections ### Functionality - [ ] Web app works through BFF without code changes to components - [ ] GraphQL queries work through proxy - [ ] WebSocket connections remain stable - [ ] Mobile apps can connect directly to backend - [ ] Real-time sync works across web and mobile ### Performance - [ ] Latency increase < 50ms for BFF - [ ] No degradation in WebSocket performance - [ ] API response times within SLA (<200ms p95) ### Monitoring - [ ] Request logs include API key and source - [ ] Rate limit violations logged - [ ] Error tracking for proxy failures - [ ] Metrics dashboard shows BFF vs direct traffic --- ## πŸ”§ Troubleshooting ### Common Issues **1. WebSocket Connections Failing** - **Symptom**: Socket.io connection refused - **Fix**: Ensure WebSocket endpoint bypasses BFF (direct backend connection) - **Config**: Update `NEXT_PUBLIC_WS_URL` to backend URL **2. CORS Errors on Mobile** - **Symptom**: OPTIONS preflight fails - **Fix**: Add mobile origins to CORS whitelist - **Config**: Check `capacitor://localhost` is allowed **3. Rate Limiting Too Aggressive** - **Symptom**: 429 errors during normal usage - **Fix**: Adjust Nginx `limit_req` or NestJS throttler - **Config**: Increase burst size or rate **4. GraphQL Subscriptions Not Working** - **Symptom**: Subscriptions disconnect immediately - **Fix**: GraphQL subscriptions need WebSocket, can't go through HTTP proxy - **Solution**: Use Apollo Client with split link (HTTP for queries, WS for subscriptions) --- ## πŸ“š References - [Next.js API Routes Documentation](https://nextjs.org/docs/api-routes/introduction) - [Kong API Gateway](https://konghq.com/products/kong-gateway) - [Nginx Reverse Proxy Guide](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) - [NestJS Guards](https://docs.nestjs.com/guards) - [Socket.io CORS Configuration](https://socket.io/docs/v4/handling-cors/) --- **Last Updated**: October 3, 2025 **Review Date**: Post-MVP Launch **Owner**: Backend Team