Skip to content

API Architecture

Daniel Truong edited this page Apr 8, 2026 · 4 revisions

EPIC/Eagle Platform Architecture

Overview

The EPIC (Environmental Assessment Information Catalogue) platform is a microservices-based application deployed on OpenShift that provides public access to environmental assessment projects in British Columbia. The platform consists of multiple services working together through a combination of direct routing and reverse proxy patterns.

Service Map

graph TB
    Route["OpenShift Routes<br/>eagle-{env}.apps.silver.devops.gov.bc.ca"]
    
    Route -->|/api direct| API["eagle-api<br/>Node.js/Express<br/>Port 3000"]
    Route -->|/ root| RProxy["rproxy<br/>nginx<br/>Port 8080"]
    
    API --> MongoDB[(MongoDB)]
    
    RProxy --> Public["eagle-public<br/>Angular<br/>Port 8080"]
    RProxy --> Admin["eagle-admin<br/>Angular<br/>Port 8080"]
    RProxy --> Analytics["penguin-analytics<br/>Node.js<br/>Port 3000"]
    
    Analytics --> TimescaleDB[("TimescaleDB<br/>PostgreSQL")]
    
    style Route fill:#e1f5ff
    style API fill:#fff4e6
    style RProxy fill:#f3e5f5
    style MongoDB fill:#e8f5e9
    style TimescaleDB fill:#e8f5e9
Loading

Core Services

eagle-api (Backend API)

Technology: Node.js/Express, MongoDB, Mongoose ODM
Port: 3000
Repository: bcgov/eagle-api

The main backend API service providing RESTful endpoints for:

  • Public project information (/api/public/*)
  • Protected admin operations (/api/* - requires Keycloak authentication)
  • Runtime configuration for frontends (/api/config)
  • Document management and search
  • User management and authorization

Key Features:

  • Keycloak JWT authentication for admin endpoints
  • MongoDB for data persistence
  • S3/Minio integration for document storage
  • Comprehensive search capabilities
  • RESTful API design following OpenAPI specification

eagle-public (Public Frontend)

Technology: Angular 21 (standalone components), TypeScript
Port: 8080 (nginx)
Repository: bcgov/eagle-public

Public-facing Angular application providing:

  • Browse environmental assessment projects
  • View project details, documents, and milestones
  • Search functionality
  • Responsive design for mobile/desktop
  • Anonymous analytics tracking

Deployment: 2-stage Docker build

  1. Build stage: Node compiles Angular app
  2. Runtime stage: nginx serves static files

eagle-admin (Admin Frontend)

Technology: Angular (standalone components), TypeScript
Port: 8080 (nginx)
Repository: bcgov/eagle-admin

Administrative interface for authorized users:

  • Keycloak SSO integration
  • Project management (CRUD operations)
  • Document upload and management
  • User and role management
  • Authenticated analytics tracking

Authentication Flow:

  1. User redirects to Keycloak login
  2. Keycloak returns JWT token
  3. Frontend includes JWT in Authorization header
  4. eagle-api validates JWT for protected endpoints

penguin-analytics-api (Analytics Service)

Technology: Node.js/Express, TimescaleDB (PostgreSQL + time-series)
Port: 3000
Repository: bcgov/penguin-analytics

Dedicated analytics service for tracking user interactions:

  • Anonymous event ingestion
  • Time-series data storage
  • Event schema validation
  • Metabase dashboard integration
  • Batch and single-event endpoints

See: Analytics Architecture for detailed analytics documentation.

rproxy/eao-nginx (Reverse Proxy)

Technology: nginx (OpenShift S2I build)
Port: 8080
Repository: bcgov/eao-nginx

Central reverse proxy providing:

  • Path-based routing to backend services
  • HTTP caching layer
  • Security headers
  • Optional HTTP Basic Auth per location
  • Single entry point for multiple services

Request Routing Architecture

The platform uses two distinct routing patterns that work together:

Pattern 1: Direct Route to eagle-api

OpenShift Route Configuration:

  • Host: eagle-{env}.apps.silver.devops.gov.bc.ca
  • Path: /api
  • Target: eagle-api:3000
  • TLS: Edge termination

Request Flow:

sequenceDiagram
    participant Browser as User Browser
    participant Route as OpenShift Route
    participant API as eagle-api:3000
    
    Browser->>Route: GET /api/projects
    Note over Route: Direct match on /api path
    Route->>API: Forward request
    API-->>Route: Response
    Route-->>Browser: Response
Loading

Pattern 2: Centralized rproxy Route

OpenShift Route Configuration:

  • Host: eagle-{env}.apps.silver.devops.gov.bc.ca
  • Path: / (root - catches all non-/api paths)
  • Target: rproxy:8080
  • TLS: Edge termination

rproxy nginx Routing Rules:

Path Target Service Purpose
/ eagle-public:8080 Public frontend
/public eagle-public:8080 Alternative public access
/admin/ eagle-admin:8080 Admin frontend
/analytics penguin-analytics-api:3000 Analytics events

Request Flow Example (accessing admin):

sequenceDiagram
    participant Browser as User Browser
    participant Route as OpenShift Route
    participant RProxy as rproxy:8080
    participant Admin as eagle-admin:8080
    
    Browser->>Route: GET /admin/
    Note over Route: Root / path matches
    Route->>RProxy: Forward to rproxy
    Note over RProxy: location /admin/ rule
    RProxy->>Admin: proxy_pass to :8080
    Admin-->>RProxy: Angular app
    RProxy-->>Route: Response
    Route-->>Browser: Angular app
Loading

Why /api Bypasses rproxy

The /api path has both routing configurations:

  1. Direct OpenShift route to eagle-api (explicit /api path)
  2. rproxy location block that could also proxy /api

The direct route takes precedence at the OpenShift ingress level.

Rationale for Direct Routing

Performance:

  • Eliminates extra nginx hop for frequent API calls
  • Reduces latency for data-heavy operations
  • Direct TLS termination at OpenShift edge

Simplicity:

  • Cleaner authentication header passing (Keycloak JWT)
  • No nginx proxy_pass header manipulation needed
  • Easier to debug connection issues

Independence:

  • API can scale independently from rproxy
  • API restarts don't affect rproxy
  • Separate resource allocation and monitoring

Backend Communication:

  • API handles sensitive operations (auth, authorization, data modification)
  • Direct path provides better security audit trail
  • Reduces complexity in request chain

When rproxy is Used

rproxy is used for:

  • Frontend applications (eagle-public, eagle-admin) - static file serving with caching
  • Analytics - separate microservice integration
  • Path-based routing - multiple services on same domain
  • Caching layer - reduce load on backend services
  • Security headers - centralized CSP, HSTS, etc.

Service Communication Patterns

Frontend → Backend API

eagle-public:

// Via ConfigService
API_LOCATION: 'https://eagle-dev.apps.silver.devops.gov.bc.ca'
API_PATH: '/api/public'

// HTTP calls
this.http.get(`${API_LOCATION}${API_PATH}/projects`)
    
https://eagle-dev.apps.silver.devops.gov.bc.ca/api/public/projects
    
OpenShift Route (direct /api)
    
eagle-api:3000/api/public/projects

eagle-admin:

// Authenticated calls include JWT
this.http.get(`${API_LOCATION}${API_PATH}/projects`, {
  headers: { Authorization: `Bearer ${jwt}` }
})
    
https://eagle-dev.apps.silver.devops.gov.bc.ca/api/projects
    
eagle-api:3000/api/projects
    
JWT validation via Keycloak

Frontend → Analytics

// Via AnalyticsService
ANALYTICS_API_URL: '/analytics'

// Event tracking
this.http.post(`${ANALYTICS_API_URL}`, eventData)
    
https://eagle-dev.apps.silver.devops.gov.bc.ca/analytics
    
OpenShift Route (root /)
    
rproxy:8080
    
location /analytics { proxy_pass penguin-analytics-api:3000/analytics; }
    
penguin-analytics-api:3000/analytics
    
TimescaleDB storage

Frontend → Configuration

Both frontends use the ConfigService pattern for runtime configuration:

// At application bootstrap
ConfigService.init()
    
HTTP GET /api/config
    
eagle-api:3000/api/config
    
Returns: {
  ENVIRONMENT: 'dev',
  API_LOCATION: 'https://eagle-dev.apps.silver.devops.gov.bc.ca',
  API_PATH: '/api',
  ANALYTICS_API_URL: '/analytics',
  KEYCLOAK_URL: '...',
  // ... other config
}
    
Frontend initializes services with runtime config

See: Configuration Management for detailed configuration documentation.

MongoDB Query Patterns

Collation Requirement

Every query in eagle-api uses .collation({ locale: 'en', strength: 2 }) for case-insensitive string matching. All indexes on the epic collection must be created with the same collation — otherwise MongoDB ignores them and falls back to a full collection scan of 79,000+ documents.

The four performance indexes (added in migration 20260408000000-addCollatedIndexes) are:

Index Fields Covers
idx_schemaName_en {_schemaName:1} All schema-filtered queries
idx_document_by_project {_schemaName:1, project:1} Document/CommentPeriod lists
idx_comment_by_period {_schemaName:1, period:1} Comment queries by period
idx_recentActivity_active_pinned_date {_schemaName:1, active:1, pinned:-1, dateAdded:-1} Homepage recent activity

All are created with collation: { locale: 'en', strength: 2 }.

$lookup Pipeline Ordering

Utils.runDataQuery() (the generic aggregation builder) runs $lookup stages before $sort/$limit. This is fine for paginated list queries, but is expensive for small-result endpoints — every joined document in the collection is processed before pagination discards most of them.

Rule: If an endpoint only needs a small fixed number of results (e.g., 4 homepage items from 2,400+ active records), write a custom pipeline that places $sort+$limit before any $lookup stages. See api/controllers/recentActivity.js for the reference implementation, which reduced response time from ~1,700 ms to ~200 ms.

// ✅ Fast — limit before lookup
$match → $sort → $limit(4) → $lookup → $lookup → ...

// ❌ Slow — lookup before limit
$match → $lookup(all 2,400 docs) → $sort → $limit(4)

Related Pages

Clone this wiki locally