Express Middleware
Add signed proof to Express.js APIs. The middleware intercepts responses, signs a PEAC receipt, and attaches it via the PEAC-Receipt HTTP header. Automatic issuance on every governed response.
Packages: @peac/middleware-express, @peac/middleware-core
Setup
import { peacMiddleware } from '@peac/middleware-express';
import express from 'express';
const app = express();
app.use(peacMiddleware({
issuer: 'https://api.example.com',
signingKey: process.env.PEAC_SIGNING_KEY,
}));
app.get('/data', (req, res) => {
res.json({ value: 42 });
// PEAC-Receipt header is attached automatically
});Install
pnpm add @peac/middleware-express @peac/middleware-core
How It Works
1. Intercept
The middleware hooks into the Express response lifecycle. When a response is about to be sent, it captures the route, method, status code, and content metadata.
2. Sign
A PEAC receipt is issued using the configured issuer identity and Ed25519 signing key. The receipt contains the interaction evidence as a compact JWS (interaction-record+jwt format).
3. Attach
The compact JWS is set as the PEAC-Receipt HTTP response header. The original response body and status code are unchanged.
Configuration Options
| Option | Type | Required | Description |
|---|---|---|---|
issuer | string | Yes | Issuer URI for the iss claim in issued receipts |
signingKey | Ed25519 private key | Yes | Ed25519 private key (JWK or raw bytes) for receipt signing |
audienceExtractor | (req) => string | No | Derives the audience for each request |
skip | (req) => boolean | No | Return true to exclude a request from receipt issuance |
Route-Level Overrides
Per-request behavior is controlled through predicate options on the middleware config. audienceExtractor derives the audience from each request, and skip excludes requests from receipt issuance.
import { peacMiddleware } from '@peac/middleware-express';
app.use(peacMiddleware({
issuer: 'https://api.example.com',
signingKey: process.env.PEAC_SIGNING_KEY,
// Derive the audience per request
audienceExtractor: (req) =>
req.path.startsWith('/partner') ? 'partner.example.com' : 'agent.consumer.com',
// Skip receipt issuance for selected requests
skip: (req) => req.path === '/health',
}));Route Filtering
The skip predicate controls which paths receive receipts. Returning true passes a request through without receipt overhead.
app.use(peacMiddleware({
issuer: 'https://api.example.com',
signingKey: process.env.PEAC_SIGNING_KEY,
skip: (req) => !req.path.startsWith('/api/'),
}));
// /api/v1/users -> receipt attached
// /health -> no receipt (skipped)
// /docs -> no receipt (skipped)Error Isolation
Middleware failures do not block API responses. If receipt signing fails (key unavailable, malformed input, internal error), the response is sent without a PEAC-Receipt header. The error is emitted via the standard Express error event for logging and monitoring, but the API consumer receives their response uninterrupted.
import { peacMiddleware } from '@peac/middleware-express';
const middleware = peacMiddleware({
issuer: 'https://api.example.com',
signingKey: process.env.PEAC_SIGNING_KEY,
onError: (error, req, res) => {
console.error('Receipt issuance failed:', error.message);
// Response is still sent; receipt is omitted
},
});
app.use(middleware);Packages
The Express middleware is split into two packages with distinct responsibilities:
@peac/middleware-express
Layer 3.5: Express Binding
Express-specific middleware function. Hooks into the response lifecycle, applies route filtering, and attaches the PEAC-Receipt header.
@peac/middleware-core
Layer 3.5: Framework-Agnostic Core
Receipt issuance logic, configuration validation, and error handling. Framework-agnostic; can be used as a foundation for other HTTP framework adapters.
Links
Verifiable APIs in Three Lines
Every governed response carries a signed receipt. Consumers can verify the interaction independently using the issuer's public key, without trusting the API server. The middleware handles signing, header attachment, and error isolation; your route handlers remain unchanged.