Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dispatching

Routes are added with server.addRoute(path, ResourceClass).

The router uses find-my-way (the same trie router as Fastify), giving O(1) route matching regardless of the number of routes.

Path Syntax

Static paths

"/articles"       matches only "/articles"
"/articles/new"   matches only "/articles/new"

Named parameters

"/articles/:id"           matches "/articles/42",  params: { id: "42" }
"/users/:user/posts/:id"  matches "/users/jon/posts/5", params: { user: "jon", id: "5" }

Parameters are available at this.req.params.id inside resource methods.

Wildcard

"/assets/*"   matches "/assets/img/logo.png", params: { "*": "img/logo.png" }

Regex constraints on parameters

"/lang/:lang([a-z]{2})"   matches "/lang/en" but not "/lang/english"

Resource Class

The second argument to addRoute must be a class that extends Resource. The decision machine constructs a new instance per request:

import { createServer, Resource } from "resource-machine";

class PingResource extends Resource {
  override async contentTypesProvided() {
    return { "text/plain": () => "pong" };
  }
}

const server = createServer();
server.addRoute("/ping", PingResource);

Injecting shared state via factory

When a resource needs access to a shared object (e.g. a database connection pool), use a factory function that closes over the shared value and returns a ResourceClass:

import type { ResourceClass } from "resource-machine";
import { Resource } from "resource-machine";
import type { Database } from "./db.js";

export function createArticleResource(db: Database): ResourceClass {
  return class ArticleResource extends Resource {
    private article: Article | null = null;

    override async resourceExists() {
      this.article = await db.find(this.req.params.id ?? "");
      return this.article !== null;
    }

    override async contentTypesProvided() {
      return {
        "application/json": () => JSON.stringify(this.article),
      };
    }
  };
}

// Registration
server.addRoute("/articles/:id", createArticleResource(db));

Shared resources (DB pools, config) belong on class statics or module scope — not on this. this is per-request.