Hono integration
This guide assumes a Hono application running on Node.js 18+, Cloudflare Workers, or Bun. By the end you'll have per-request flag evaluation available on every route via the Hono context variable, using @avsbhq/node and the @avsbhq/utils/middleware/hono adapter.
Install
1npm install @avsbhq/node@^1 @avsbhq/utils@^1@avsbhq/edge/cloudflare instead for a lighter-weight client that uses KV for datafile caching.Obtain your SDK key
Open Settings > Environments in your A vs B project and copy the server-side SDK key:
1AVSB_SDK_KEY=sdk_production_xxxxxxxxxxxxxxxxInitialise the server SDK
1import { AvsbServer } from '@avsbhq/node'2
3export const avsb = new AvsbServer({ sdkKey: process.env.AVSB_SDK_KEY! })4
5export async function waitForAvsb(): Promise<void> {6 const result = await avsb.onReady()7 if (!result.success && !result.degraded) {8 console.warn('[avsb] degraded init', result.error)9 }10}Mount the middleware
The honoMiddleware from @avsbhq/utils/middleware/hono stores the per-request UserBoundClient in the Hono context variable c.var.avsb.
1import { Hono } from 'hono'2import { honoMiddleware } from '@avsbhq/utils/middleware/hono'3import { avsb } from './avsb'4
5const app = new Hono()6
7// Mount before route handlers.8app.use(9 '*',10 honoMiddleware(avsb, {11 contextFrom: (c) => {12 const uid = c.req.header('x-user-id')13 if (!uid) return undefined14 return { kind: 'user', key: uid }15 },16 withDecisionLog: true,17 })18)19
20export default apphonoMiddleware is also available from @avsbhq/node as a single-package alternative.Read a flag in a route handler
1import { Hono } from 'hono'2
3const checkout = new Hono()4
5checkout.post('/session', (c) => {6 const checkoutV2 = c.var.avsb.getBoolFlag('checkout-v2', false)7
8 return c.json({9 flow: checkoutV2.value ? 'v2' : 'legacy',10 variationKey: checkoutV2.variationKey ?? null,11 })12})13
14export default checkoutTrack an event
1checkout.post('/complete', async (c) => {2 const { amount } = await c.req.json<{ amount: number }>()3
4 c.var.avsb.track('purchase', {5 value: amount,6 properties: { runtime: 'hono' },7 })8
9 return c.json({ success: true })10})Use AsyncLocalStorage for implicit context
Service functions that do not receive the Hono context can still access the current user's client via getRequestClient:
1import { getRequestClient } from '@avsbhq/utils'2
3export function getDynamicPrice(basePrice: number): number {4 const client = getRequestClient()5 const pricingFlag = client.getStringFlag('dynamic-pricing', 'standard')6
7 return pricingFlag.value === 'surge' ? basePrice * 1.2 : basePrice8}Graceful shutdown
For Node.js deployments, handle SIGTERM to flush events before exit:
1import { serve } from '@hono/node-server'2import { avsb, waitForAvsb } from './avsb'3import app from './app'4
5await waitForAvsb()6
7const server = serve({ fetch: app.fetch, port: 3000 })8
9process.on('SIGTERM', async () => {10 server.close(async () => {11 await avsb.close()12 process.exit(0)13 })14})On Cloudflare Workers, use ctx.waitUntil(avsb.flush()) at the end of each request handler to flush events without blocking the response.
Testing
1import { createMockClient, TestData } from '@avsbhq/test'2import { testClient } from 'hono/testing'3import app from './app'4
5const td = TestData.flag('checkout-v2').booleanFlag().fallthroughVariation(true)6const mockAvsb = createMockClient({ flags: [td.build()] })7
8// Replace the middleware's server reference for the test.9const res = await testClient(app).checkout.session.$post(10 {},11 { headers: { 'x-user-id': 'u_test' } }12)13
14expect((await res.json()).flow).toBe('v2')What's next
- Cloudflare Workers integration — lighter-weight edge client for Workers deployments.
- Multi-context identity
@avsbhq/nodeAPI reference