Elysia
npm install elysia x511Full example
import { html } from '@elysiajs/html'
import { Elysia } from 'elysia'
import { x511, self, zkpassport } from 'x511/elysia'
const { verify, verified } = x511({
domain: 'https://your-app.example.com',
basePath: '/x511',
dev: true,
mode: { type: 'session', ttl: 1800 },
disclosures: {
minAge: 18,
nationality: { excluded: ['RUS'] },
},
providers: [self({ scope: 'your-app', appName: 'Your App' }), zkpassport()],
})
new Elysia()
.use(html())
.onRequest(verify)
.onBeforeHandle({ as: 'scoped' }, verified)
.get('/adults-only', ({ store }) => {
const identity = (store as any).x511 as { uniqueId?: string } | undefined
return `Welcome, ${identity?.uniqueId ?? 'anon'}!`
})
.listen(3000)Mounting
verify is registered with .onRequest(verify). It receives Elysia's context, extracts ctx.request, and returns a Response if the request matches an internal route (Elysia uses that as the response). Otherwise it returns undefined and the regular handler chain runs.
new Elysia().onRequest(verify)verified is registered as a beforeHandle hook. When the cookie is valid it writes ctx.store.x511 = { uniqueId } and returns undefined (handler runs). When unverified it sets status 511, sets Content-Type: text/html, and returns the HTML body.
new Elysia().onBeforeHandle({ as: 'scoped' }, verified)Scope of { as: 'scoped' }
The hook applies to all routes on the same Elysia instance registered after .onBeforeHandle(...). If you only want to protect specific routes, use Elysia's .guard() to scope the hook to a subset, or split protected and public routes across separate Elysia instances and .use() them together.
Reading the identity
.get('/me', ({ store }) => {
const { uniqueId } = (store as any).x511 ?? {}
return { uniqueId }
})ctx.store is Record<string, unknown> by default; cast or augment it as you would for any other key you write into Elysia's store.
Tips
- Selective protection — wrap protected routes in a sub-
Elysiainstance and call.use(...)from the parent; this keeps the 511 page from being returned for public endpoints. - Order matters — register
verifybeforeverified. The reverse will route X511's internalPOST /x511/verify/self(etc.) throughverified, which then refuses it as unauthenticated. - Streaming responses — the SSE endpoint returns a streaming
Response. Elysia respects this without extra configuration whenverifyreturns it from.onRequest.
Native Request required
Internally the Elysia adapter reads ctx.request, which must be a native WinterTC-compatible Request instance. Elysia satisfies this on Bun and Node (via the standard adapter).