ProvenPin Install

Express 5 + pino-http structured request logging: working versions (tested 2026-10-15)

Structured JSON request logs in Express 5 with pino-http: request ids, redacted auth headers and per-request child loggers.

passed2 passed · 0 failed2026-10-15 13:14 UTC
Status
passed
Last passed
2026-10-15 13:14 UTC
Tests
2 / 2
Language
TypeScript
Runtime
node v22.23.3
pinned versions8
express@5.3.0pino@10.4.0pino-http@11.0.0tsx@4.23.15@types/node@24.19.2supertest@7.3.1@types/supertest@7.2.1@types/express@5.0.6
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38740169402

The code

Exactly the files that passed. Your agent can pull them with get_recipe("express-pino-http-logging") (install), or see them on GitHub.

package.json
{
  "name": "express-pino-http-logging",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "express": "5.3.0",
    "pino": "10.4.0",
    "pino-http": "11.0.0"
  },
  "devDependencies": {
    "tsx": "4.23.15",
    "@types/node": "24.19.2",
    "supertest": "7.3.1",
    "@types/supertest": "7.2.1",
    "@types/express": "5.0.6"
  }
}
src/app.ts
import express from "express";
import pino from "pino";
import { pinoHttp } from "pino-http";
import { randomUUID } from "node:crypto";

export function createApp(stream?: pino.DestinationStream) {
  const logger = pino({ redact: ["req.headers.authorization", "req.headers.cookie"] }, stream);
  const app = express();
  app.use(pinoHttp({
    logger,
    genReqId: (req, res) => {
      const id = (req.headers["x-request-id"] as string) ?? randomUUID();
      res.setHeader("x-request-id", id);
      return id;
    },
  }));
  app.get("/orders/:id", (req, res) => {
    req.log.info({ orderId: req.params.id }, "loading order");
    res.json({ id: req.params.id });
  });
  return app;
}
test/app.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { Writable } from "node:stream";
import request from "supertest";
import { createApp } from "../src/app.ts";

function capture() {
  const lines: any[] = [];
  const stream = new Writable({ write(chunk, _e, cb) { lines.push(JSON.parse(chunk.toString())); cb(); } });
  return { lines, stream };
}

test("logs with request id and redacts auth", async () => {
  const { lines, stream } = capture();
  const res = await request(createApp(stream as any)).get("/orders/42").set("x-request-id", "req-1").set("authorization", "Bearer secret");
  assert.equal(res.headers["x-request-id"], "req-1");
  const custom = lines.find((l) => l.msg === "loading order");
  assert.equal(custom.orderId, "42");
  assert.equal(custom.req.id, "req-1");
  const done = lines.find((l) => l.res?.statusCode === 200);
  assert.ok(done);
  assert.equal(done.req.headers.authorization, "[Redacted]");
});

test("generates a request id when none is sent", async () => {
  const { stream } = capture();
  const res = await request(createApp(stream as any)).get("/orders/1");
  assert.match(res.headers["x-request-id"], /^[0-9a-f-]{36}$/);
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.