Server Instance

Server owns middleware composition, request augmentation, and runtime lifecycle.

Creating a Server

import { Server } from "@hornjs/fest";
import { NodeRuntimeAdapter } from "@hornjs/fest/node";

const server = new Server({
  adapter: new NodeRuntimeAdapter(),
  routes: {
    "/": () => new Response("hello"),
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

await server.ready();
console.log(server.url);

adapter is optional. In the common Bun, Deno, and Node.js cases you can omit it entirely unless you need to customize native adapter options or force a specific runtime configuration.

When omitted, Server lazily resolves a built-in adapter for the current process with dynamic imports such as import("@hornjs/fest/bun"), import("@hornjs/fest/deno"), and import("@hornjs/fest/node"):

  • Bun -> BunRuntimeAdapter
  • Deno -> DenoRuntimeAdapter
  • Node.js -> NodeRuntimeAdapter
import { Server } from "@hornjs/fest";

const server = new Server({
  routes: {
    "/": () => new Response("hello"),
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

await server.ready();

If you prefer, use the convenience factory:

import { serve } from "@hornjs/fest";
import { NodeRuntimeAdapter } from "@hornjs/fest/node";

const server = serve({
  adapter: new NodeRuntimeAdapter(),
  routes: {
    "/": () => new Response("hello"),
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

Important Properties

server.options

The normalized options used to build the server.

server.url

The public URL reported by the runtime adapter once listening has started.

server.waitUntil

Register background tasks outside the request pipeline.

server.waitUntil?.(
  Promise.resolve().then(() => {
    console.log("background startup task");
  }),
);

Lifecycle Events

Server extends the typed event system from @hornjs/evt. You can subscribe to lifecycle transitions with standard event listeners.

serve

Emitted after the runtime adapter reports that the server is listening.

close

Emitted after shutdown completes and all waitUntil() tasks have settled.

error

Emitted when asynchronous runtime adapter initialization fails.

import { Server, ServerErrorEvent, ServerServeEvent } from "@hornjs/fest";

server.addEventListener("serve", (event: ServerServeEvent) => {
  console.log("ready at", server.url);
});

server.addEventListener("error", (event: ServerErrorEvent) => {
  console.error(event.error);
});

Important Methods

server.fetch(request)

Runs a request through middleware, invocation context initialization, route matching, and the fallback fetch handler when needed.

server.serve()

Starts listening if the server has not already been started.

server.ready()

Waits until the adapter reports that the server is ready.

server.close(closeActiveConnections?)

Stops the runtime adapter and waits for registered background work.

await server.close();
await server.close(true);

Passing true asks the runtime adapter to terminate active connections when it supports that behavior.

server.close() also dispatches the close lifecycle event once teardown is finished.

Manual Startup

Set manual: true when you want to delay listening:

const server = new Server({
  adapter: new NodeRuntimeAdapter(),
  manual: true,
  routes: {
    "/": () => new Response("ok"),
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

await server.serve();
await server.ready();

If you prefer event-driven coordination instead of awaiting ready(), listen for serve and close directly.