@addilytics/express
v0.0.1
Published
Express middleware and bundled browser relay for Addilytics.
Readme
@addilytics/express
Server-side analytics middleware for Express 4 and 5.
Requires Node.js 20 or newer.
import express from 'express';
import { createAddilyticsMiddleware } from '@addilytics/express';
const app = express();
const analytics = createAddilyticsMiddleware({
endpoint: 'https://addilytics.example',
siteKey: process.env.ADDILYTICS_KEY!,
user: (_request, response) => response.locals.userId
});
app.use(sessionMiddleware);
app.use(analytics);
app.use(express.json());Register authentication and session middleware first, Addilytics second, and body parsers, routes,
error handlers, and response-mutating middleware after it. Addilytics answers relay requests without
calling later middleware, so its user resolver can read response.locals only when an earlier
middleware populated it. A resolver that reads the raw request does not need that setup. Express
skips middleware that appears after a route ends the response. Addilytics listens for the response's
finish event, then reads the final status and content type without touching the body. Aborted
responses don't produce pageviews.
Express has no request-lifetime API that can await work started after finish. A normal Node server will usually keep the outbound request alive, but short-lived serverless runtimes may stop it. If your host exposes waitUntil, pass it in:
const analytics = createAddilyticsMiddleware({
endpoint: 'https://addilytics.example',
siteKey: process.env.ADDILYTICS_KEY!,
waitUntil: (_request, response) => {
const context = response.locals.executionContext;
return context?.waitUntil.bind(context);
}
});Bundled navigation tracking
The default server mode counts document requests handled by Express. To cover prerendered pages,
CDN hits, and client-side navigation, set mode: 'client' and call the tracker from your browser
router's committed-navigation hook:
const analytics = createAddilyticsMiddleware({
endpoint: 'https://addilytics.example',
mode: 'client',
siteKey: process.env.ADDILYTICS_KEY!
});
app.use(sessionMiddleware);
app.use(analytics);
app.use(express.json());import { createBrowserTracker } from '@addilytics/express/browser';
const tracker = createBrowserTracker({ mode: 'client' });
void tracker.pageview();
export const trackCommittedNavigation = (url: URL, key: string) =>
tracker.pageview(url, { navigationKey: key });
window.addEventListener('pageshow', (event) => {
if (event.persisted) void tracker.pageview(location.href, { force: true });
});Call trackCommittedNavigation from the router's official post-navigation hook. Do not treat a
loader, prefetch, or data request as a navigation.
This is application code compiled by your build, not a hosted script. It posts to the middleware's
same-origin /__addilytics relay and contains no site key. Mount authentication before Addilytics,
then mount body parsers after it so the relay can read its small request body. Use hybrid on both
sides when Express always sees the initial document. Hybrid keeps that server pageview and skips the
browser's first callback. If you change the browser endpoint, set the same path as relayPath on
the middleware.
Behind a TLS-terminating proxy, configure Express's trusted proxy setting so request.protocol
reflects the public HTTPS origin. The relay rejects the request when its server-side URL and browser
Origin do not match.
Custom events
Use the same middleware instance from a route. track reuses the request metadata captured when the middleware ran and never reads the request body.
app.post('/checkout', async (request, response, next) => {
try {
const order = await createOrder(request.body);
await analytics.track(request, 'purchase', {
props: { currency: 'USD', total: order.total }
});
response.status(201).json(order);
} catch (error) {
next(error);
}
});The user and ip options receive the Express request and response. Explicit userId values passed to track take priority over user.
