# Horse Routing

> Guidelines for setting up endpoints, defining path parameters, route wildcards, and structuring HTTP controllers.

- Skill: `hashload/horse-routing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add hashload/horse-routing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hashload/horse-routing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: hashload (https://skillmd.com/u/hashload)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hashload/horse-routing

---


# Horse Routing

## Defining HTTP Endpoints
Horse supports registering endpoints for all standard HTTP verbs using static or instance methods:

```pascal
THorse.Get('/products', GetProductsHandler);
THorse.Post('/products', CreateProductHandler);
THorse.Put('/products', UpdateProductHandler);
THorse.Delete('/products', DeleteProductHandler);
THorse.Patch('/products', PatchProductHandler);
```

---

## Route Parameters
Path parameters are defined using the **colon syntax (`:param`)**:

*   **Correct**: `THorse.Get('/users/:id', GetUserHandler);`
*   **Incorrect**: `THorse.Get('/users/{id}', GetUserHandler);`

Inside the handler, you access the parameter via `Req.Params.Items['id']` (or `Req.Params['id']`).

---

## Wildcards
Use the wildcard asterisk (`*`) to match any subpath:

```pascal
// Matches /public/css/site.css, /public/images/logo.png, etc.
THorse.Get('/public/*', ServeStaticFilesHandler);
```

---

## Route Grouping & Prefixes (THorse.Group)
You can group related routes under a common path prefix using `THorse.Group`. This is highly useful for API versioning (e.g., `/v1`) and isolating group-level middlewares:

*   **Prefixing Routes**:
    ```pascal
    THorse.Group.Prefix('/v1')
      .Get('/products', GetProductsHandler)
      .Get('/customers', GetCustomersHandler);
    ```

*   **Group-level Middlewares (Restricted Middlewares)**:
    Instead of registering a middleware globally via `THorse.Use`, you can register it specifically for a group using `.Use()`. The middleware will only run for endpoints defined inside that group:
    ```pascal
    THorse.Group.Prefix('/admin')
      .Use(AuthMiddleware) // Only runs for routes under /admin
      .Get('/dashboard', GetDashboardHandler)
      .Get('/settings', GetSettingsHandler);
    ```

## Route-level Middlewares (Local)
You can pass an array of route-specific middlewares (`array of THorseCallback`) to apply checks only to a single endpoint. These run after global and group-level middlewares:

*   **Static routing**:
    ```pascal
    THorse.Get('/admin/dashboard', [AuthMiddleware, LoggerMiddleware], GetDashboardHandler);
    ```

*   **Fluent routing**:
    ```pascal
    THorse.Route('/reports')
      .Get([AuthMiddleware, LoggerMiddleware], GetReportsHandler)
      .Post([AuthMiddleware], CreateReportHandler);
    ```

---

## Grouping with Controllers
To keep routes organized, group them logically within static class methods or dedicated units:

```pascal
type
  TUserController = class
  public
    class procedure RegisterRoutes;
    class procedure ListUsers(Req: THorseRequest; Res: THorseResponse; Next: TProc);
  end;

class procedure TUserController.RegisterRoutes;
begin
  THorse.Get('/users', ListUsers);
end;
```

