Practical use cases with working code examples.
Authenticate every request globally before it reaches any handler.
import { Orvaxis, createExpressServer } from "orvaxis"
import type { Policy } from "orvaxis"
const requireApiKey: Policy = {
name: "require-api-key",
priority: 100,
evaluate(ctx) {
const key = ctx.req.headers["x-api-key"]
if (!key || typeof key !== "string")
return { allow: false, reason: "Missing X-API-Key", status: 401 }
return { allow: true, modify: { apiKey: key } }
},
}
const app = new Orvaxis()
app.policy(requireApiKey)
app.group({
prefix: "/api",
routes: [
{
method: "GET",
path: "/me",
handler: async (ctx) => {
ctx.res.json({ apiKey: ctx.meta.apiKey })
},
},
],
})
createExpressServer(app).listen(3000)Notes:
modifyinjects data intoctx.meta, available to all downstream handlers.- Global policies run before group and route policies, in priority order.
- To scope a policy to specific routes, use
scope.
Separate authentication (who you are) from authorization (what you can do) using two policies.
import type { Policy } from "orvaxis"
const authenticate: Policy = {
name: "authenticate",
priority: 100,
async evaluate(ctx) {
const token = ctx.req.headers["authorization"]?.replace("Bearer ", "")
if (!token) return { allow: false, reason: "Unauthenticated", status: 401 }
// decode/verify your JWT here
const user = verifyToken(token) // { id, role }
return { allow: true, modify: { user } }
},
}
const requireAdmin: Policy = {
name: "require-admin",
priority: 10,
scope: { path: /^\/admin/ },
async evaluate(ctx) {
if (ctx.meta.user?.role !== "admin")
return { allow: false, reason: "Forbidden", status: 403 }
return { allow: true }
},
}
app.policy(authenticate)
app.policy(requireAdmin)Notes:
authenticateruns first (higher priority) and populatesctx.meta.user.requireAdminis scoped to/admin/*paths — other routes are unaffected.- To use typed context, define
type AppMeta = { user: { id: string; role: string } }andOrvaxisContext<{}, AppMeta>.
Apply a policy only to specific paths or methods without modifying route definitions.
const rateLimitApi: Policy = {
name: "rate-limit",
priority: 50,
scope: {
path: "/api", // prefix: matches /api and all sub-paths
method: "POST", // only POST requests
},
async evaluate(ctx) {
const ip = String(ctx.req.headers["x-forwarded-for"] ?? "unknown")
const allowed = await checkRateLimit(ip)
if (!allowed) return { allow: false, reason: "Too many requests", status: 429 }
return { allow: true }
},
}scope.path accepts:
string— prefix match:"/api"matches/apiand all sub-paths (/api/v1/users) but not/apiv2RegExp— pattern match (e.g./^\/api/for all paths under/api)(path: string) => boolean— custom predicate for complex rules (e.g. exclude a specific sub-path)
scope.method is one of GET | POST | PUT | DELETE | PATCH | HEAD | OPTIONS.
Gate access to experimental features with zero changes to route handlers.
const betaAccess: Policy = {
name: "beta-access",
priority: 20,
scope: { path: /^\/beta/ },
async evaluate(ctx) {
const userId = ctx.meta.user?.id
const enabled = await isFeatureEnabled("beta", userId)
if (!enabled) return { allow: false, reason: "Feature not available", status: 404 }
return { allow: true, modify: { beta: true } }
},
}Handlers under /beta/* receive ctx.meta.beta === true and can branch accordingly. All other routes are unaffected.
Every request automatically produces a structured trace. Use traceMiddleware and traceEvent to enrich it.
import { Orvaxis, traceMiddleware, traceEvent, buildExecutionSummary, createExpressServer } from "orvaxis"
const app = new Orvaxis()
app.debugger.enable() // optional: adds internal lifecycle events
app.on("afterPipeline", (ctx) => {
const summary = buildExecutionSummary(ctx)
console.log({
requestId: summary.requestId,
route: summary.route,
duration: `${summary.duration}ms`,
events: summary.traceEvents,
})
})
app.group({
prefix: "/api",
middleware: [traceMiddleware()], // records timing for each middleware
routes: [
{
method: "GET",
path: "/users",
handler: async (ctx) => {
traceEvent("db:query", { table: "users" }) // emit custom event from anywhere in the call chain
ctx.res.json({ users: [] })
},
},
],
})
createExpressServer(app).listen(3000)buildExecutionSummary returns:
| Field | Description |
|---|---|
requestId |
Unique ID per request |
duration |
Total ms from start to afterPipeline |
traceEvents |
Custom + middleware timing events |
debugSteps |
Internal lifecycle steps (requires debugger.enable()) |
route |
Matched route and group |
traceEvent is a no-op outside a request scope — safe to call from shared service functions.
Log every request with timing, status, and error info using lifecycle hooks.
import { Orvaxis, createExpressServer } from "orvaxis"
const app = new Orvaxis()
app.on("onRequest", (ctx) => {
ctx.meta.startedAt = Date.now()
})
app.on("afterPipeline", (ctx) => {
const ms = Date.now() - (ctx.meta.startedAt as number)
console.log(`[OK] ${ctx.req.method} ${ctx.req.path} ${ctx.res.statusCode} ${ms}ms — ${ctx.meta.trace?.requestId}`)
})
app.on("onError", (ctx, err) => {
const ms = Date.now() - (ctx.meta.startedAt as number ?? 0)
const status = (err as { status?: number }).status ?? 500
console.error(`[ERR] ${ctx.req.method} ${ctx.req.path} ${status} ${ms}ms — ${err?.message}`)
})ctx.res.statusCode reflects the last value set via ctx.res.status(code). Defaults to 200 if status() was never called.
Apply shared middleware to an entire group of routes.
import { createExpressServer } from "orvaxis"
import express from "express"
import type { Middleware } from "orvaxis"
// Assign a correlation ID to every request
const correlationId: Middleware = async (ctx, next) => {
ctx.meta.correlationId = ctx.req.headers["x-correlation-id"] ?? crypto.randomUUID()
await next()
}
app.group({
prefix: "/api",
middleware: [correlationId],
routes: [...],
})Body parsing with Express: since createExpressServer accepts an existing Express app, you can add framework-level middleware before Orvaxis takes over:
import express from "express"
import { Orvaxis, createExpressServer } from "orvaxis"
const expressApp = express()
expressApp.use(express.json()) // parse JSON bodies
expressApp.use(express.urlencoded({ extended: true })) // parse form bodies
const app = new Orvaxis()
// ... define groups and routes ...
createExpressServer(app, expressApp).listen(3000)
// Body is now accessible as ctx.req.body inside handlersSame pattern for Fastify plugins:
import Fastify from "fastify"
import multipart from "@fastify/multipart"
import { Orvaxis, createFastifyServer } from "orvaxis"
const fastifyApp = Fastify()
await fastifyApp.register(multipart)
const app = new Orvaxis()
createFastifyServer(app, fastifyApp).listen(3000)Use prefix "/" for a root-level group. Useful for health and readiness endpoints that must bypass authentication.
// Register the health group BEFORE adding global policies
const app = new Orvaxis()
app.group({
prefix: "/",
routes: [
{
method: "GET",
path: "/health",
handler: async (ctx) => {
ctx.res.json({ status: "ok" })
},
},
],
})
// Global policies added after — they still apply to all other groups
app.policy(requireApiKey)
app.group({ prefix: "/api", routes: [...] })Global policies apply to all routes regardless of registration order. To exclude specific routes from a global policy, use
scopeon the policy.
Encapsulate cross-cutting behavior (logging, metrics, tracing) into a reusable plugin.
import type { Plugin } from "orvaxis"
export const metricsPlugin: Plugin = {
name: "metrics",
apply(runtime) {
runtime.hooks.on("onRequest", (ctx) => {
ctx.meta.startedAt = Date.now()
})
runtime.hooks.on("afterPipeline", (ctx) => {
const ms = Date.now() - (ctx.meta.startedAt as number)
recordMetric(ctx.req.path, ctx.req.method, ms)
})
runtime.hooks.on("onError", (_ctx, err) => {
recordError(err?.message ?? "unknown")
})
},
}
// Usage
app.register(metricsPlugin)Add compile-time types to ctx.state and ctx.meta for full IDE support.
import type { OrvaxisContext } from "orvaxis"
type AppState = {
user: { id: string; role: "admin" | "user" }
}
type AppMeta = {
apiKey: string
correlationId: string
}
type AppContext = OrvaxisContext<AppState, AppMeta>
const handler = async (ctx: AppContext) => {
ctx.state.user.role // "admin" | "user"
ctx.meta.apiKey // string
ctx.meta.correlationId // string
ctx.meta.trace // TracerLike — still available from ContextMeta
}Use getContext() to access the typed context from anywhere in the async call chain:
import { getContext } from "orvaxis"
async function getCurrentUser() {
const ctx = getContext() as AppContext | undefined
return ctx?.state.user
}| Area | Detail |
|---|---|
| Body parsing | No built-in body parsing. Use createExpressServer(app, expressApp) with express.json() pre-registered (see use case 7). |
| Rate limiting | No built-in counter/storage. Implement using any in-memory map or Redis client inside a policy. |
Policy scope path: string |
Prefix match — "/api" covers /api and all sub-paths. Use a predicate function for exclusions: p => p.startsWith("/admin") && !p.startsWith("/admin/public"). |
| Response body interception | Orvaxis does not intercept or transform outgoing response bodies. Handlers write the body directly via ctx.res.json() / ctx.res.send(). Response headers, however, can be set from any middleware via ctx.res.setHeader(). |