Skip to content
RELKIT
Esc
↑↓navigate↵open⌘Jpreview
On this page

File-system routes

Choose URLs, dynamic segments, method exports, matching precedence, and stable operation identities.

Place a route.ts file under src/routes/ to define a URL, then export the HTTP methods it accepts. Named exports define its HTTP methods. The domain directory does not supply a URL prefix: /uploads can call the Assets service, and several paths can call the same public function. See Define a route for the factories.

Choose a path

File Route pattern Matching input
src/routes/route.ts / None
src/routes/orders/route.ts /orders None
src/routes/orders/[orderId]/route.ts /orders/:orderId orderId string
src/routes/files/[...parts]/route.ts /files/*parts parts array of strings; at least one segment
src/routes/docs/[[...parts]]/route.ts /docs and /docs/*parts Optional parts array

These are normalized route patterns, not literal URLs to request. For example, /files/images/logo.png maps parts to ["images", "logo.png"]. A catch-all must be the final segment; an optional catch-all also matches the base path without any segments. Use an array input schema for catch-all values.

Parameter names must be valid identifiers and cannot repeat in one path. Route groups, parallel routes, intercepting routes, and single optional segments such as [[id]] are not supported. Keep layout or domain names out of the route tree unless they should appear in the URL.

Export the methods you support

Function-backed routes support GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS:

/** Maps order methods to service members, normalizing GET identifiers and preserving the deletion 202 response. */
import { defineServiceRoutes, http } from "@relkit/app/routes";
import orders from "../../../services/orders/service.js";
import normalizeOrderId from "@app/routes/transforms/orders/normalize-id.transform.js";

export const { GET, HEAD, PUT, PATCH, DELETE, OPTIONS } = defineServiceRoutes(orders, {
  GET: {
    member: "getOrder",
    request: http.input({
      orderId: http.transform(normalizeOrderId, http.path("orderId")),
    }),
  },
  HEAD: "getOrder",
  PUT: "updateOrder",
  PATCH: "updateOrder",
  DELETE: { member: "deleteOrder", successStatus: 202 },
  OPTIONS: "getOrder",
});

Each export is a separate operation, even when several exports select the same function. The GET above maps the dynamic parameter through an explicit transform; other methods use inference. Path properties map first, then remaining properties come from query or body according to the method. See Requests.

Export HEAD explicitly when it is part of the contract. It invokes the target but removes the response body, so it still performs the target’s work. An OPTIONS export also invokes its selected target; it is not an automatic CORS policy. Use middleware for CORS behavior.

Helper exports are allowed. A default route export, legacy *.route.ts layout, or explicit path/method configuration produces migration diagnostics. ALL is reserved for raw handlers.

Understand matching precedence

Static segments match before dynamic segments, then required catch-all, then optional catch-all. For example, /orders/search takes precedence over /orders/[orderId]. The compiler checks ambiguous or duplicate registrations before activation; do not depend on module import order to choose a route.

An optional catch-all has base and expanded runtime registrations but remains one authored operation. Test both forms, especially when middleware or authentication depends on the path.

Treat route identity as a public contract

When id is omitted, RELKIT derives it from the method and normalized URL. For example, GET /orders/:orderId becomes route.get.orders.by-order-id, and GET / becomes route.get.root. Changing a path or method changes that inferred identity. Route IDs also name generated-client operations.

An explicit id can preserve operation identity during a move, but it does not preserve the old URL. Review both URL compatibility and generated-client compatibility when renaming routes.

Verify discovery and matching

From your application’s root:

bun run check

Inspect the checked graph and .relkit/generated/openapi.json for every intended method and path. Use HTTP integration tests for a static path beside a dynamic path, an unknown path, and both forms of an optional catch-all. The first-route walkthrough shows the test harness.

Reference and source