Server Options

Provide additional options to customize listening server.

When starting a new server, in addition to main fetch handler, you can provide additional options to customize listening server.

import { serve } from "srvx";

serve({
  // Generic options
  port: 3000,
  hostname: "localhost",

  // Runtime specific options
  node: {},
  bun: {},
  deno: {},

  // Main server handler
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
});

There are two kind of options:

  • Generic options: Top level options are intended to have exactly same functionality regardless of runtime
  • Runtime specific: Allow customizing more runtime specific options

Generic Options

port

The port server should be listening to.

Default is value of PORT environment variable or 3000.

You can set the port to 0 to use a random port.

hostname

The hostname (IP or resolvable host) server listener should bound to.

Default is value of the HOST environment variable, if set. When neither hostname nor HOST is provided, the server will listen to all network interfaces by default.

If you are running a server that should not be exposed to the network, use localhost.

reusePort

Enabling this option allows multiple processes to bind to the same port, which is useful for load balancing.

Despite Node.js built-in behavior that has exclusive flag enabled by default, srvx uses non-exclusive mode for consistency.

silent

If enabled, no server listening message will be printed (enabled by default when TEST environment variable is set).

manual

If set to true, the server will not start listening automatically. You must call server.serve() yourself to begin accepting connections. Useful when you need to fully construct the server before it starts.

import { serve } from "srvx";

const server = serve({
  manual: true,
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
});

// ...later
await server.serve();
await server.ready();
manual is meaningless on module-worker style runtimes (Cloudflare module syntax, Bunny), where the runtime drives the request lifecycle and there is no listening step to defer.

middleware

An array of middleware handlers to run before the main fetch handler.

import { serve } from "srvx";

serve({
  middleware: [
    (request, next) => {
      console.log(`[${request.method}] ${request.url}`);
      return next();
    },
  ],
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
});
Read more in Middleware.

plugins

An array of plugins to extend the server. A plugin is a synchronous function that receives the server instance and can register middleware, hook into the lifecycle, or augment requests.

import { serve } from "srvx";

const myPlugin = (server) => {
  server.options.middleware.push((request, next) => next());
};

serve({
  plugins: [myPlugin],
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
});
Plugins are sync-only (they return void). All plugin-registered middleware is therefore in place before the first request is handled.

protocol

The protocol to use for the server.

Possible values are http or https.

If protocol is not set, Server will use http as the default protocol or https if both tls.cert and tls.key options are provided.

tls

TLS server options.

Example:

import { serve } from "srvx";

serve({
  tls: { cert: "./server.crt", key: "./server.key" },
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
});

Options:

  • cert: Path or inline content for the certificate in PEM format (required).
  • key: Path or inline content for the private key in PEM format (required).
  • passphrase: Passphrase for the private key (optional).
You can pass inline cert and key values in PEM format starting with -----BEGIN .

Client certificates (mutual TLS) are available through the mtls() plugin.

Read more in TLS & mutual TLS.

onError

Runtime agnostic error handler.

This handler will take over the built-in error handlers of Deno and Bun.

Example:

import { serve } from "srvx";

serve({
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
  onError(error) {
    return new Response(`<pre>${error}\n${error.stack}</pre>`, {
      headers: { "Content-Type": "text/html" },
    });
  },
});

maxRequestBodySize

Maximum allowed size in bytes for the request body. Defaults to undefined (no limit).

As the body is read, its accumulated length is tracked and, once it exceeds the limit, reading is aborted and rejects with a 413-style error. The error carries statusCode: 413, status: 413 and code: "ERR_BODY_TOO_LARGE", so a handler (or onError) can map it to an HTTP 413 Payload Too Large response.

The limit covers both buffered reads (request.text() / request.json()) and the streamed body (request.body, and therefore request.arrayBuffer() / .blob() / .bytes() / .formData()).

Example:

import { serve } from "srvx";

serve({
  maxRequestBodySize: 1024 * 1024, // 1 MiB
  fetch: async (request) => {
    try {
      return Response.json(await request.json());
    } catch (error) {
      if (error.code === "ERR_BODY_TOO_LARGE") {
        return new Response("Payload Too Large", { status: 413 });
      }
      throw error;
    }
  },
});
Runtime support:
  • Node: enforced by srvx (the request body stream is size-limited).
  • Bun: forwarded to Bun's native maxRequestBodySize, enforced by Bun (responds with 413 before the handler runs).
  • Deno: enforced by srvx (Deno.serve has no native option).

trustProxy

Whether to trust X-Forwarded-* headers (X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-For, and the HTTP/2 :scheme) when deriving request.url and request.ip. Defaults to false.

Any client can send X-Forwarded-Proto: https, X-Forwarded-Host or X-Forwarded-For, so trusting them lets a request masquerade as https:, forge its host, or spoof its client IP. Only enable this when a proxy you control sits in front and overwrites the headers.

  • false (default): ignore the headers; use the real connection protocol, the on-the-wire Host header and the socket peer address.
  • true: always trust the headers.
  • "loopback": trust them only when the proxy connects from a loopback address (127.0.0.0/8 or ::1).
  • string[]: trust them only when the proxy's address is in the list.

Example:

import { serve } from "srvx";

serve({
  // Behind a reverse proxy you control (e.g. Nginx, a load balancer):
  trustProxy: true,
  fetch: (request) => new Response(new URL(request.url).protocol),
});
Applies to the Node, AWS Lambda, Bun and Deno adapters.

Runtime Specific Options

Node.js

Example:

import { serve } from "srvx";

serve({
  node: {
    maxHeaderSize: 16384 * 2, // Double default
    ipv6Only: true, // Disable dual-stack support
    // http2: false // Disable http2 support (enabled by default in TLS mode)
  },
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
});
See Node.js documentation for ServerOptions and ListenOptions for all available options.

Bun

Example:

import { serve } from "srvx";

serve({
  bun: {
    error(error) {
      return new Response(`<pre>${error}\n${error.stack}</pre>`, {
        headers: { "Content-Type": "text/html" },
      });
    },
  },
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
});
See Bun HTTP documentation for all available options.

Deno

Example:

import { serve } from "srvx";

serve({
  deno: {
    onError(error) {
      return new Response(`<pre>${error}\n${error.stack}</pre>`, {
        headers: { "Content-Type": "text/html" },
      });
    },
  },
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
});
See Deno serve documentation for all available options.

Service Worker

Options for the service worker adapter (srvx/service-worker).

import { serve } from "srvx/service-worker";

serve({
  serviceWorker: {
    url: "/sw.js", // path to the service worker file to register
    scope: "/", // service worker scope
  },
  fetch: () => new Response("๐Ÿ‘‹ Hello there!"),
});
  • url: The path to the service worker file to be registered.
  • scope: The scope of the service worker.

Per-runtime Support

srvx aims for identical behavior across runtimes, but a few options depend on capabilities the underlying runtime does not expose. The table below lists the intentional differences.

Option / featureBehavior
close(true) (force close)Honored on Node and Bun. On Deno the force argument is currently ignored โ€” close(true) behaves like a graceful close().
trustProxyApplies to the Node, AWS Lambda, Bun and Deno adapters only. Ignored on Cloudflare, Bunny and the generic/service-worker adapters.
maxRequestBodySizeNode/Deno enforced by srvx (unlimited by default). Bun forwards it to Bun's native option, which has its own 128 MiB default even when unset. Dropped (not applied) when running under the CLI loader on any runtime.
manualMeaningless on module-worker runtimes (Cloudflare module syntax, Bunny) โ€” there is no listening step to defer.
gracefulShutdownSupported on Node, Deno and Bun. No-op on Cloudflare, Bunny and service-worker runtimes.
Cloudflare env bindingsrequest.runtime.cloudflare.env is only populated in module-worker syntax. In service-worker syntax (the global fetch listener) bindings are unavailable.
upgrade / WebSocketsrvx does not handle HTTP upgrade requests on any runtime โ€” WebSocket upgrades never reach your fetch handler. Use crossws for WebSocket support.

Deno version

srvx targets Deno 2.x (CI runs against Deno 2.7.x).

Under the Bunny runtime, a root srvx import resolves the deno export condition (Bunny is Deno-based) and would give you the Deno adapter. Always import the Bunny adapter explicitly via srvx/bunny.

Public Subpaths

In addition to the main srvx entry and the runtime adapters, srvx exposes several public subpaths:

SubpathExportsUse when
srvx/staticserveStatic middlewareServing static files. Node-API-only (uses node:fs / node:zlib) despite the neutral name โ€” works only on runtimes with Node compatibility.
srvx/loglog() middlewareAdding request logging (used by the CLI).
srvx/loaderloadServerEntry, related typesResolving and loading a server entry file (as the CLI does).
srvx/climain, cliFetch, CLI typesBuilding a custom CLI on top of srvx.
srvx/genericserve, FastURL, FastResponseA web-standard (fetch/Response) runtime with no dedicated adapter.
srvx/service-workerserve, FastURL, FastResponseRunning inside a browser/service-worker environment.

TypeScript Types

Runtime-specific option and context types are typed against each runtime's own type packages, which srvx does not bundle. To get full typing for options.bun, options.deno, request.runtime.cloudflare, request.runtime.awsLambda (and similar), install the relevant devDependencies in your project:

Without these, the corresponding fields fall back to any (with skipLibCheck) or raise type errors (without it).