Koa (@ops-ai/toggly-koa)
Examples and requirements
Start with the Koa SDK sample. Its README explains the setup, then src/catalog.js names the flags and filter inputs, src/app.js shows request-bound evaluation and Order checks, and test/http.test.js demonstrates the expected enabled, disabled, and isolated responses. Use the Samples catalog to find the other runnable SDK examples.
Identity, groups, and claims are targeting inputs, not authentication. Populate them from your trusted session; a caller-controlled identity header does not authorize a user.
Requires Node.js 18+ and supports Koa 2.x or 3.x (^2.0.0 || ^3.0.0).
Install
npm install @ops-ai/toggly-koa
Middleware
import Koa from 'koa'
import Router from '@koa/router'
import { togglyMiddleware, featureGate, closeKoaToggly } from '@ops-ai/toggly-koa'
const app = new Koa()
const router = new Router()
app.use(
togglyMiddleware({
appKey: process.env.TOGGLY_APP_KEY!,
environment: 'Production',
verifySignatures: true,
enableStreaming: true,
}),
)
router.get('/dashboard', async (ctx) => {
if (await ctx.state.toggly.isFeatureOn('new-dashboard')) {
ctx.body = { version: 'v2' }
return
}
ctx.body = { version: 'v1' }
})
router.get(
'/beta',
featureGate({ featureKey: 'beta-access' }),
async (ctx) => {
ctx.body = { ok: true }
},
)
app.use(router.routes())
process.on('SIGTERM', () => closeKoaToggly())
Helpers live on ctx.state.toggly.
Ambient EvalContext (recommended)
Configure identity, groups, and claims once on middleware. Segment
request.* fields are filled from headers via fromHttpRequest. Then
ctx.state.toggly.isFeatureOn('X') needs no context argument.
app.use(
togglyMiddleware({
appKey: process.env.TOGGLY_APP_KEY!,
getIdentity: (ctx) => ctx.state.user?.id,
getGroups: (ctx) => ctx.state.user?.roles ?? [],
getClaims: (ctx) => ({ role: ctx.state.user?.role ?? '' }),
// Or getContext: (ctx) => ({ identity, groups, claims, request })
}),
)
router.get('/checkout', async (ctx) => {
if (await ctx.state.toggly.isFeatureOn('MobileCheckout')) {
ctx.body = { flow: 'mobile' }
return
}
ctx.body = { flow: 'desktop' }
})
Default identity (when getIdentity is unset): x-toggly-identity header.
When you supply getContext, missing request keys are still filled from
headers. Prefer getIdentity / getGroups / getClaims for the happy path.
Overrides and entity checks
In the adapter, the request helper isFeatureOn(key) accepts only the flag key and uses the middleware's context. Passing a second argument does not override that context. Call its core client explicitly and merge the fields you want to preserve:
const toggly = ctx.state.toggly
const evaluation = {
...toggly.context,
identity: 'impersonated-user',
claims: { ...toggly.context.claims, role: 'admin' },
}
await toggly.client.isFeatureOn('MobileCheckout', evaluation)
// A ready entity context keeps Order data separate from user targeting.
await toggly.client.isFeatureOn('OrderBadge', toggly.context, {
kind: 'Order',
key: 'ORD-42',
attributes: { Status: 'Paid' },
})
The entity kind/property must match the feature definition. See Node entity context for mapper registration and HTTP segment filters for request fields.
Feature gates
Same gate options as Express. Also: featureRoutes, withFeature, featuresHandler(), getKoaToggly.
Core config: Configuration.