Targ Apps Docs
Chroma

API Routing

Next.js-style file-based API routes for Chroma apps — browser fetch webhooks with reserved methods GET, POST, PUT, DELETE, CUSTOM.

Chroma has two complementary routing layers, both running in the browser (no Node):

  1. WASM core router (createRouter, matchRoute, parseQuery from pkg/chroma.js) — programmatic route tables for SPA navigation (navigate, View, hash/history mode) and verb dispatch via router.request(). Used by Chroma Desktop for library navigation and internal channels.
  2. File-based API routes (@chroma/router plugin) — Next.js-style handlers under api/; fetch("/api/...") is hijacked and dispatched to your handler modules (local webhooks).

This page documents the file-based API layer. For the WASM router API, see Architecture → Router.

File convention

Place handlers under api/ at the project root (next to chroma.json):

FileRoute
api/users/GET.jsGET /api/users
api/users/POST.jsPOST /api/users
api/users/[id]/GET.jsGET /api/users/:id
api/status/CUSTOM.jsCUSTOM /api/status

Each file exports a named function matching the method:

// api/hello/GET.js
export async function GET(req) {
  return { status: 200, data: { message: "Hello", query: req.query } };
}

Reserved methods

MethodHandler signatureNotes
GET(req) => response
POST(req) => responsereq.body parsed from JSON when possible
PUT(req) => response
DELETE(req) => response
CUSTOM(req, operation) => responseoperation from X-Custom-Operation header

Request object

{
  method: "GET" | "POST" | "PUT" | "DELETE" | "CUSTOM",
  url: string,
  path: string,
  params: Record<string, string>,  // :id from path
  query: Record<string, string>,   // ?foo=bar
  body: unknown,
  customOperation: string | null
}

Handlers return a plain object (serialized as JSON). Include status for HTTP semantics; default is 200.

CLI discovery

chroma routes              # list routes under api/
chroma routes --manifest   # write api/routes-manifest.json

Bracket segments [id] are shown and exported as :id in the manifest.

Registering routes in a project

1. Declare the plugin

{
  "plugins": ["chroma-router"]
}

chroma new vendors plugins from ~/.chroma/plugins/ into plugins/.

2. Bootstrap at startup

In plugins/loader.js (template default):

import { bootstrapRouter } from "./chroma-router/index.js";

export async function loadPlugins() {
  const router = await bootstrapRouter();
  return { router };
}

bootstrapRouter() loads ./api/routes-manifest.json, registers all handlers, and hijacks window.fetch for matching paths.

3. Generate the manifest

After adding route files:

chroma routes --manifest

4. Manual registration (optional)

import { createRouter } from "./plugins/chroma-router/index.js";
import * as webhook from "./api/webhook/GET.js";

const router = createRouter();
router.registerRoute("/api/webhook", webhook);
router.hijackFetch();

Public router API

ExportDescription
CHROMA_METHODS["GET","POST","PUT","DELETE","CUSTOM"]
ChromaRouterRouter class
createRouter()Factory
bootstrapRouter(options?)Load manifest + optional fetch hijack
filePathToRoute(relPath)users/[id]/GET.js → /api/users/:id
parseRoutesManifest(manifest)Normalize manifest entries

ChromaRouter methods

  • registerRoute(path, handlers) — merge handlers for same path
  • registerRoutes([{ path, handlers }])
  • loadFromManifest([{ path, module }]) — dynamic import()
  • dispatch(method, url, body?, customOperation?)
  • hijackFetch() — intercept local API fetch calls

Chroma Desktop & dev server

Chroma Server serves static files only (GET/HEAD). API routes still run in the browser when your app calls bootstrapRouter(). Chroma Desktop uses the same model — built apps keep using client-side fetch interception.

Example

See Chroma/example/ — api/webhook/ handlers and a live demo section in index.html.

License

@chroma/router (canonical source: Chroma-Router/) is licensed under the GNU GPL version 3. See Licencia GPLv3.

On this page