@workers-powertools/hono
v0.4.0
Published
Hono middleware adapters for Workers Powertools (logger, metrics, tracer, idempotency)
Downloads
445
Readme
@workers-powertools/hono
Hono middleware adapters for all Workers Powertools utilities. A single package for integrating logger, metrics, tracer, and idempotency into your Hono application.
Part of Workers Powertools — a developer toolkit for observability and reliability best practices on Cloudflare Workers, inspired by Powertools for AWS Lambda.
Framework adapters — this is the Hono adapter. More framework adapters (Astro, etc.) may be added in the future. The core packages (
logger,metrics,tracer,idempotency) are framework-agnostic and can be used directly in any Workers project.
Installation
pnpm add @workers-powertools/honoIf you only want a single adapter surface, subpath exports are available:
import { injectLogger } from "@workers-powertools/hono/logger";
import { injectMetrics } from "@workers-powertools/hono/metrics";
import { injectTracer } from "@workers-powertools/hono/tracer";
import { injectIdempotency } from "@workers-powertools/hono/idempotency";Middleware
injectLogger(logger, options?)
Enriches the logger with request context (CF properties, correlation ID) before the handler runs, and clears temporary keys afterward.
When wideEvent is enabled, creates a request-scoped WideEvent accessible via c.get("wideEvent") that auto-emits with duration_ms after the handler completes.
import { injectLogger } from "@workers-powertools/hono";
import type { WideEventVariables } from "@workers-powertools/hono/logger";
// Basic — no wide event
app.use(injectLogger(logger));
// With wide events (recommended)
const app = new Hono<{ Variables: WideEventVariables }>();
app.use(injectLogger(logger, { wideEvent: true }));
app.get("/orders", (c) => {
c.get("wideEvent").set({ ordersFound: 42 });
return c.json(orders);
// wide event auto-emits: { message: "GET /orders", ordersFound: 42, duration_ms: ... }
});wideEvent accepts true, a static string, or a (request, routePath) => string function for custom messages.
injectMetrics(metrics, options?)
Records request_duration and request_count metrics with route, method, and status dimensions, then flushes via ctx.waitUntil().
import { injectMetrics } from "@workers-powertools/hono";
import { PipelinesBackend } from "@workers-powertools/metrics";
// Default — uses env.METRICS_PIPELINE automatically
app.use(injectMetrics(metrics));
// Custom binding name
app.use(
injectMetrics(metrics, {
backendFactory: (env) =>
new PipelinesBackend({ binding: env.MY_PIPELINE as PipelineBinding }),
}),
);The backend is only created once per binding reference — setBackend() is idempotent when the underlying binding hasn't changed.
injectTracer(tracer) (deprecated)
Deprecated: Cloudflare Workers has no API for custom spans. Use wide events via
injectLogger(logger, { wideEvent: true })instead. The tracer module will be removed in a future major version.
Extracts correlation ID, wraps the handler in a route-level span (METHOD /path), and annotates it with http.method, http.route, http.url, and http.status.
import { injectTracer } from "@workers-powertools/hono";
app.use(injectTracer(tracer));injectIdempotency(options)
Checks idempotency before the handler runs. If a completed record exists, returns the stored response. Concurrent duplicates receive 409 Conflict.
Apply per-route to state-mutating endpoints only:
import { injectIdempotency } from "@workers-powertools/hono";
app.post(
"/orders",
async (c, next) => {
persistenceLayer ??= new KVPersistenceLayer({ binding: c.env.IDEMPOTENCY_KV });
return injectIdempotency({ persistenceLayer, config: idempotencyConfig })(c, next);
},
async (c) => {
const body = await c.req.json();
return c.json({ orderId: body.orderId, status: "created" }, 201);
},
);Full Example
import { Hono } from "hono";
import { Logger } from "@workers-powertools/logger";
import { Metrics, MetricUnit, PipelinesBackend } from "@workers-powertools/metrics";
import type { PipelineBinding } from "@workers-powertools/metrics";
import {
injectLogger,
injectMetrics,
injectIdempotency,
} from "@workers-powertools/hono";
import type { WideEventVariables } from "@workers-powertools/hono/logger";
import { IdempotencyConfig } from "@workers-powertools/idempotency";
import { KVPersistenceLayer } from "@workers-powertools/idempotency/kv";
const logger = new Logger();
const metrics = new Metrics();
let persistenceLayer: KVPersistenceLayer | undefined;
const idempotencyConfig = new IdempotencyConfig({
eventKeyPath: "$",
expiresAfterSeconds: 3600,
});
const app = new Hono<{ Bindings: Env; Variables: WideEventVariables }>();
app.use(injectLogger(logger, { wideEvent: true }));
app.use(
injectMetrics(metrics, {
backendFactory: (env) =>
new PipelinesBackend({ binding: env.METRICS_PIPELINE as PipelineBinding }),
}),
);
app.get("/hello", (c) => {
c.get("wideEvent").set({ greeting: "hello" });
return c.json({ message: "hello" });
});
app.post(
"/orders",
async (c, next) => {
persistenceLayer ??= new KVPersistenceLayer({ binding: c.env.IDEMPOTENCY_KV });
return injectIdempotency({ persistenceLayer, config: idempotencyConfig })(c, next);
},
async (c) => {
const body = await c.req.json();
c.get("wideEvent").set({ action: "createOrder" });
return c.json({ status: "created" }, 201);
},
);
export default app;