Middleware
Example
import { serve, type ServerMiddleware, type ServerPlugin } from "srvx";
const xPoweredBy: ServerMiddleware = async (req, next) => {
const res = await next();
res.headers.set("X-Powered-By", "srvx");
return res;
};
const devLogs: ServerPlugin = (server) => {
if (process.env.NODE_ENV === "production") {
return;
}
console.log(`Logger plugin enabled!`);
server.options.middleware.push((req, next) => {
console.log(`[request] [${req.method}] ${req.url}`);
return next();
});
};
serve({
middleware: [xPoweredBy],
plugins: [devLogs],
fetch(request) {
return new Response(`👋 Hello there.`);
},
});
Order of execution
Middleware run in the order they appear in the middleware array, each wrapping the next, with your fetch handler at the center. A middleware that returns a response without calling next() short-circuits the rest of the chain — nothing after it runs.
Plugins are applied in plugins array order, before the server starts listening. A plugin that pushes middleware appends to the end of the array, so middleware entries always run first.
Built-in middleware and plugins
srvx ships several optional extensions as separate subpath imports. All of them are opt-in — importing srvx alone pulls in none of them.
| Import | Export | Kind | Runtimes |
|---|---|---|---|
srvx/log | log() | Middleware | All |
srvx/static | serveStatic() | Middleware | Node, Deno, Bun |
srvx/mtls | mtls() | Plugin | Node |
srvx/tracing | tracingPlugin() | Plugin | Node, Deno, Bun |
Request logging
log() from srvx/log prints one colored line per request with the method, URL, status, and duration.
import { serve } from "srvx";
import { log } from "srvx/log";
serve({
middleware: [log()],
fetch: () => new Response("👋 Hello there."),
});
[10:32:03 AM] GET http://localhost:3000/ [200] (1.42ms)
The duration is measured around next(), so place log() first in the array for it to cover the whole chain. The CLI enables this middleware automatically.
Static files
serveStatic() from srvx/static serves files from a directory, with index.html resolution, .html extension fallback (/about → about.html), common MIME types, gzip/Brotli compression, and path-traversal protection.
import { serve } from "srvx";
import { serveStatic } from "srvx/static";
serve({
middleware: [serveStatic({ dir: "public" })],
fetch: () => new Response("Not found", { status: 404 }),
});
When no file matches the request, it calls next() — so your handler acts as the fallback for unmatched paths.
serveStatic() options:
dir: The directory to serve files from (required).methods: HTTP methods to serve (default["GET", "HEAD"]). Other methods fall through tonext().renderHTML: A function receiving{ request, html, filename }for every.htmlfile, returning theResponseto send. Use it to inject or template markup before serving.
srvx/static is Node-API-only — it uses node:fs and node:zlib internally, so it works only on runtimes with Node.js compatibility (Node, Deno, Bun).See Serving static files for the equivalent CLI flag.
Mutual TLS
mtls() from srvx/mtls requests a client certificate during the TLS handshake and exposes it on request.tls. It requires the Node.js adapter.
Tracing
tracingPlugin() from srvx/tracing wraps your fetch handler and each middleware with diagnostics_channel instrumentation, publishing to the srvx.request and srvx.middleware tracing channels.
import { serve } from "srvx";
import { tracingPlugin } from "srvx/tracing";
import { tracingChannel } from "node:diagnostics_channel";
tracingChannel("srvx.request").subscribe({
start: ({ request }) => console.log(`[start] ${request.url}`),
asyncEnd: ({ request }) => console.log(`[end] ${request.url}`),
error: ({ request, error }) => console.error(`[error] ${request.url}`, error),
});
serve({
plugins: [tracingPlugin()],
fetch: () => new Response("👋 Hello there."),
});
Each event carries { server, request }, plus { middleware: { index, handler } } on the srvx.middleware channel. Pass { fetch: false } or { middleware: false } to instrument only one of the two.
Because plugins run in order, tracingPlugin() only wraps middleware registered before it — keep it last in the plugins array so it covers middleware added by earlier plugins.
srvx/tracing is experimental.