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:
| Field | Meaning |
|---|---|
apiEndpoint | Express route template when available, otherwise a normalized path |
httpMethod | Request method |
durationMs | Elapsed request time |
status | success for status below 400, otherwise failed |
metadata.statusCode | HTTP status as a string |
metadata.routePattern | The recorded route pattern |
errorType | unauthorized (401), forbidden (403), not_found (404), timeout (408), rate_limited (429), server_error (5xx) |
sessionId | The session header value, or a generated per-request ID |
agentType | The 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
| Option | Behavior in 0.1.0 |
|---|---|
express.ignoreRoutes | Exact match on the path, without the query string |
express.ignoreUserAgents | Substring match on the User-Agent header |
express.sessionHeader | Header read for the session ID (default x-doclight-session-id) |
express.agentHeader | Header 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-idandx-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-Agentandx-agent-idare 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/pricingThis 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.
SDK
Install and use the published Doclight JavaScript SDKs (npm 0.1.0) to send application telemetry from your Express site, Node.js code or your own MCP server.
Node.js
Send explicit session, tool and HTTP events with @doclight/node 0.1.0, and understand batching, retries, delivery limits and shutdown.