Koa
Add Nyoxis threat detection to a Koa application using a middleware function.
Prerequisites
- Node.js 18 or later
- Koa 2 or later
- A Nyoxis workspace API key — get one here
No extra install required
Uses the native fetch API available in Node.js 18+.
Middleware
Create middleware/nyoxis.js:
javascript
const NYO_API = "https://api.nyoxis.com";
/**
* Nyoxis WAF middleware for Koa.
*
* @param {object} options
* @param {string} options.apiKey - Your workspace API token.
* @param {boolean} [options.blockOnHigh=false] - Reject requests with risk "high".
* @param {string} [options.onError="open"] - "open" | "closed"
*/
function nyoxisWAF({ apiKey, blockOnHigh = false, onError = "open" } = {}) {
if (!apiKey) throw new Error("nyoxis: apiKey is required");
return async function nyoxisMiddleware(ctx, next) {
const url = new URL(ctx.url, `http://${ctx.host}`);
const payload = {
method: ctx.method,
path: ctx.path,
query: url.search ? url.search.slice(1) : undefined,
headers: ctx.headers,
ip_addr: ctx.ip,
body:
typeof ctx.request.body === "string"
? ctx.request.body
: ctx.request.body != null
? JSON.stringify(ctx.request.body)
: undefined,
};
try {
const response = await fetch(`${NYO_API}/v0/predict?api_key=${apiKey}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(3000),
});
const verdict = await response.json();
// Attach verdict to Koa context state
ctx.state.nyoxis = verdict;
if (blockOnHigh && verdict.prediction?.risk === "high") {
ctx.status = 403;
ctx.body = { error: "Forbidden" };
return;
}
} catch (err) {
console.warn("[nyoxis] prediction error:", err.message);
if (onError === "closed") {
ctx.status = 503;
ctx.body = { error: "Service unavailable" };
return;
}
}
await next();
};
}
module.exports = { nyoxisWAF };Register the middleware
javascript
const Koa = require("koa");
const Router = require("@koa/router");
const bodyParser = require("koa-bodyparser");
const { nyoxisWAF } = require("./middleware/nyoxis");
const app = new Koa();
const router = new Router();
app.use(bodyParser());
app.use(nyoxisWAF({ apiKey: process.env.NYOXIS_API_KEY, blockOnHigh: true }));
router.get("/", (ctx) => {
const risk = ctx.state.nyoxis?.prediction?.risk ?? "unknown";
ctx.body = { ok: true, risk };
});
app.use(router.routes());
app.listen(3000);Acting on the verdict
javascript
router.get("/sensitive", (ctx) => {
const verdict = ctx.state.nyoxis ?? {};
const prediction = verdict.prediction ?? {};
if (["medium", "high"].includes(prediction.risk)) {
ctx.status = 403;
ctx.body = { error: "Forbidden" };
return;
}
const xss = prediction.attacks?.find((a) => a.kind === "xss");
if (xss && xss.confidence > 0.8) {
ctx.app.emit("security:alert", {
kind: "xss",
path: ctx.path,
ip: ctx.ip,
});
}
ctx.body = { data: "sensitive content" };
});TypeScript
For TypeScript projects, extend Koa's DefaultState to type the verdict:
typescript
import type Application from "koa";
import type Router from "@koa/router";
declare module "koa" {
interface DefaultState {
nyoxis?: {
is_cached: boolean;
prediction?: {
risk: "none" | "low" | "medium" | "high";
risk_score: number;
attacks: Array<{ kind: string; confidence: number }>;
};
};
}
}Next steps
- API Reference — complete field descriptions and status codes.
- Overview — how the classifier and redaction pipeline work.