---
title: File-system routes
description: Choose URLs, dynamic segments, method exports, matching precedence, and stable operation identities.
pagination: false
sidebar:
  icon: folder-tree
---

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](/docs/modules/http/define) for the factories.

> **Let the CLI create the route path**
>
> [`relkit add route`](/docs/modules/http/define#scaffold-a-route) converts URL paths such as
> `/orders/:orderId` into `src/routes/orders/[orderId]/route.ts`. Choose a raw handler or map HTTP
> methods to public service functions; the conventions below apply to both.

## 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`:

```ts title="examples/commerce/src/routes/orders/[orderId]/route.ts"
/** 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](/docs/modules/http/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](/docs/modules/http/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](/docs/modules/http/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:

```sh
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](/docs/modules/http/first-route) shows the test harness.

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

**Reference and source**

- [`@relkit/routes` API](/docs/api/routes)

- [`examples/commerce/src/routes/orders/[orderId]/route.ts`](https://github.com/rel-kit/relkit/blob/main/examples/commerce/src/routes/orders/[orderId]/route.ts)
