Dhal v1.1 ships dedicated adapters for Express, Fastify, NestJS, Koa, Hono on Node.js, and raw node:http. Every adapter reuses the same Dhal engine, configuration model, stores, telemetry, and decision lifecycle.
Express
import express from "express";
import { dhal } from "@rokadhq/dhal/express";
const app = express();
app.use(express.json({ limit: "1mb" }));
app.use(dhal({ configPath: "dhal.json" }));Place body parsers before Dhal when body-aware rules should inspect parsed payloads. The adapter reads req.body, an optional req.rawBody, identity fields, and the final response status.
Use dhalFromEngine(engine) when you create the engine yourself for custom stores, telemetry, or lifecycle control.
Fastify
import Fastify from "fastify";
import { dhalFastify } from "@rokadhq/dhal/fastify";
const app = Fastify();
await app.register(dhalFastify({ configPath: "dhal.json" }));Dhal inspects requests in preHandler and records response outcomes in onResponse. Use dhalFastifyFromEngine(engine) with a pre-created engine.
NestJS
import { NestFactory } from "@nestjs/core";
import { installDhalNest } from "@rokadhq/dhal/nest";
import { AppModule } from "./app.module.js";
const app = await NestFactory.create(AppModule);
const dhal = await installDhalNest(app, { configPath: "dhal.json" });
await app.listen(3000);Install Dhal after creating the Nest application and before app.listen(). Dhal detects whether the Nest HTTP application uses Express or Fastify and installs the corresponding stable adapter.
For an existing engine:
import { installDhalNestFromEngine } from "@rokadhq/dhal/nest";
const installation = await installDhalNestFromEngine(app, engine);The returned installation exposes engine, the detected platform, and close(). Nest microservice transports without an HTTP adapter are outside this integration.
Koa
import Koa from "koa";
import { dhalKoa } from "@rokadhq/dhal/koa";
const app = new Koa();
app.use(dhalKoa({ configPath: "dhal.json" }));Register Dhal before application routes. The adapter:
- inspects the request before downstream middleware;
- terminates the middleware chain for blocked requests;
- sets
x-dhal-actionandx-dhal-rule; - records the downstream response status;
- reads optional identities from
context.state.userId,tenantId,apiKeyId, orcontext.state.user.id.
Use dhalKoaFromEngine(engine) for custom lifecycle management.
Hono on Node.js
import { Hono } from "hono";
import { dhalHono } from "@rokadhq/dhal/hono";
const app = new Hono();
app.use("*", dhalHono({ configPath: "dhal.json" }));The Hono adapter consumes standard Web Request and Response objects, returns a controlled response when Dhal blocks, and records the final downstream response status. Identity values can be exposed through context.var.userId, tenantId, apiKeyId, or context.var.user.id.
The supported v1.1 target is Hono running on Node.js. Edge runtimes are not included in the compatibility commitment. Use dhalHonoFromEngine(engine) for an existing engine.
Raw node:http
import http from "node:http";
import { createNodeHttpDhal } from "@rokadhq/dhal/node-http";
const protection = createNodeHttpDhal({ configPath: "dhal.json" });
const server = http.createServer(async (req, res) => {
const decision = await protection.inspect(req, res);
if (decision.action === "block") return;
res.statusCode = 200;
res.end("ok");
});The raw adapter normalizes method, URL, path, headers, client IP, identity headers, and content-length. It does not consume the request stream.
Custom integrations with the core engine
import { createDhal, type DhalRequest } from "@rokadhq/dhal";
const protection = createDhal({ configPath: "dhal.json" });
const request: DhalRequest = {
method: "POST",
url: "/api/orders?source=web",
path: "/api/orders",
route: "/api/orders",
headers: { "content-type": "application/json" },
ip: "203.0.113.10",
body: { productId: "sku_123", quantity: 1 },
userId: "user_123",
tenantId: "tenant_456"
};
const decision = await protection.inspect(request);After the application response is known, call recordOutcome() so response-aware signals can be updated:
await protection.recordOutcome(request, { statusCode: 401 });Lifecycle and blocking responses
Create the engine yourself when you need explicit shutdown control:
process.once("SIGTERM", async () => {
await protection.close(5_000);
process.exit(0);
});Bundled adapters set x-dhal-action: block and x-dhal-rule: <rule id>, then return the configured status code and controlled JSON response. Do not expose private decision metadata to untrusted clients.