Step 1
Plug Hyperwatch into the Express server
The frontend is a Next.js app served by a custom Express server. Hyperwatch lives in the same process: one function receives the Express app and wires everything in.
- 1Mount it first. Call it before any other middleware or route, so every request is seen, including the ones rejected later by the rate limiter.
- 2One log per request. The middleware records the request as it arrives, attaches
req.hyperwatch, and completes the log with the status and response time when the response finishes. - 3No second server. Hyperwatch's API and live streams are routes of the app, under
/_hyperwatchand behind Basic Auth, ready for the watcher. - 4Stay light. Only the modules needed to identify clients run in the app: real IP from Cloudflare, User-Agent parsing, verified hostnames and identity.
const express = require('express');
const next = require('next');
const hyperwatch = require('./hyperwatch');
const { identify, rateLimiter, serviceLimiter } = require('./limits');
const app = express();
// Behind Cloudflare: trust its addresses so req.ip is the visitor
app.set('trust proxy', cloudflareIps);
const nextApp = next({ dev: process.env.NODE_ENV !== 'production' });
nextApp.prepare().then(() => {
// 1. Hyperwatch first, so it sees every request
hyperwatch(app);
// 2. Then the middlewares that use its identity (step 2)
app.use(identify, rateLimiter, serviceLimiter);
// 3. Finally, Next.js renders the pages
app.all('*', nextApp.getRequestHandler());
app.listen(process.env.PORT || 3000);
});const hyperwatch = require('@hyperwatch/hyperwatch');
const basicAuth = require('express-basic-auth');
const expressWs = require('express-ws');
module.exports = function (app) {
const { input, modules, pipeline } = hyperwatch;
// Keep the servers light: only what the app needs to decide
hyperwatch.init({
modules: {
logs: { active: true }, // stream logs over WebSocket
cloudflare: { active: true }, // real client IP from Cloudflare
agent: { active: true },
hostname: { active: true },
identity: { active: true }, // "Googlebot", "GPTBot", …
},
});
// Expose the API and live streams, behind Basic Auth
expressWs(app); // WebSocket support for the live streams
const auth = basicAuth({ users: { watcher: process.env.HYPERWATCH_SECRET } });
app.use('/_hyperwatch', auth, hyperwatch.app.api);
app.use('/_hyperwatch', auth, hyperwatch.app.websocket);
// Every request goes through Hyperwatch
const expressInput = input.express.create({ name: 'opencollective.com' });
app.use(expressInput.middleware());
pipeline.registerInput(expressInput);
// No hyperwatch.start(): the app's own server does the serving
modules.start();
pipeline.start();
};