Documentation Index Fetch the complete documentation index at: https://docs.ensemble.ai/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The HTTP trigger supports Hono middleware for adding cross-cutting concerns like logging, compression, security headers, and custom logic to your API endpoints.
Middleware can be used in:
YAML ensembles - Reference by string name
TypeScript ensembles - Pass functions directly
# YAML: Use built-in middleware by name
trigger :
- type : http
path : /api/data
middleware :
- logger
- compress
- secure-headers
// TypeScript: Pass functions directly
import { logger } from 'hono/logger'
import { compress } from 'hono/compress'
const ensemble = {
trigger: [{
type: 'http' ,
path: '/api/data' ,
middleware: [ logger (), compress ()]
}]
}
Built-in Middleware
Conductor ships with 6 Hono middleware handlers ready to use:
Name Package Description loggerhono/loggerLogs HTTP requests/responses to console compresshono/compressGzip/Brotli response compression timinghono/timingAdds Server-Timing header for performance metrics secure-headershono/secure-headersSecurity headers (X-Frame-Options, CSP, etc) pretty-jsonhono/pretty-jsonPretty-prints JSON responses (dev only) etaghono/etagGenerates ETags for cache validation
Quick Example
name : api-with-middleware
description : Production API with security and performance
trigger :
- type : http
path : /api/users
methods : [ GET , POST ]
public : true
middleware :
- logger # Log all requests
- secure-headers # Add security headers
- compress # Compress responses
- etag # Add cache validation
agents :
- name : handle-request
operation : code
config :
script : scripts/handle-users-request
// scripts/handle-users-request.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function handleUsersRequest ( context : AgentExecutionContext ) {
// Fetch users from database or other source
const users = [
{ id: 1 , name: 'Alice' },
{ id: 2 , name: 'Bob' }
]
return { users }
}
output : ${handle-request.output}
Middleware Execution Order
Middleware executes in this specific order:
CORS (if configured in trigger)
Rate Limiting (if configured in trigger)
Cache (if configured in trigger)
Custom Middleware (your middleware array)
Auth (if configured in trigger)
Main Handler (ensemble execution)
trigger :
- type : http
path : /api/secure
# 1. CORS runs first
cors :
origin : "https://example.com"
# 2. Rate limiting runs second
rateLimit :
requests : 100
window : 60
# 3. Cache runs third
cache :
enabled : true
ttl : 300
# 4. Custom middleware runs fourth
middleware :
- logger
- secure-headers
# 5. Auth runs fifth
auth :
type : bearer
secret : ${env.API_SECRET}
# 6. Main handler executes last
Custom Middleware
Register your own middleware in your worker’s initialization.
Basic Registration
// src/index.ts
import { getHttpMiddlewareRegistry } from '@ensemble-edge/conductor'
import { createAutoDiscoveryAPI } from '@ensemble-edge/conductor/api'
const registry = getHttpMiddlewareRegistry ()
// Register custom authentication middleware
registry . register ( 'custom-auth' , async ( c , next ) => {
const token = c . req . header ( 'x-api-token' )
if ( ! token || token !== 'secret' ) {
return c . json ({ error: 'Unauthorized' }, 401 )
}
await next ()
}, {
description: 'Custom API token authentication' ,
package: 'my-app'
})
export default createAutoDiscoveryAPI ({
agents ,
ensembles ,
})
Using Custom Middleware
# ensembles/protected-api.yaml
name : protected-api
trigger :
- type : http
path : /api/protected
middleware :
- custom-auth # Your custom middleware
- logger
Advanced Custom Middleware
import type { MiddlewareHandler } from 'hono'
import { getHttpMiddlewareRegistry } from '@ensemble-edge/conductor'
const registry = getHttpMiddlewareRegistry ()
// Request ID middleware
const requestIdMiddleware : MiddlewareHandler = async ( c , next ) => {
const requestId = c . req . header ( 'x-request-id' ) || crypto . randomUUID ()
c . set ( 'requestId' , requestId )
c . header ( 'x-request-id' , requestId )
await next ()
}
// Rate limiting middleware factory
const createRateLimitMiddleware = ( limit : number ) : MiddlewareHandler => {
const requests = new Map < string , number []>()
return async ( c , next ) => {
const ip = c . req . header ( 'cf-connecting-ip' ) || 'unknown'
const now = Date . now ()
const windowMs = 60000 // 1 minute
const existing = requests . get ( ip ) || []
const recent = existing . filter ( time => time > now - windowMs )
if ( recent . length >= limit ) {
return c . json ({ error: 'Rate limit exceeded' }, 429 )
}
recent . push ( now )
requests . set ( ip , recent )
await next ()
}
}
// Register both
registry . register ( 'request-id' , requestIdMiddleware , {
description: 'Adds unique request ID to each request' ,
})
registry . register ( 'rate-limit-strict' , createRateLimitMiddleware ( 10 ), {
description: 'Rate limit: 10 requests per minute' ,
configurable: true ,
})
Common Patterns
Production API
Full production setup with security and performance:
trigger :
- type : http
path : /api/v1/data
methods : [ GET , POST , PUT , DELETE ]
middleware :
- logger # Request logging
- secure-headers # Security
- compress # Compression
- timing # Performance metrics
auth :
type : bearer
secret : ${env.API_KEY}
rateLimit :
requests : 1000
window : 60
key : user
cors :
origin : "*"
methods : [ GET , POST , PUT , DELETE ]
allowHeaders : [ Authorization , Content-Type ]
Development API
Focused on visibility and debugging:
trigger :
- type : http
path : /dev/api
public : true
middleware :
- logger # See all requests
- pretty-json # Readable JSON
- timing # Performance metrics
Public Content Site
Optimized for caching and performance:
trigger :
- type : http
path : /blog/:slug
methods : [ GET ]
public : true
middleware :
- secure-headers # Security
- compress # Fast loading
- etag # Cache validation
cache :
enabled : true
ttl : 3600
vary : [ Accept-Encoding , Accept-Language ]
responses :
html :
enabled : true
Mixed TypeScript + YAML
You can mix both approaches in the same project:
// src/ensembles/ts-api.ts
import { logger } from 'hono/logger'
export const tsEnsemble = {
name: 'ts-api' ,
trigger: [{
type: 'http' ,
middleware: [ logger ()] // Direct function
}],
agents: [ /* ... */ ]
}
# ensembles/yaml-api.yaml
name : yaml-api
trigger :
- type : http
middleware :
- logger # String reference to same middleware
Project Organization
Option 1: Simple Projects
Register middleware in your worker entry point:
my-worker/
├── src/
│ └── index.ts # Register middleware here
├── ensembles/
│ └── api.yaml # Use middleware by name
└── conductor.config.ts
// src/index.ts
import { getHttpMiddlewareRegistry } from '@ensemble-edge/conductor'
const registry = getHttpMiddlewareRegistry ()
registry . register ( 'custom-auth' , async ( c , next ) => {
// ... middleware logic
})
export default createAutoDiscoveryAPI ({
agents ,
ensembles ,
})
Option 2: Organized Projects
Create dedicated middleware directory:
my-worker/
├── src/
│ ├── index.ts
│ └── middleware/
│ ├── index.ts # Export all middleware
│ ├── auth.ts
│ ├── logging.ts
│ └── rate-limit.ts
├── ensembles/
└── conductor.config.ts
// src/middleware/auth.ts
import type { MiddlewareHandler } from 'hono'
export const customAuth : MiddlewareHandler = async ( c , next ) => {
// ... auth logic
}
// src/middleware/index.ts
export { customAuth } from './auth.js'
export { requestLogger } from './logging.js'
export { rateLimiter } from './rate-limit.js'
// src/index.ts
import { getHttpMiddlewareRegistry } from '@ensemble-edge/conductor'
import * as middleware from './middleware/index.js'
const registry = getHttpMiddlewareRegistry ()
registry . register ( 'custom-auth' , middleware . customAuth )
registry . register ( 'request-logger' , middleware . requestLogger )
registry . register ( 'rate-limiter' , middleware . rateLimiter )
Option 3: Reusable Plugins
Package middleware in Conductor plugins:
// packages/my-middleware-plugin/src/index.ts
import type { ConductorPlugin } from '@ensemble-edge/conductor'
export const myMiddlewarePlugin : ConductorPlugin = {
name: 'my-middleware' ,
version: '1.0.0' ,
async initialize ( context ) {
const registry = context . getHttpMiddlewareRegistry ()
registry . register ( 'custom-auth' , async ( c , next ) => {
// ... auth logic
})
registry . register ( 'custom-logger' , async ( c , next ) => {
// ... logging logic
})
}
}
Debugging Middleware
List all registered middleware:
import { getHttpMiddlewareRegistry } from '@ensemble-edge/conductor'
const registry = getHttpMiddlewareRegistry ()
// List all middleware names
console . log ( 'Available middleware:' , registry . list ())
// Output: ['logger', 'compress', 'timing', 'secure-headers', 'pretty-json', 'etag', 'custom-auth']
// Get metadata for all middleware
const metadata = registry . getAllMetadata ()
console . log ( metadata )
// Output: [
// { name: 'logger', description: 'HTTP request/response logger', package: 'hono/logger' },
// { name: 'compress', description: 'Response compression (gzip, brotli)', package: 'hono/compress' },
// ...
// ]
API Reference
HttpMiddlewareRegistry
class HttpMiddlewareRegistry {
// Register middleware
register (
name : string ,
handler : MiddlewareHandler ,
metadata ?: {
description ?: string
package ?: string
configurable ?: boolean
}
) : void
// Get middleware by name
get ( name : string ) : MiddlewareHandler | undefined
// Check if registered
has ( name : string ) : boolean
// List all names
list () : string []
// Get all metadata
getAllMetadata () : HttpMiddlewareMetadata []
// Resolve mixed array (used internally)
resolve ( middlewareArray : ( string | MiddlewareHandler )[]) : MiddlewareHandler []
}
getHttpMiddlewareRegistry()
Get the global singleton registry instance:
import { getHttpMiddlewareRegistry } from '@ensemble-edge/conductor'
const registry = getHttpMiddlewareRegistry ()
Best Practices
Order Matters - Place security middleware (secure-headers) early, compression (compress) late
YAML for Production - Use named middleware in YAML for better visibility and consistency
TypeScript for Prototyping - Use direct functions for one-off custom logic
Register Once - Register custom middleware during app initialization, not per-request
Performance - Only use middleware you need - each adds latency
Testing - Test middleware in isolation before adding to production ensembles
Next Steps
Triggers Full trigger reference
Writing Ensembles Ensemble patterns
Testing & Observability Monitor your APIs
Hono Middleware Official Hono docs