Plugins & Coprocessors
HyperRoute provides two extensibility mechanisms: plugins for built-in transformations and coprocessors for external HTTP services that hook into the request lifecycle.
Plugin System
Lifecycle Stages
Plugins hook into a defined request lifecycle:
Request In
→ on_router_request (auth, rate limit, transform)
→ on_subgraph_request (per-subgraph headers, logging)
→ [upstream call]
→ on_subgraph_response (transform, cache)
→ on_router_response (headers, logging, metrics)
Response Out
Plugin Priority
Plugins execute in priority order within each lifecycle stage:
| Priority | Use Case | Example |
|---|---|---|
| Critical | Must run first, can block | Authentication |
| High | Important, security-related | Rate limiting, validation |
| Normal | Standard processing | Transformation, logging |
| Low | Can run last | Analytics, feature flags |
Built-in Plugins
Logging Plugin
Logs request details for debugging and audit:
plugins:
- name: logging
enabled: true
priority: normal
config:
log_headers: true
log_timing: true
Headers Plugin
Add, remove, or transform headers at the router level:
plugins:
- name: headers
enabled: true
priority: low
config:
add:
X-Router-Version: "2.0.0"
Strict-Transport-Security: "max-age=31536000"
remove:
- X-Powered-By
Custom Plugins
Custom plugins follow the same lifecycle interface. Register them in your configuration:
plugins:
- name: my-custom-plugin
enabled: true
priority: high
config:
custom_key: custom_value
Coprocessors
Coprocessors are external HTTP services that HyperRoute calls at specific lifecycle stages. They allow you to run arbitrary logic — authentication, audit logging, header injection — in any language, deployed as separate services.
Architecture
Client → HyperRoute Router → [Coprocessor HTTP Call] → Continue/Break
↓
Subgraph Fetch
↓
[Coprocessor HTTP Call] → Continue/Break
↓
Response to Client
Lifecycle Stages
| Stage | When | Use Case |
|---|---|---|
router_request | Before any processing | Authentication, rate limiting |
router_response | Before sending to client | Audit logging, response transform |
subgraph_request | Before each subgraph call | Per-subgraph auth, header injection |
subgraph_response | After each subgraph response | Response validation, caching |
execution_request | Before query execution | Query cost estimation |
execution_response | After query execution | Result transformation |
supergraph_request | Before supergraph processing | Early request validation |
Protocol
HyperRoute sends an HTTP POST to your coprocessor URL with a JSON body:
Request from HyperRoute:
{
"version": 1,
"stage": "router_request",
"router": {
"request_id": "req-abc-123",
"headers": {
"authorization": "Bearer eyJhbGci...",
"content-type": "application/json"
},
"body": "{\"query\": \"{ user { name } }\"}",
"context": {},
"operation_name": "GetUser"
}
}
Response — Continue Processing:
{
"version": 1,
"stage": "router_request",
"control": {
"action": "continue"
},
"router": {
"headers": {
"x-user-id": "user-789",
"x-user-roles": "admin,viewer"
},
"context": {
"authenticated": true,
"user_id": "user-789"
}
}
}
Response — Reject Request:
{
"version": 1,
"stage": "router_request",
"control": {
"action": "break",
"status_code": 401,
"message": "Invalid or expired authentication token"
}
}
Example: Authentication Coprocessor (Node.js)
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
app.post('/coprocess', async (req, res) => {
const { stage, router } = req.body;
if (stage !== 'router_request') {
return res.json({
version: 1, stage,
control: { action: 'continue' }
});
}
const authHeader = router.headers['authorization'];
if (!authHeader?.startsWith('Bearer ')) {
return res.json({
version: 1, stage,
control: { action: 'break', status_code: 401, message: 'Missing token' }
});
}
try {
const decoded = jwt.verify(authHeader.slice(7), process.env.JWT_SECRET);
return res.json({
version: 1, stage,
control: { action: 'continue' },
router: {
...router,
headers: { ...router.headers, 'x-user-id': decoded.sub },
context: { ...router.context, user: decoded }
}
});
} catch (err) {
return res.json({
version: 1, stage,
control: { action: 'break', status_code: 401, message: 'Invalid token' }
});
}
});
app.listen(8080);
Example: Audit Logging Coprocessor (Python)
from flask import Flask, request, jsonify
from datetime import datetime
import logging, json
app = Flask(__name__)
logger = logging.getLogger('audit')
@app.route('/coprocess', methods=['POST'])
def coprocess():
data = request.json
stage = data.get('stage')
if stage == 'router_response':
audit_log = {
'request_id': data.get('router', {}).get('request_id'),
'operation': data.get('router', {}).get('operation_name'),
'user_id': data.get('router', {}).get('context', {}).get('user_id'),
'status_code': data.get('response', {}).get('status_code'),
'timestamp': datetime.utcnow().isoformat()
}
logger.info(json.dumps(audit_log))
return jsonify({
'version': 1, 'stage': stage,
'control': {'action': 'continue'}
})
if __name__ == '__main__':
app.run(port=8081)
Coprocessor Configuration
coprocessors:
# Authentication — must succeed
- name: auth
url: http://auth-service:8080/coprocess
timeout: 5s
stages:
- router_request
fail_open: false # Reject requests if auth service is down
max_retries: 2
retry_delay: 100ms
# Audit logging — best effort
- name: audit
url: http://audit-service:8081/coprocess
timeout: 2s
stages:
- router_response
fail_open: true # Don't block requests if audit fails
# Header injection for specific subgraphs
- name: header-inject
url: http://header-service:8082/coprocess
stages:
- subgraph_request
subgraphs: # Only apply to these subgraphs
- users
- billing
Best Practices
- Authentication: Use
fail_open: false— security cannot be bypassed - Audit/Analytics: Use
fail_open: true— don't block requests for non-critical processing - Set appropriate timeouts — don't let slow coprocessors impact latency
- Order plugins by criticality: Authentication → Rate Limiting → Transformation → Logging → Analytics
- Use coprocessors for cross-cutting concerns — keep business logic in subgraphs
Next Steps
- Configuration — Full plugin and coprocessor config
- Observability — Monitor plugin execution times
- Security — Built-in security pipeline