Skip to content

Latest commit

 

History

History
168 lines (122 loc) · 5.65 KB

File metadata and controls

168 lines (122 loc) · 5.65 KB

Routing

Read this in English or Português (BR).

A route binds a URL path + HTTP method to a callback. This document covers everything Horse can express in a route definition.

For the API of the request and response objects passed to each callback, see Request & Response.


Basic routes

THorse exposes one method per HTTP verb. Each takes a path string and a callback:

THorse.Get   ('/ping',     procedure(Req: THorseRequest; Res: THorseResponse) begin Res.Send('pong');    end);
THorse.Post  ('/items',    procedure(Req: THorseRequest; Res: THorseResponse) begin Res.Send('created'); end);
THorse.Put   ('/items/:id', ...);
THorse.Patch ('/items/:id', ...);
THorse.Delete('/items/:id', ...);
THorse.Head  ('/items/:id', ...);

Method-routing is exact: THorse.Get only matches GET requests to that path. A request with the wrong method on a known path returns 405 Method Not Allowed. A request with an unknown path returns 404 Not Found.

Path parameters

Use a colon-prefixed segment to capture part of the URL:

THorse.Get('/users/:id',
  procedure(Req: THorseRequest; Res: THorseResponse)
  begin
    Res.Send('User: ' + Req.Params['id']);
  end);
  • GET /users/42User: 42 (and Req.Params['id'] = '42')
  • GET /users/404
  • GET /users404

Multiple parameters in a single path work the same way:

THorse.Get('/teams/:teamId/members/:memberId',
  procedure(Req: THorseRequest; Res: THorseResponse)
  var
    T, M: string;
  begin
    T := Req.Params['teamId'];
    M := Req.Params['memberId'];
    Res.Send(Format('Team %s, member %s', [T, M]));
  end);

Path parameters are always strings. Convert them yourself with StrToInt, TryStrToInt, etc.

Query strings

Query strings are accessed via Req.Query:

// GET /search?name=Horse&category=framework
THorse.Get('/search',
  procedure(Req: THorseRequest; Res: THorseResponse)
  begin
    Res.Send('Looking for ' + Req.Query['name'] + ' in ' + Req.Query['category']);
  end);

Missing keys return an empty string. If you need to know whether a key was present, use Req.Query.TryGetValue (returns False when absent).

Route groups

Group related routes under a common prefix:

THorse.Group.Prefix('/api/v1')
  .Get   ('/users',     ListUsers)
  .Post  ('/users',     CreateUser)
  .Get   ('/users/:id', GetUser)
  .Put   ('/users/:id', UpdateUser)
  .Delete('/users/:id', DeleteUser);

The above is identical to writing THorse.Get('/api/v1/users', ...), etc. Groups can carry their own middleware:

THorse.Group
  .Use(JWT(SECRET))           // middleware applies to everything in this group
  .Prefix('/api/v1/admin')
  .Get ('/stats', GetStats)
  .Post('/audit', WriteAudit);

Wildcard middleware

THorse.Use(...) registers middleware that runs on every request, regardless of path:

THorse.Use(MyLogger);            // every request is logged
THorse.Use('/api', RequireAuth); // only /api/* requires auth

See Middleware for the full story.

The TMethodType enum

Internally, routes are stored by method. The enum lives in Horse.Commons:

type
  TMethodType = (mtAny, mtGet, mtPut, mtPost, mtHead, mtDelete, mtPatch);

mtAny is the wildcard used by middleware (THorse.Use) — it matches any method.

Note: OPTIONS, TRACE, and CONNECT are not in TMethodType. They route as mtAny (matching wildcard middleware only). The Horse.CORS middleware uses this to intercept OPTIONS for preflight handling. If you need to discriminate by raw verb in your own middleware, use Req.Method: string or Req.RawWebRequest.Method.

Pattern matching rules

  • Case-sensitive path matching: /Users and /users are different routes.
  • No trailing slash normalisation: /users and /users/ are different routes. Decide your project convention and stick to it.
  • First-registered wins for identical patterns — registering /users/:id twice is a duplicate; the second registration emits a runtime error in recent Horse versions (Duplicate route detected: [GET] /users/:id).
  • Parameter segments cannot have multiple colons/users/:id:name is invalid; use two segments instead (/users/:id/:name).

Sub-resources

There's no built-in mount like Express, but you can express sub-resources with groups:

procedure RegisterUsersRoutes(AGroup: THorseCoreGroup);
begin
  AGroup
    .Get   ('',         ListUsers)
    .Post  ('',         CreateUser)
    .Get   ('/:id',     GetUser);
end;

// In main:
RegisterUsersRoutes(THorse.Group.Prefix('/api/v1/users'));

The same RegisterUsersRoutes can be mounted under multiple prefixes (/api/v1/users and /api/v2/users) without duplication — useful for API versioning when v2 only adds new endpoints.

Listing routes (for debugging)

Horse doesn't ship a printRoutes() helper, but you can iterate the router tree directly. The simpler approach is to print each route as you register it:

procedure RegisterAndLog(const Method, Path: string; Cb: THorseCallback);
begin
  case Method of
    'GET':  THorse.Get   (Path, Cb);
    'POST': THorse.Post  (Path, Cb);
    // ...
  end;
  WriteLn(Method, ' ', Path);
end;

For non-trivial apps, keep the route registration centralised in one unit so the layout is obvious from a single file.

See also

  • Request & Response — what you do inside the callback.
  • MiddlewareTHorse.Use, the Next proc, registration order.
  • Providers — how the transport layer hands the request to your route callback.