Server Instance

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

Creating a Server

import { Server } from "sevok";
import { NodeRuntimeAdapter } from "sevok/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("sevok/bun"), import("sevok/deno"), and import("sevok/node"):

  • Bun -> BunRuntimeAdapter
  • Deno -> DenoRuntimeAdapter
  • Node.js -> NodeRuntimeAdapter
import { Server } from "sevok";

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 "sevok";
import { NodeRuntimeAdapter } from "sevok/node";

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

Method Maps

Route entries can also dispatch by HTTP method instead of pointing to a single handler:

const server = new Server({
  routes: {
    "/users": {
      GET: () => new Response("list"),
      POST: () => new Response("create"),
      "*": (ctx) => new Response(`Unhandled method: ${ctx.request.method}`),
    },
  },
  fetch: () => new Response("Not Found", { status: 404 }),
});

Method resolution order is:

  • explicit request method
  • GET for HEAD requests when HEAD is not defined
  • * as a fallback for any remaining method

That makes * a route-local method fallback rather than a replacement for explicit handlers.

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.

update

Emitted after the server's routing configuration is updated via updateRouting().

import { Server, ServerErrorEvent, ServerServeEvent, ServerUpdateEvent } from "sevok";

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

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

server.addEventListener("update", (event: ServerUpdateEvent) => {
  console.log("routing updated:", event.reason);
});

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.

server.updateRouting(options)

Updates the server's routing configuration at runtime without restarting.

await server.updateRouting({
  routes: {
    "/api": () => new Response("new api"),
    "/*": () => new Response("Not Found", { status: 404 }),
  },
  middleware: [loggingMiddleware],
});

This method replaces the current request handler pipeline with new routes, middleware, error handlers, and fetch handler. The operation is atomic and safe to call while the server is actively handling requests.

If multiple updateRouting() calls are made concurrently, only the most recent one will take effect. The method dispatches an update event after successfully updating the routing configuration.

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.