Rokad

Framework integrations

Integrate Dhal v1.1 with Express, Fastify, NestJS, Koa, Hono, node:http, or the core engine.

View repository
dhal documentation
Page 3 of 9

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

ts
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

ts
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

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

ts
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

ts
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-action and x-dhal-rule;
  • records the downstream response status;
  • reads optional identities from context.state.userId, tenantId, apiKeyId, or context.state.user.id.

Use dhalKoaFromEngine(engine) for custom lifecycle management.

Hono on Node.js

ts
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

ts
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

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

ts
await protection.recordOutcome(request, { statusCode: 401 });

Lifecycle and blocking responses

Create the engine yourself when you need explicit shutdown control:

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