Skip to content

Elysia

bash
npm install elysia x511

Full example

ts
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.

ts
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.

ts
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

ts
.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-Elysia instance and call .use(...) from the parent; this keeps the 511 page from being returned for public endpoints.
  • Order matters — register verify before verified. The reverse will route X511's internal POST /x511/verify/self (etc.) through verified, which then refuses it as unauthenticated.
  • Streaming responses — the SSE endpoint returns a streaming Response. Elysia respects this without extra configuration when verify returns 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).