Gati Architecture Documentation
This document provides a comprehensive overview of the Gati framework's architecture, design decisions, and internal workings.
Table of Contents
- Architecture Overview
- Layered Architecture Model
- Core Components
- Request Flow
- Component Interactions
- Design Decisions
- Extension Points
- Performance Considerations
- Security Architecture
Architecture Overview
Gati is built with a modular, event-driven architecture designed for cloud-native deployments. It follows functional programming principles with minimal abstraction layers.
High-Level Architecture
graph TB
Client[HTTP Client]
AppCore[App Core]
Router[Route Manager]
HandlerEngine[Handler Engine]
ModuleLoader[Module Loader]
ContextManager[Context Manager]
Middleware[Middleware Manager]
Client -->|HTTP Request| AppCore
AppCore -->|Parse Request| Router
Router -->|Find Route| HandlerEngine
AppCore -->|Create Context| ContextManager
AppCore -->|Execute Chain| Middleware
HandlerEngine -->|Access Modules| ModuleLoader
HandlerEngine -->|Execute Handler| Handler[Handler Function]
Handler -->|Use Modules| Module[Module Exports]
ModuleLoader -.->|Provides| Module
Handler -->|Send Response| Client
style AppCore fill:#4a90e2
style HandlerEngine fill:#e24a4a
style ModuleLoader fill:#4ae24a
style ContextManager fill:#e2e24aCore Principles
- Simplicity: Minimal abstraction, easy to understand
- Type Safety: Full TypeScript support with strict types
- Modularity: Reusable components via dependency injection
- Performance: Zero-overhead abstractions where possible
- Developer Experience: Fast feedback, hot reload, great tooling
Layered Architecture Model
Gati is organized into 10 distinct layers, each with specific responsibilities and performance characteristics. This layered model enables systematic optimization and clear separation of concerns.
Complete Layer Stack
┌─────────────────────────────────────────────────────────────┐
│ 1. Developer Tooling Layer │
│ (gati dev daemon, VSCode extension, analyzer) │
│ Performance: Incremental reanalysis < 100ms │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. File-Based Router & Route Loader │
│ (src/api/**, src/handlers/**, METHOD/ROUTE extraction) │
│ Performance: Route lookup < 0.5ms │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. Gati Analyzer & Type Registry │
│ (ts-morph, AST parsing, GType extraction) │
│ Performance: Single file analysis < 50ms │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. Artifact Generators │
│ (Validators, .d.ts, DB schemas, ORM schemas) │
│ Performance: Validator gen < 50ms per type │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. Runtime Protocol Gateways │
│ (HTTP, WebSocket, RPC adapters) │
│ Performance: Protocol overhead 0.5-2ms │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 6. Middleware Chain │
│ (Auth, CORS, rate limiting, tracing) │
│ Performance: Total chain < 5ms │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 7. Context Builder (gctx + lctx → ctx) │
│ (Shallow merge, request-scoped augmentation) │
│ Performance: Context creation < 0.2ms │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 8. Handler Execution Engine │
│ (Handler invocation, validation hooks, error handling) │
│ Performance: Invocation overhead < 1ms │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 9. Gati Native DB Client & Migration Engine │
│ (Type → SQL schema, query builder, migrations) │
│ Performance: Query overhead < 2ms (network dominates) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 10. External Systems (Postgres, Redis, S3, etc.) │
│ (Network I/O, third-party services) │
│ Performance: Variable (5-100+ ms typical) │
└─────────────────────────────────────────────────────────────┘Layer Responsibilities
| Layer | Responsibility | Performance Target | Critical Path |
|---|---|---|---|
| 1. Developer Tooling | File watching, hot reload, analyzer orchestration | < 100ms incremental | Dev only |
| 2. File-Based Router | Route discovery, METHOD/ROUTE extraction, param inference | < 0.5ms lookup | ✅ Runtime |
| 3. Gati Analyzer | AST parsing, type extraction, GType generation | < 50ms single file | Dev/Build |
| 4. Artifact Generators | Compile validators, generate .d.ts, DB schemas | < 50ms per type | Build |
| 5. Protocol Gateways | HTTP parsing, WebSocket framing, body deserialization | 0.5-2ms | ✅ Runtime |
| 6. Middleware Chain | Auth, CORS, rate limit, tracing, logging | < 5ms total | ✅ Runtime |
| 7. Context Builder | Merge gctx + lctx, create synthetic ctx for handlers | < 0.2ms | ✅ Runtime |
| 8. Handler Engine | Execute handler, validate input/output, error handling | < 1ms overhead | ✅ Runtime |
| 9. DB Client | Query building, type safety, migrations | < 2ms overhead | ✅ Runtime |
| 10. External Systems | Database I/O, cache lookups, third-party APIs | 5-100+ ms | ✅ Runtime |
Performance Focus: Layers 2, 5, 6, 7, 8, 9 are on the critical request path. Combined overhead target: < 10ms (excluding Layer 10 network I/O).
Request Lifecycle Through Layers
sequenceDiagram
participant Client
participant L5 as Protocol Gateway
participant L2 as Router
participant L6 as Middleware
participant L7 as Context Builder
participant L8 as Handler Engine
participant L9 as DB Client
participant L10 as Database
Client->>L5: HTTP Request
L5->>L2: Parse & Route
L2->>L6: Matched Route + Params
L6->>L7: Auth + Tracing
L7->>L8: Create ctx (gctx + lctx)
L8->>L8: Validate Input
L8->>L9: Execute Handler Logic
L9->>L10: Query Database
L10-->>L9: Result
L9-->>L8: Data
L8->>L8: Validate Output
L8-->>L7: Response
L7-->>L6: Serialize
L6-->>L5: JSON
L5-->>Client: HTTP ResponseOff-Critical-Path Layers
Layer 1 (Developer Tooling) and Layer 3 (Analyzer) run during development/build time:
- Watch file changes → incremental analysis → regenerate artifacts
- Full analysis on build → compiled validators/schemas → deployed artifacts
- No runtime cost in production
Layer 4 (Artifact Generators) runs at build time:
- Compiles validators to optimized imperative code
- Generates TypeScript declaration files
- Produces database schema migrations
- No runtime cost (artifacts are pre-compiled)
Core Components
1. App Core
Location: src/runtime/app-core.ts
Purpose: Main application orchestrator that manages the HTTP server, routing, and request lifecycle.
Key Responsibilities:
- Create and manage HTTP server
- Handle incoming requests
- Coordinate middleware execution
- Manage application lifecycle (startup, shutdown)
- Route registration API
Architecture:
class GatiApp {
private server: Server;
private router: RouteManager;
private middleware: MiddlewareManager;
private gctx: GlobalContext;
// Public API
get(path, handler) // Register GET route
post(path, handler) // Register POST route
use(middleware) // Add middleware
listen() // Start server
close() // Stop server
}Request Handling Flow:
sequenceDiagram
participant Client
participant AppCore
participant Middleware
participant Router
participant Handler
Client->>AppCore: HTTP Request
AppCore->>AppCore: Create Request/Response
AppCore->>AppCore: Create Local Context
AppCore->>Middleware: Execute Middleware Chain
Middleware->>Router: Find Matching Route
Router-->>Middleware: Route + Params
Middleware->>Handler: Execute Handler
Handler-->>Middleware: Response
Middleware->>AppCore: Complete
AppCore->>Client: HTTP Response2. Handler Engine
Location: src/runtime/handler-engine.ts
Purpose: Executes handler functions with proper error handling, timeout management, and context injection.
Key Responsibilities:
- Validate handler functions
- Execute handlers with timeout protection
- Catch and format handler errors
- Provide consistent error responses
Architecture:
async function executeHandler(
handler: Handler,
req: Request,
res: Response,
gctx: GlobalContext,
lctx: LocalContext,
options?: HandlerExecutionOptions
): Promise<void>Error Handling Flow:
graph LR
A[Handler Execution] -->|Success| B[Response Sent]
A -->|HandlerError| C[Format Error Response]
A -->|Timeout| D[Timeout Error]
A -->|Generic Error| E[Internal Server Error]
C --> F[JSON Error Response]
D --> F
E --> F
F --> G[Client]
style A fill:#4a90e2
style C fill:#e2e24a
style D fill:#e24a4a
style E fill:#e24a4a3. Route Manager
Location: src/runtime/route-manager.ts
Purpose: Manages route registration and matching with support for path parameters.
Key Responsibilities:
- Register routes with HTTP methods and paths
- Parse path patterns (e.g.,
/users/:id) - Match incoming requests to registered routes
- Extract path parameters
Architecture:
class RouteManager {
private routes: Route[];
register(method, path, handler) // Register route
match(method, path) // Find matching route
getRoutes() // List all routes
}Route Matching Algorithm:
graph TD
A[Incoming Request] -->|Method + Path| B{Normalize Path}
B --> C[For Each Route]
C --> D{Method Match?}
D -->|No| C
D -->|Yes| E{Path Pattern Match?}
E -->|No| C
E -->|Yes| F[Extract Params]
F --> G[Return RouteMatch]
C -->|No More Routes| H[Return null/404]
style G fill:#4ae24a
style H fill:#e24a4aPattern Examples:
/users→ Static path/users/:id→ Dynamic segment/users/:userId/posts/:postId→ Multiple parameters
4. Module Loader
Location: src/runtime/module-loader.ts
Purpose: Manages module lifecycle with dependency resolution and initialization.
Key Responsibilities:
- Register modules
- Resolve dependencies
- Initialize modules in correct order
- Detect circular dependencies
- Manage module lifecycle (init, shutdown, health checks)
Architecture:
class ModuleLoader {
private registry: ModuleRegistry;
register(module, gctx) // Register module
initialize(name, gctx) // Initialize module
get(name, gctx) // Get module exports
shutdown(name) // Shutdown module
healthCheck() // Check all modules
}Dependency Resolution:
graph TD
A[Module Registration] --> B{Has Dependencies?}
B -->|No| C[Mark as Ready]
B -->|Yes| D[Check Dependencies]
D --> E{All Available?}
E -->|No| F[Error: Missing Dependency]
E -->|Yes| G{Initialize Dependencies}
G --> H[Initialize Module]
H --> I[Mark as Initialized]
style C fill:#4ae24a
style I fill:#4ae24a
style F fill:#e24a4aModule States:
uninitialized→ Registered but not initializedinitializing→ Currently being initializedinitialized→ Ready to useerror→ Initialization failedshutdown→ Cleaned up
5. Context Manager
Location: src/runtime/context-manager.ts
Purpose: Manages global and local contexts with lifecycle hooks.
Key Responsibilities:
- Create global context (shared across requests)
- Create local context (per-request)
- Manage context lifecycle
- Execute cleanup hooks
Architecture:
graph TB
subgraph "Global Context (gctx)"
GM[Modules Registry]
GC[App Config]
GS[Shared State]
GL[Lifecycle Hooks]
end
subgraph "Local Context (lctx)"
LR[Request ID]
LT[Timestamp]
LS[Request State]
LL[Cleanup Hooks]
end
App[Application] -->|Creates Once| GM
App -->|Creates Once| GC
App -->|Creates Once| GS
Request1[Request 1] -->|Creates| LR
Request2[Request 2] -->|Creates| LT
Request3[Request 3] -->|Creates| LS
style GM fill:#4a90e2
style LR fill:#e2e24aContext Lifecycle:
sequenceDiagram
participant App
participant GlobalContext
participant Request
participant LocalContext
participant Handler
App->>GlobalContext: Create (once)
App->>GlobalContext: Load Modules
Request->>LocalContext: Create (per request)
LocalContext->>Handler: Inject gctx + lctx
Handler->>Handler: Execute
Handler->>LocalContext: Trigger Cleanup
LocalContext->>LocalContext: Execute onCleanup Hooks
Note over App,GlobalContext: On Shutdown
App->>GlobalContext: Trigger Shutdown
GlobalContext->>GlobalContext: Execute onShutdown Hooks6. Middleware Manager
Location: src/runtime/middleware.ts
Purpose: Manages middleware chain execution with error handling.
Key Responsibilities:
- Register request middleware
- Register error middleware
- Execute middleware chain
- Handle middleware errors
Middleware Chain:
graph LR
A[Request] --> B[Middleware 1]
B --> C[Middleware 2]
C --> D[Middleware 3]
D --> E[Route Handler]
E --> F[Middleware 3']
F --> G[Middleware 2']
G --> H[Middleware 1']
H --> I[Response]
E -.->|Error| J[Error Middleware]
J --> I
style E fill:#4a90e2
style J fill:#e24a4aRequest Flow
Complete Request Lifecycle
sequenceDiagram
autonumber
participant Client
participant AppCore
participant ContextManager
participant Middleware
participant Router
participant HandlerEngine
participant Handler
participant Module
Client->>AppCore: HTTP Request
AppCore->>AppCore: Create Request Object
AppCore->>AppCore: Create Response Object
AppCore->>ContextManager: Create Local Context
ContextManager-->>AppCore: lctx
AppCore->>Middleware: Execute Chain
Middleware->>Router: Match Route
Router-->>Middleware: RouteMatch + Params
Middleware->>HandlerEngine: Execute Handler
HandlerEngine->>Handler: Call (req, res, gctx, lctx)
Handler->>Module: Use Module
Module-->>Handler: Result
Handler->>Handler: Process Logic
Handler-->>HandlerEngine: Response
HandlerEngine-->>Middleware: Complete
Middleware->>ContextManager: Cleanup Local Context
ContextManager->>ContextManager: Execute onCleanup Hooks
Middleware-->>AppCore: Complete
AppCore->>Client: HTTP ResponseStep-by-Step Breakdown
1. Request Reception
- HTTP server receives request
- Node.js
IncomingMessagecreated
2. Request Parsing
- Create Gati
Requestobject - Parse URL, query parameters
- Parse request body (JSON)
3. Context Creation
- Create local context with unique ID
- Set request timestamp
- Initialize request state
4. Middleware Execution
- Execute logging middleware
- Execute authentication middleware
- Execute custom middleware
5. Route Matching
- Normalize request path
- Iterate through registered routes
- Match HTTP method and path pattern
- Extract path parameters
6. Handler Execution
- Validate handler function
- Set execution timeout
- Call handler with (req, res, gctx, lctx)
- Catch and format errors
7. Module Access
- Handler accesses modules via
gctx.modules - Module exports provide functionality
- Modules share resources (DB, cache, etc.)
8. Response Generation
- Handler calls response methods
- Set status code and headers
- Send JSON, text, or raw response
9. Cleanup
- Execute local context cleanup hooks
- Close request-scoped resources
- Log request completion
10. Response Delivery
- Send HTTP response to client
- Close connection (or keep-alive)
Component Interactions
Handler → Module Interaction
graph TB
subgraph "Handler Layer"
H1[User Handler]
H2[Post Handler]
H3[Auth Handler]
end
subgraph "Module Layer"
M1[Database Module]
M2[Cache Module]
M3[Logger Module]
M4[Email Module]
end
subgraph "External Resources"
DB[(PostgreSQL)]
Redis[(Redis)]
SMTP[SMTP Server]
end
H1 -->|Query Users| M1
H1 -->|Get Cache| M2
H2 -->|Query Posts| M1
H2 -->|Log Event| M3
H3 -->|Send Email| M4
M1 <-->|Connection Pool| DB
M2 <-->|Client| Redis
M4 -->|Send| SMTP
style H1 fill:#4a90e2
style M1 fill:#4ae24a
style DB fill:#e2e24aModule Dependencies
graph TD
A[User Service Module] --> B[Database Module]
A --> C[Cache Module]
D[Email Service Module] --> E[Template Module]
F[Auth Module] --> B
F --> G[JWT Module]
H[Logging Module] --> I[Config Module]
style A fill:#4a90e2
style B fill:#4ae24a
style C fill:#4ae24aDesign Decisions
1. Handler Signature: Four Parameters
Decision: handler(req, res, gctx, lctx)
Rationale:
- Explicit Context: Clear separation of global vs. local state
- Type Safety: Each parameter is strongly typed
- Testability: Easy to mock context objects
- Clarity: No hidden dependencies
Alternatives Considered:
- Express-style
(req, res, next)- Lacks context support - Single context object - Less clear separation
- Dependency injection via decorators - More complex
2. Module Pattern: Functional vs. Class-Based
Decision: Support both patterns
Rationale:
- Flexibility: Developers choose based on needs
- Simplicity: Functions for simple modules
- Power: Classes for complex stateful modules
- TypeScript: Both work well with TypeScript
Examples:
// Functional
export function createLoggerModule() { /* ... */ }
// Class-based
export class DatabaseModule implements Module { /* ... */ }3. Error Handling: HandlerError Class
Decision: Custom HandlerError class with status code and context
Rationale:
- Consistency: Standardized error responses
- Context: Additional error information
- HTTP Codes: Built-in status code support
- Type Safety: TypeScript type checking
Example:
throw new HandlerError('User not found', 404, { userId: '123' });4. Context Split: Global vs. Local
Decision: Two separate context objects
Rationale:
- Clarity: Clear what's shared vs. request-scoped
- Performance: No overhead tracking scope
- Safety: Prevents accidental state leaks
- Lifecycle: Different cleanup requirements
Usage:
gctx- Modules, config, shared statelctx- Request ID, timestamp, request state
5. Route Matching: Simple Pattern Matching
Decision: Basic pattern matching with :param syntax
Rationale:
- Simplicity: Easy to understand
- Performance: Fast matching algorithm
- Predictability: No regex complexity
- Sufficiency: Covers 95% of use cases
Limitations:
- No regex patterns
- No optional segments
- No wildcard matching
Future: Can be extended if needed
6. Module Loading: Lazy by Default
Decision: Modules initialize on first use unless configured otherwise
Rationale:
- Fast Startup: Don't initialize unused modules
- Flexibility: Configure eager loading if needed
- Error Isolation: Init errors don't crash app
Configuration:
const loader = createModuleLoader({
autoInit: true, // Override for eager loading
});Extension Points
Gati provides several extension points for customization:
1. Custom Middleware
Add custom request processing:
const customMiddleware: Middleware = async (req, res, gctx, lctx, next) => {
// Before handler
console.log('Before:', req.path);
await next();
// After handler
console.log('After:', res.status);
};
app.use(customMiddleware);2. Custom Modules
Create domain-specific modules:
export function createMyModule(): Module<MyAPI> {
return {
name: 'myModule',
version: '1.0.0',
exports: { /* API */ },
init: async (gctx) => { /* Setup */ },
shutdown: async () => { /* Cleanup */ },
};
}3. Custom Error Handlers
Override default error handling:
const errorHandler: ErrorMiddleware = (error, req, res, gctx, lctx) => {
// Custom error formatting
res.status(500).json({
error: error.message,
requestId: lctx.requestId,
timestamp: lctx.timestamp,
});
};
app.useError(errorHandler);4. Custom Request/Response
Extend request/response objects:
// Via middleware
const enhanceRequest: Middleware = async (req, res, gctx, lctx, next) => {
(req as any).user = await authenticate(req);
await next();
};5. Plugin System (Future)
Planned plugin architecture:
// Future API
const plugin: GatiPlugin = {
name: 'my-plugin',
version: '1.0.0',
setup(app: GatiApp) {
// Register routes, middleware, modules
app.use(myMiddleware);
app.get('/plugin-route', handler);
},
};
app.plugin(plugin);Performance Considerations
1. Zero-Copy Request Parsing
- Minimal object creation
- Direct access to Node.js primitives
- No unnecessary transformations
2. Connection Pooling
- Database connection pools in modules
- Reuse connections across requests
- Configurable pool sizes
3. Lazy Module Loading
- Initialize modules on demand
- Reduce startup time
- Lower memory footprint
4. Efficient Route Matching
- O(n) route matching (linear scan)
- Early exit on first match
- Pre-compiled route patterns
5. Context Reuse
- Global context created once
- Local context pooled (future optimization)
- Minimal garbage collection
Performance Tips
// ✅ Good - Reuse module instances
const db = gctx.modules['db'];
await db.query(/* ... */);
await db.query(/* ... */);
// ❌ Bad - Repeated module lookups
await gctx.modules['db'].query(/* ... */);
await gctx.modules['db'].query(/* ... */);
// ✅ Good - Parallel queries
const [users, posts] = await Promise.all([
db.query('SELECT * FROM users'),
db.query('SELECT * FROM posts'),
]);
// ❌ Bad - Sequential queries
const users = await db.query('SELECT * FROM users');
const posts = await db.query('SELECT * FROM posts');Security Architecture
1. Input Validation
- Validate all user input
- Use Zod or similar schemas
- Sanitize data before use
2. Error Handling
- Never expose stack traces in production
- Log errors internally
- Return generic error messages
3. Module Isolation
- Modules don't share mutable state
- Each module has isolated initialization
- Clean shutdown prevents leaks
4. Context Isolation
- Local context per request
- No cross-request state leakage
- Automatic cleanup
5. Type Safety
- TypeScript strict mode
- No implicit
anytypes - Runtime type checking with Zod
Security Best Practices
// ✅ Good - Validate input
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
const result = schema.safeParse(req.body);
if (!result.success) {
throw new HandlerError('Invalid input', 400);
}
// ✅ Good - Don't expose errors
try {
await dangerousOperation();
} catch (error) {
logger.error('Operation failed', error);
throw new HandlerError('Operation failed', 500);
}
// ❌ Bad - Expose internal errors
catch (error) {
res.json({ error: error.stack }); // Don't do this!
}Summary
Gati's architecture is designed for:
- Simplicity: Minimal abstractions, easy to understand
- Performance: Fast request handling, efficient resource use
- Modularity: Reusable components via dependency injection
- Type Safety: Full TypeScript support
- Developer Experience: Great tooling, hot reload, clear errors
The framework follows functional programming principles where possible while maintaining the flexibility for object-oriented patterns when beneficial.
See Also: