Typed EventEmitter instrumentation for Express. Every middleware and request emits
start/end events with real durations, so you can wire up logging, tracing, or metrics
without the library deciding how you output them.
import express from 'express';
import { expressEmits } from 'express-emits';
const app = express();
const { on, log, namedMiddleware } = expressEmits(app);
on('request.start', (r) => console.log(`-> ${r.method} ${r.url} [${r.requestID}]`));
on('request.end', (r) => console.log(`<- ${r.statusCode} in ${r.duration.toFixed(1)}ms`));
app.use(namedMiddleware('auth', (req, res, next) => {
log(res, 'checking token');
next();
}));
app.get('/', (_req, res) => res.send('Hello World'));
app.listen(3000);npm install express-emitsExpress 5 is a peer dependency. Node 18+.
Instruments app and returns { on, log, namedMiddleware }. Each call gets its own
emitter, so two apps in one process stay isolated.
| Option | Default | |
|---|---|---|
unguessableRequestIds |
false |
Use randomUUID() instead of a per-process counter. Turn on if IDs ever leave the process or need to be unpredictable. The counter is faster and fine for correlating logs. |
Typed against the event map. Wrong event name or wrong payload shape is a compile error.
| Event | Payload |
|---|---|
request.start |
{ requestID, method, url, path, headers } |
request.end |
{ requestID, statusCode, duration } |
middleware.start |
{ requestID, middlewareName } |
middleware.end |
{ requestID, middlewareName, duration } |
log |
(requestID, middlewareName, ...args) |
Labels a middleware so its events carry name instead of an anonymous N placeholder.
Emits a log event tagged with the current request ID and middleware name. Takes res
because that is where per-request state lives.
Middleware registered without namedMiddleware is numbered by execution order within each
request, restarting at 1 every request:
request.start id=1
middleware.start anonymous 1
middleware.start auth
A middleware that a given request skips does not consume a number for that request.
npm run bench runs bench/rps-bench.ts: five scenarios at 30s / 50
connections via autocannon. Two independent
runs on the same machine:
| Scenario | Run 1 | Run 2 |
|---|---|---|
| express (base) | 18330 req/s | 17201 req/s |
| zero listeners, counter IDs | 16551 req/s (-9.7%) | 16885 req/s (-1.8%) |
| silent listeners, counter IDs | 16405 req/s (-10.5%) | 16260 req/s (-5.5%) |
zero listeners, unguessableRequestIds: true |
16342 req/s (-10.8%) | 16455 req/s (-4.3%) |
silent listeners, unguessableRequestIds: true |
16063 req/s (-12.4%) | 15791 req/s (-8.2%) |
Benchmark was run on my local computer, that is why the benchmark results are not stable.
npm run build # compile to dist/
npm test # build, then run the test suite
npm run bench # 30s x 5 RPS benchmark against plain Express, see Performance
npm run dev # watch mode, runs the demo in main.tsMIT