Automatic Router¶
Implementation status
The automatic router is a planned component. This page documents the intended design based on the module system architecture and the principle that modules declare their routes rather than the core knowing about them.
Purpose¶
In a module-based ERP, routes cannot be hardcoded in the core — modules don't exist yet when the core compiles. The automatic router solves this by letting modules declare routes at load time, which the core then wires into the HTTP mux.
Responsibilities¶
- Maintain a route table that maps
(method, path)to a module handler - Accept route registrations from modules at load time
- Namespace module routes automatically to prevent collisions
- Generate an API schema (OpenAPI) from registered routes
- Handle path parameters (
/orders/{id}) and query parameters - Enforce that no two modules register the same route
Route Namespace Convention¶
Every module's routes are automatically prefixed with /api/{module_name}/:
| Module | Declared path | Effective path |
|---|---|---|
crm | /contacts | /api/crm/contacts |
crm | /contacts/{id} | /api/crm/contacts/{id} |
inventory | /items | /api/inventory/items |
This prevents collisions without requiring module authors to know about other modules.
Route Registration Flow¶
sequenceDiagram
participant Loader as Module Loader
participant WASM as CRM Module
participant Router as Route Registry
participant Mux as HTTP Mux
Loader->>WASM: Instantiate module
WASM->>Router: register_route("GET", "/contacts", handler_id=1)
WASM->>Router: register_route("POST", "/contacts", handler_id=2)
WASM->>Router: register_route("GET", "/contacts/{id}", handler_id=3)
Router->>Router: Prefix with /api/crm/
Router->>Mux: mux.Handle("GET /api/crm/contacts", dispatch(crm, 1))
Router->>Mux: mux.Handle("POST /api/crm/contacts", dispatch(crm, 2))
Router->>Mux: mux.Handle("GET /api/crm/contacts/{id}", dispatch(crm, 3))
Loader->>Mux: Freeze route table (no more registrations at runtime) After all modules have loaded, the route table is frozen. New routes cannot be registered without a restart.
Handler Dispatch¶
When a request arrives at a module route, the core:
- Extracts the matched
handler_idand module instance - Serializes the request (headers, path params, body) into a WASM-compatible format
- Calls the module's
handle(handler_id, request_ptr, request_len)export - Reads the response from WASM linear memory
- Writes the response to the HTTP response writer
sequenceDiagram
participant Mux
participant Dispatch as Core Dispatcher
participant WASM as Module Instance
participant Memory as WASM Linear Memory
Mux->>Dispatch: Route matched (module=crm, handler_id=3, r=Request)
Dispatch->>Dispatch: Serialize request → JSON bytes
Dispatch->>Memory: Write request bytes at ptr
Dispatch->>WASM: handle(3, ptr, len)
WASM->>WASM: Deserialize request, run business logic
WASM->>Memory: Write response JSON at resp_ptr
WASM-->>Dispatch: (resp_ptr, resp_len)
Dispatch->>Memory: Read response[resp_ptr : resp_ptr+resp_len]
Dispatch->>Dispatch: Write to http.ResponseWriter Core Routes¶
The core itself registers a small number of routes outside the module namespace:
| Method | Path | Purpose |
|---|---|---|
GET | /health | Liveness probe |
GET | /ready | Readiness probe (DB connectivity) |
GET | /api/modules | List loaded modules and their versions |
POST | /api/auth/login | Issue an authentication token |
POST | /api/auth/refresh | Refresh a token |
Extension Points¶
| Extension | How |
|---|---|
| Custom path prefix | Override module name prefix via module.json route_prefix field |
| Route middleware | Apply per-module middleware by wrapping the dispatcher |
| OpenAPI generation | Walk the route table post-load and emit an OpenAPI 3 spec |
| GraphQL | Register a single /graphql handler in the core that routes to a schema stitched from module-declared types |