Skip to main content

Middleware Architecture

Overview

The Axiom Genesis Middleware is a Node.js/Express backend service that powers the entire platform. It handles multi-tenant request processing, workflow execution, database connectivity, and business logic orchestration. The middleware enforces strict tenant isolation while providing extensible APIs for all platform operations.

Current Status:Production-Ready | Future: 🚀 AI agent integration for autonomous workflow generation


Current Technology Stack ✅

ComponentTechnologyPurpose
RuntimeNode.js 18+JavaScript runtime
Web FrameworkExpress.js 4.xHTTP server and routing
LanguageJavaScript/TypeScriptDynamic and type-safe code
Database Clientmssql/tediousMSSQL connection pooling
Queue SystemBullBackground job processing
CacheRedisSession and config caching
LoggingWinstonStructured logging
TestingJestUnit and integration tests
DocumentationSwagger/OpenAPIAPI documentation

Project Structure

middleware/
├── auth/ # Authentication & encryption
│ ├── jwt.service.js # JWT token generation, validation
│ ├── encryption.service.js # Tenant ID encryption/decryption
│ └── bcrypt.handler.js # Password hashing

├── config/ # Configuration management
│ ├── db/
│ │ ├── db_connections.js # Connection pooling for MSSQL
│ │ ├── config_db.js # Config database connection
│ │ └── redis.js # Redis client setup
│ ├── index.js # Environment-based loader
│ └── constants.js # Application constants

├── controllers/ # Request handlers (thin layer)
│ ├── page.controller.js # Page configuration endpoints
│ ├── workflow.controller.js # Workflow endpoints
│ ├── dataview.controller.js # DataView endpoints
│ ├── entity.controller.js # Dynamic CRUD operations
│ ├── auth.controller.js # Authentication endpoints
│ └── [10+ more controllers]

├── services/ # Business logic layer (core logic)
│ ├── page.service.js # Page configuration logic
│ ├── workflow.service.js # Workflow execution engine (973 lines)
│ ├── dataview.service.js # DataView logic
│ ├── entity.service.js # Dynamic entity operations
│ ├── ms-sql.service.js # MSSQL operations
│ ├── context.service.js # Request/tenant context
│ ├── validation.service.js # Validation rule engine
│ ├── field-mapping.service.js # Form field mapping
│ ├── object-registry.service.js # Object configuration
│ ├── schema-discovery.service.js # Database schema detection
│ ├── audit.service.js # Audit trail logging
│ └── [10+ more services]

├── repositories/ # Data access layer
│ ├── page.repository.js # Page data access
│ ├── workflow.repository.js # Workflow data access
│ ├── entity.repository.js # Entity data access
│ └── [domain repositories]

├── routes/ # API route definitions
│ ├── page.routes.js # /api/v1/pages
│ ├── workflow.routes.js # /api/v1/workflows
│ ├── dataview.routes.js # /api/v1/dataviews
│ ├── entity.routes.js # /api/v1/entities
│ ├── auth.routes.js # /api/v1/auth
│ └── [domain routes]

├── middleware/ # Express middleware
│ ├── tenantMiddleware.ts # Extract & validate tenant context
│ ├── authMiddleware.ts # JWT validation & user context
│ ├── errorHandler.ts # Centralized error formatting
│ ├── requestLogger.ts # Request/response logging
│ ├── rateLimiter.ts # API rate limiting
│ ├── validation.ts # Request body validation
│ └── [cross-cutting concerns]

├── workers/ # Background job processors
│ ├── workflow-executor.js # Processes workflow steps
│ ├── notification-sender.js # Sends notifications
│ ├── report-generator.js # Generates reports async
│ └── [domain workers]

├── crons/ # Scheduled tasks
│ ├── workflow-cleanup.js # Cleanup old instances
│ ├── cache-refresh.js # Refresh cached configs
│ └── [scheduled tasks]

├── jobs/ # Job queue definitions
│ ├── workflow-step.job.js # Workflow execution job
│ ├── notification.job.js # Notification job
│ └── [domain jobs]

├── utils/ # Utility functions
│ ├── logger.util.js # Structured logging
│ ├── errors.util.js # Custom error classes
│ ├── validators.util.js # Validation helpers
│ ├── date-time.util.js # Date utilities
│ └── transformers.util.js # Data transformation

├── migrations/ # Database schema migrations
│ ├── 001-initial-schema.sql
│ ├── 002-add-audit-tables.sql
│ └── [migration scripts]

├── tests/ # Test files
│ ├── unit/
│ ├── integration/
│ └── e2e/

├── swagger.js # API documentation config
├── index.js # Application entry point
├── package.json
├── .env.example
└── jest.config.js

Core Services

Workflow Engine Service ✅ Production

Manages workflow creation, execution, and monitoring.

// services/workflow.service.js (973 lines)
class WorkflowService {
// Create and execute workflow instance
async startWorkflow(workflowId, { inputData, version, userId, tenantId }) {
// 1. Validate workflow exists & user has access
// 2. Create instance record in wft_instances table
// 3. Queue workflow steps to wft_queue
// 4. Trigger background worker
// 5. Return instance ID
}

// Pause workflow execution (preserves state)
async pauseWorkflow(instanceId) {
// Update instance status to 'paused'
// Keep all execution state for resume
}

// Resume paused workflow
async resumeWorkflow(instanceId) {
// Requeue next steps
// Continue execution from pause point
}

// Get workflow execution logs
async getWorkflowLogs(instanceId, filter) {
// Retrieve step execution logs
// Include timing, data, errors
}

// Validate workflow definition
async validateWorkflow(workflowDefinition) {
// Check all triggers, steps, actions are valid
// Verify field mappings exist
}
}

Execution Model:

1. User submits workflow form
2. Controller validates input
3. Service creates instance record
4. Service queues steps to Bull queue
5. Worker processes step atomically
6. Worker updates instance status
7. Next step auto-queued on completion
8. Error handling redirects to error step
9. Instance completes when no more steps

Entity Services (ReadService / WriteService / DeleteService) ✅ Production

Dynamic CRUD operations are handled by three dedicated services, each with specific responsibilities:

ReadService — Fetch data

// services/entity/read.service.js
const ReadService = {
async get(objectCode, filter, userContext) {
// 1. resolveRegistryAndProfile(objectCode) → registry + profile
// 2. Build parameterized query from filter
// 3. Apply tenant isolation if prf_policy.tenantPolicy.mode = "STRICT"
// 4. Apply field-level security from prf_policy.projection
// 5. Execute via dialect-aware DB adapter
// 6. Return records
},

async getById(objectCode, id, userContext) {
// 1. resolveRegistryAndProfile(objectCode)
// 2. Extract identifier field from registry.obj_payload.identifier
// 3. Build single-record query: WHERE {identifier} = @id AND tenant check
// 4. Execute and return or 404
},

async list(objectCode, { filter, sort, page }, userContext) {
// 1. resolveRegistryAndProfile(objectCode)
// 2. Apply multi-field filter with AND/OR logic
// 3. Apply pagination (OFFSET/LIMIT)
// 4. Return { records: [], count, page, pageSize }
}
};

WriteService — Create and update data

// services/entity/write.service.js
const WriteService = {
async create(objectCode, payload, userContext) {
// 1. resolveRegistryAndProfile(objectCode)
// 2. Validate payload against prf_payload schema
// 3. Encrypt fields marked encrypted in obj_payload.field_mappings
// 4. Inject tenantId if prf_policy.tenantPolicy.mode != "PERMISSIVE"
// 5. Execute INSERT via adapter
// 6. Create audit log: { operation: "CREATE", before: null, after: record }
// 7. Emit realtime: emitCollectionEvent(objectCode, "create", { id })
// 8. Return created record
},

async update(objectCode, id, payload, userContext) {
// 1. resolveRegistryAndProfile(objectCode)
// 2. Fetch current record via ReadService.getById()
// 3. Validate update payload
// 4. Detect changed fields (before vs. after)
// 5. Encrypt changed encrypted fields
// 6. Execute UPDATE via adapter
// 7. Create audit log with before/after delta
// 8. Emit realtime: emitCollectionEvent(objectCode, "update", { id })
// 9. Return updated record
}
};

DeleteService — Soft and hard deletes

// services/entity/delete.service.js
const DeleteService = {
async softDelete(objectCode, id, userContext) {
// 1. resolveRegistryAndProfile(objectCode)
// 2. Extract soft-delete column from registry.obj_payload.softDeleteField
// 3. Execute UPDATE {softDeleteColumn} = TRUE WHERE id = @id
// 4. Create audit log: { operation: "DELETE", before: record, after: null }
// 5. Emit realtime: emitCollectionEvent(objectCode, "delete", { id })
},

async hardDelete(objectCode, id, userContext) {
// Physical removal (only if prf_policy.permissions.hardDelete = true)
// Same steps as softDelete but DELETE instead of UPDATE
}
};

All three services depend on resolveRegistryAndProfile(), which is detailed in profile-loader.md. Every call to these services first resolves the entity's registry (table mapping, column metadata, soft-delete config) and profile (access policy, audit rules, field permissions).


Repository Data Access Rule ⚠️ Architecture Constraint

Rule: Every middleware repository (and any service that accesses data) must delegate to ReadService / WriteService / DeleteService. Direct SQL, raw adapter calls, and bypass of the entity services are forbidden.

Why: The entity services enforce:

  • Tenant isolation — multi-tenant safety is guaranteed inside WriteService.create() and ReadService.get()
  • Soft-delete awareness — deleted records are automatically excluded from queries
  • Audit trails — every mutation is logged with before/after state and user context
  • Access control — field visibility and operation permissions come from prf_policy
  • Cache consistency — mutations trigger automatic cache invalidation via CacheService.onEntityMutated()

Bypassing these services skips all five layers. The result is security holes, missing audit records, and stale caches.

Call chain:

Controller

DomainService (FileService, WorkflowService, etc.)

DomainRepository (FileRepository, WorkflowRepository, etc.)

ReadService / WriteService / DeleteService

resolveRegistryAndProfile(objectCode)
├─ getRegistry(objectCode) — app_object_registries row
└─ getProfile(objectCode) — app_object_profiles row

DB Adapter (MssqlRead, PostgresRead, etc.)

Database

Concrete example — FileRepository refactoring:

Before (direct SQL):

// ❌ WRONG — bypasses tenant isolation, audit, soft-delete
FileRepository.findById = async (id, tenantId) => {
return db.query(`SELECT * FROM apt_files WHERE fst_id = ${id} AND fst_tenant_id = ${tenantId}`);
};

After (entity services):

// ✅ CORRECT — all safeguards present
FileRepository.findById = async (id, tenantId, userId) => {
const userContext = { userId, tenantId };
return ReadService.getById('FILES', id, userContext);
// This calls resolveRegistryAndProfile('FILES')
// Which fetches the registry (apt_files table, fst_id identifier)
// And the profile (permissions, audit rules)
// Then executes the query via adapter with tenant + soft-delete checks
};

Entity registration prerequisite:

For a new domain (e.g. apt_files) to use the entity services:

  1. Register in app_object_registries:

    INSERT INTO app_object_registries (obj_code, obj_type_code, obj_payload, obj_status)
    VALUES ('FILES', 'TABLE', '{"table":"apt_files","identifier":"fst_id","softDeleteField":"fst_is_deleted"}', 1)
  2. Register in app_object_profiles:

    INSERT INTO app_object_profiles (prf_object_code, prf_code, prf_policy, prf_status)
    VALUES ('FILES', 'FILES', '{"tenantPolicy":{"mode":"STRICT"},"permissions":{"read":true,"write":true}}', 1)

Without these rows, the services cannot resolve the entity and will return 404.


Page Service ✅ Production

Manages form/dashboard/report configurations.

class PageService {
async getPageConfig(pageCode, { tenantId }) {
// Fetch from cache (Redis) first
// If miss, get from app_page_configs table
// Parse pgc_page_schema JSON
// Apply tenant isolation check
// Cache for 5 minutes
}

async updatePageConfig(pageCode, schema, { tenantId, userId }) {
// Validate schema structure
// Update app_page_configs
// Invalidate cache
// Create audit log
}

async validatePageSchema(schema) {
// Validate all tabs exist
// Validate all fields have types
// Validate conditions reference valid fields
// Validate object code exists
}
}

Validation Service ✅ Production

Executes field and form-level validation rules.

class ValidationService {
async validateField(fieldCode, value, { rules, objectCode }) {
// Rules: required, minLength, maxLength, pattern, custom
// Return { valid: boolean, errors: string[] }
}

async validateForm(formData, formSchema, { objectCode, tenantId }) {
// Validate all fields against form schema
// Execute cross-field validations
// Return comprehensive error map
}
}

Request-Response Cycle

Incoming Request Flow

1. Express Router Middleware
└─ Route matches /api/v1/workflows/:id/execute

2. Request Middleware Stack (in order)
├─ tenantMiddleware
│ ├─ Extract x-tenant-id header
│ ├─ Decrypt tenant ID
│ └─ Set req.tenantId

├─ authMiddleware
│ ├─ Extract JWT from Authorization header
│ ├─ Validate token signature
│ ├─ Verify token hasn't expired
│ └─ Set req.user with decoded claims

└─ validation Middleware
├─ Validate request body against schema
└─ Return 400 if invalid

3. Controller
├─ Receive request with validated tenantId, user, body
├─ Call service layer method
└─ Return response to Express

4. Response Formatting
├─ Success (200):
│ └─ { success: true, data: {...}, meta: {timestamp, requestId} }

└─ Error (4xx/5xx):
└─ { success: false, error: {code, message, details}, meta: {...} }

5. Error Handler Middleware
├─ Catches any thrown errors
├─ Formats error response
├─ Logs with context (tenantId, userId, action)
└─ Sends response to client

Response Format

// Success Response (200, 201)
{
success: true,
data: {
workflowId: "wf-123",
instanceId: "wf-inst-456",
status: "running",
createdAt: "2026-03-12T10:30:00Z"
},
meta: {
timestamp: "2026-03-12T10:30:00Z",
requestId: "req-789",
version: "1.0"
}
}

// Error Response (400+)
{
success: false,
error: {
code: "VALIDATION_ERROR",
message: "Workflow validation failed",
details: {
trigger: "Trigger type is required",
steps: "At least one step required"
}
},
meta: {
timestamp: "2026-03-12T10:30:00Z",
requestId: "req-789"
}
}

API Endpoints ✅

Page Configuration Endpoints

GET    /api/v1/pages/:pageCode              # Get page config
POST /api/v1/pages # Create page
PUT /api/v1/pages/:pageCode # Update page
DELETE /api/v1/pages/:pageCode # Delete page
GET /api/v1/pages/validate # Validate page schema

Workflow Endpoints

GET    /api/v1/workflows                    # List workflows
POST /api/v1/workflows # Create workflow
GET /api/v1/workflows/:id # Get workflow
PUT /api/v1/workflows/:id # Update workflow
DELETE /api/v1/workflows/:id # Delete workflow
POST /api/v1/workflows/:id/execute # Execute workflow
POST /api/v1/workflows/:id/pause # Pause execution
POST /api/v1/workflows/:id/resume # Resume execution
GET /api/v1/workflows/:id/instances # List instances
GET /api/v1/workflows/:id/instances/:iid # Get instance details
GET /api/v1/workflows/:id/instances/:iid/logs # Get logs

Entity Endpoints

GET    /api/v1/entities/:objectCode         # List records
POST /api/v1/entities/:objectCode # Create record
GET /api/v1/entities/:objectCode/:id # Get record
PUT /api/v1/entities/:objectCode/:id # Update record
DELETE /api/v1/entities/:objectCode/:id # Delete record
POST /api/v1/entities/:objectCode/validate # Validate payload

DataView Endpoints

GET    /api/v1/dataviews/:dataviewCode     # Get data with config
GET /api/v1/dataviews/:dataviewCode/schema # Get schema
POST /api/v1/dataviews/:dataviewCode/bulk-action # Execute bulk action

Multi-Tenancy Isolation ✅

All data access enforces tenant isolation through:

Request-Level Isolation

// middleware/tenantMiddleware.ts
app.use((req, res, next) => {
const encryptedTenantId = req.headers['x-tenant-id'];
const tenantId = decrypt(encryptedTenantId);

// Verify tenant exists and user belongs to it
const userTenant = req.user.tenants.includes(tenantId);
if (!userTenant) {
return res.status(403).json({error: 'Tenant not authorized'});
}

req.tenantId = tenantId;
next();
});

Database Query Isolation

// All queries include tenant_id in WHERE clause
const getUserData = async (userId, tenantId) => {
return await db.query(
'SELECT * FROM app_users WHERE user_id = @userId AND tenant_id = @tenantId',
{ userId, tenantId }
);
};

Cache Isolation

// Cache keys include tenant_id
const cacheKey = `config:${tenantId}:pageCode:${pageCode}`;
const cachedConfig = await redis.get(cacheKey);

Security Features ✅

  • JWT Authentication - Stateless token-based auth
  • Encryption - Tenant IDs encrypted in transit
  • Parameterized Queries - SQL injection prevention
  • Rate Limiting - API rate limiting per tenant
  • Audit Trails - All operations logged with user context
  • Field-Level Security - Control who can see/edit fields
  • Row-Level Security - Control accessible records
  • Error Message Sanitization - No sensitive data in errors

Future Enhancements 🚀

AI Agent Integration (Q2 2026)

  • REST endpoints for AI agents to create workflows
  • Intelligent workflow suggestion API
  • Auto-generation of validation rules from data

Real-time Updates (Q3 2026)

  • WebSocket support for live workflow updates
  • Server-sent events for notifications
  • Real-time collaboration updates

Advanced Workflow Features (Q3 2026)

  • Parallel workflow steps
  • Advanced error handling and retry logic
  • Dynamic workflow generation from AI

Performance Optimization (Q4 2026)

  • Query optimization and indexing
  • Caching layer improvements
  • Connection pooling enhancements