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):
- WASM core router (
createRouter,matchRoute,parseQueryfrompkg/chroma.js) — programmatic route tables for SPA navigation (navigate,View, hash/history mode) and verb dispatch viarouter.request(). Used by Chroma Desktop for library navigation and internal channels. - File-based API routes (
@chroma/routerplugin) — Next.js-style handlers underapi/;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):
| File | Route |
|---|---|
api/users/GET.js | GET /api/users |
api/users/POST.js | POST /api/users |
api/users/[id]/GET.js | GET /api/users/:id |
api/status/CUSTOM.js | CUSTOM /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
| Method | Handler signature | Notes |
|---|---|---|
GET | (req) => response | |
POST | (req) => response | req.body parsed from JSON when possible |
PUT | (req) => response | |
DELETE | (req) => response | |
CUSTOM | (req, operation) => response | operation 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.jsonBracket 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 --manifest4. 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
| Export | Description |
|---|---|
CHROMA_METHODS | ["GET","POST","PUT","DELETE","CUSTOM"] |
ChromaRouter | Router 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 pathregisterRoutes([{ path, handlers }])loadFromManifest([{ path, module }])— dynamicimport()dispatch(method, url, body?, customOperation?)hijackFetch()— intercept local APIfetchcalls
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.