Framework Adapters
Hono
Hono adapter for edge runtimes and Cloudflare Workers
Hono#
Surf provides first-class Hono support via @surfjs/core. The honoApp and honoMiddleware functions use the Web Streams API for SSE, making them compatible with Cloudflare Workers, Deno, Bun, and any edge runtime.
pnpm add @surfjs/core honoSub-App (Recommended)
Mount Surf as a Hono sub-app. Note that honoApp is async — always await it:
// server.tsimport { Hono } from 'hono'import { createSurf, honoApp } from '@surfjs/core' const surf = await createSurf({ name: 'My API', commands: { search: { description: 'Search items', params: { query: { type: 'string', required: true }, limit: { type: 'number', default: 10 }, }, run: async ({ query, limit }) => { return db.items.search(query, { limit }) }, }, },}) const app = new Hono()app.route('/', await honoApp(surf)) export default appCloudflare Worker
For Cloudflare Workers, use honoMiddleware to get a fetch handler. Again, honoMiddleware is async — use top-level await or initialize in a startup phase:
// worker.tsimport { createSurf, honoMiddleware } from '@surfjs/core' const surf = await createSurf({ name: 'Edge API', commands: { ping: { description: 'Health check', run: async () => ({ pong: true, region: 'auto' }), }, },}) // Returns a standard fetch handlerexport default { fetch: await honoMiddleware(surf) }Sync Variant (No Dynamic Import)
If you need synchronous initialization — useful in environments where top-level await is constrained — pass the Hono constructor directly using honoAppSync:
import { Hono } from 'hono'import { createSurf } from '@surfjs/core'import { honoAppSync } from '@surfjs/core/hono' const surf = await createSurf({ name: 'My API', commands: { /* ... */ } }) // Pass the Hono constructor directly — no dynamic import, fully synchronousconst surfSubApp = honoAppSync(surf, Hono)const app = new Hono()app.route('/', surfSubApp) export default app💡 Tip:
honoAppis async because it dynamically imports Hono.honoAppSyncavoids this by accepting the Hono constructor directly — useful in environments where top-level await is constrained or dynamic imports are problematic.
Composing with Existing Hono Routes
Surf mounts under the root path. You can freely add other routes to your Hono app:
import { Hono } from 'hono'import { cors } from 'hono/cors'import { createSurf } from '@surfjs/core'import { honoApp } from '@surfjs/core' const surf = await createSurf({ /* ... */ }) const app = new Hono() // Your existing middlewareapp.use('*', cors()) // Your existing routesapp.get('/health', (c) => c.json({ ok: true }))app.get('/api/v1/users', (c) => getUsersHandler(c)) // Mount Surf routes alongside your APIapp.route('/', await honoApp(surf)) export default appWith Authentication
Pass an authVerifier to createSurf. The Hono adapter extracts the Authorization header automatically and populates ctx.claims:
import { authFailed } from '@surfjs/core' const surf = await createSurf({ name: 'Protected API', auth: { type: 'bearer', description: 'API token' }, authVerifier: async (token, command) => { const user = await db.users.findByToken(token) if (!user) return { valid: false, reason: 'Invalid token' } return { valid: true, claims: { userId: user.id, role: user.role } } }, commands: { 'profile.get': { description: 'Get the authenticated user profile', auth: 'required', run: async (_params, ctx) => { return db.users.findById(ctx.claims!.userId) }, }, 'admin.stats': { description: 'Server statistics (admin only)', auth: 'required', run: async (_params, ctx) => { if (ctx.claims!.role !== 'admin') { throw authFailed('Admin access required') } return db.getStats() }, }, },})Endpoints
The Hono adapter mounts the following routes:
| Method | Path | Description |
|--------|------|-------------|
| GET | /.well-known/surf.json | Manifest (with ETag/304 caching) |
| POST | /surf/execute | Command execution (supports SSE streaming) |
| POST | /surf/pipeline | Pipeline execution |
| POST | /surf/session/start | Start a session |
| POST | /surf/session/end | End a session |
Edge Runtime Notes
| Feature | Cloudflare Workers | Deno / Bun | |---------|-------------------|------------| | Manifest + Discovery | ✅ | ✅ | | Command execution | ✅ | ✅ | | SSE Streaming | ✅ (Web Streams) | ✅ | | WebSocket + Surf Live | ⚠️ Requires custom implementation | ✅ | | Sessions | ✅ (KV or D1 store) | ✅ |
💡 Tip: Surf's built-in WebSocket transport uses the
wsnpm package, which is not available in Cloudflare Workers. For WebSocket support on Workers, you'd need a custom implementation using Durable Objects.