DoclightDocs

Express

Instrument incoming website requests with @doclight/express 0.1.0, and understand what that request evidence does and does not show.

@doclight/express is Express middleware that queues one event for each finished request. Version covered: npm 0.1.0. Source: sdk-express · npm.

Install

pnpm add @doclight/[email protected]

Requires Node.js >=18 and Express 4 or 5.

Configure credentials

Set credentials in the server environment, never in browser code:

export DOCLIGHT_API_KEY="<your-ingestion-key>"
export DOCLIGHT_PROJECT_ID="<your-ingestion-project-id>"
export DOCLIGHT_ENDPOINT="<compatible-ingestion-base-url>"

DOCLIGHT_* names are only a convention used by these examples; the SDK reads apiKey, projectId and endpoint from its configuration, so pass the values explicitly. DOCLIGHT_ENDPOINT is a base URL without the batch path (the default is https://ingest.doclight.app). Obtain a key and project ID accepted by your ingestion service; they are not your aeok_ API credential.

Register the middleware

Register it once, before the routes you want to observe. Use an ESM file (.mjs or "type": "module").

import express from "express";
import { doclightMiddleware } from "@doclight/express";

function required(name) {
  const value = process.env[name];
  if (!value) throw new Error(`${name} is required`);
  return value;
}

const app = express();
const telemetry = doclightMiddleware({
  apiKey: required("DOCLIGHT_API_KEY"),
  projectId: required("DOCLIGHT_PROJECT_ID"),
  endpoint: required("DOCLIGHT_ENDPOINT"),
  express: {
    ignoreRoutes: ["/healthz", "/readyz"],
    sessionHeader: "x-doclight-session-id",
    agentHeader: "x-agent-id",
  },
});

app.use(telemetry);
app.get("/pricing", (_req, res) => res.json({ available: true }));
app.get("/products/:id", (_req, res) => res.json({ available: true }));
app.listen(3000);

What 0.1.0 records

On the response's finish event, the middleware queues one api_called event:

FieldMeaning
apiEndpointExpress route template when available, otherwise a normalized path
httpMethodRequest method
durationMsElapsed request time
statussuccess for status below 400, otherwise failed
metadata.statusCodeHTTP status as a string
metadata.routePatternThe recorded route pattern
errorTypeunauthorized (401), forbidden (403), not_found (404), timeout (408), rate_limited (429), server_error (5xx)
sessionIdThe session header value, or a generated per-request ID
agentTypeThe agent header value, when present

Other 4xx responses still get status: "failed" without an errorType.

Not in 0.1.0

0.1.0 records all non-ignored requests. It does not classify User-Agent tokens, collect referrer origins, report aborted requests, or expose flush() / shutdown() on the middleware. Do not pass express.collect; that option exists only in repository code. captureRoutePattern is declared in the types but not used by the published middleware.

Options

OptionBehavior in 0.1.0
express.ignoreRoutesExact match on the path, without the query string
express.ignoreUserAgentsSubstring match on the User-Agent header
express.sessionHeaderHeader read for the session ID (default x-doclight-session-id)
express.agentHeaderHeader read for agentType (default x-agent-id)

All Node client options (transport, privacy, debug, ...) can be passed alongside.

Optional agent-only collection

To limit collection to likely AI agents in 0.1.0, wrap the middleware in a filter you own:

const observedTokens = [
  "gptbot", "chatgpt-user", "oai-searchbot",
  "claudebot", "claude-user", "claude-searchbot",
  "perplexitybot", "perplexity-user",
];

app.use((req, res, next) => {
  const ua = (req.get("user-agent") || "").slice(0, 512).toLowerCase();
  const matched = observedTokens.some((token) => ua.includes(token));
  if (!matched) return next();
  return telemetry(req, res, next);
});

This is your application code, not SDK classification. It filters likely traffic but does not verify identity or fill in agentType.

Correlation and its limits

  • An agent under your control can send x-doclight-session-id and x-agent-id. Non-empty string values are used as-is, so validate or strip untrusted values in your own code first. Never use email addresses, tokens or customer names.
  • Without a session header, every request gets its own generated session ID. Independent requests are not a multi-step agent journey.
  • User-Agent and x-agent-id are client-controlled and can be spoofed. Treat them as claimed identities.
  • The built-in route fallback replaces some identifier-like segments with :id; it is not a personal-data scrubber. Prefer route templates and exclude sensitive paths.

Verify safely

With your app running, send a synthetic request:

curl -H 'User-Agent: GPTBot' \
  -H 'x-agent-id: test-crawler' \
  -H 'x-doclight-session-id: sdk-smoke-001' \
  http://localhost:3000/pricing

This is a claimed identity, not a real GPTBot visit. Wait for the flush interval (3 seconds by default), then check that your ingestion receiver got the batch for the right project. A 200 from your website only shows the route responded; it does not confirm telemetry delivery.

Lifecycle

The middleware creates its own client with lifecycle hooks off and does not expose it. There is no public flush or shutdown in 0.1.0, so abrupt termination can lose buffered events. See Node delivery limits.

See also what website request data can and cannot show, including CDN and WAF coverage.