Horse provides a simple and expressive API for defining HTTP routes. Each HTTP method has a corresponding class method on THorse that accepts a path and a callback function.
Basic Routing
Routes are defined using the HTTP method name followed by a path pattern and a callback procedure:
THorse.Get('/users',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Send('List of users');
end);
HTTP Methods
Horse supports all standard HTTP methods. Each method is defined as a class function in Horse.Core.pas.
GET
Handle GET requests to retrieve resources:
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Send('pong');
end);
THorse.Get('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LUserId: string;
begin
LUserId := Req.Params['id'];
Res.Send('User ID: ' + LUserId);
end);
Signature:
class function Get(const APath: string;
const ACallback: THorseCallback): THorseCore;
POST
Handle POST requests to create new resources:
THorse.Post('/users',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LBody: string;
LRequest: TJSONObject;
begin
LBody := Req.Body;
LRequest := TJSONObject.ParseJSONValue(LBody) as TJSONObject;
try
// Process the request
Res.Status(THTTPStatus.Created).Send('{"message": "User created"}');
finally
LRequest.Free;
end;
end);
Signature:
class function Post(const APath: string;
const ACallback: THorseCallback): THorseCore;
PUT
Handle PUT requests to update existing resources:
THorse.Put('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LUserId: string;
LBody: string;
begin
LUserId := Req.Params['id'];
LBody := Req.Body;
// Update user logic
Res.Send('{"message": "User updated"}');
end);
Signature:
class function Put(const APath: string;
const ACallback: THorseCallback): THorseCore;
DELETE
Handle DELETE requests to remove resources:
THorse.Delete('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LUserId: string;
begin
LUserId := Req.Params['id'];
// Delete user logic
Res.Status(THTTPStatus.NoContent).Send('');
end);
Signature:
class function Delete(const APath: string;
const ACallback: THorseCallback): THorseCore;
PATCH
Handle PATCH requests for partial updates:
THorse.Patch('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LUserId: string;
LBody: string;
begin
LUserId := Req.Params['id'];
LBody := Req.Body;
// Partial update logic
Res.Send('{"message": "User partially updated"}');
end);
Signature:
class function Patch(const APath: string;
const ACallback: THorseCallback): THorseCore;
PATCH is available in Free Pascal and Delphi versions greater than XE3 (CompilerVersion > 27.0).
HEAD
Handle HEAD requests (similar to GET but without response body):
THorse.Head('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
// Check if resource exists
Res.Status(THTTPStatus.OK);
end);
Signature:
class function Head(const APath: string;
const ACallback: THorseCallback): THorseCore;
All Methods
Register a route handler for all HTTP methods:
THorse.All('/api/*',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
// This handles GET, POST, PUT, DELETE, PATCH, HEAD
Res.Send('Any method accepted');
end);
Signature:
class function All(const APath: string;
const ACallback: THorseCallback): THorseCore;
Route Parameters
Route parameters are defined using the :param syntax in the path:
THorse.Get('/users/:userId/posts/:postId',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LUserId, LPostId: string;
begin
LUserId := Req.Params['userId'];
LPostId := Req.Params['postId'];
Res.Send(Format('User: %s, Post: %s', [LUserId, LPostId]));
end);
Accessing Parameters
Route parameters are available through Req.Params:
var
LId: string;
begin
LId := Req.Params['id'];
// or check if exists
if Req.Params.ContainsKey('id') then
LId := Req.Params['id'];
end;
Query Strings
Query string parameters are accessed via Req.Query:
THorse.Get('/search',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LSearchTerm: string;
LPage: string;
begin
LSearchTerm := Req.Query['q'];
LPage := Req.Query['page'];
Res.Send(Format('Search: %s, Page: %s', [LSearchTerm, LPage]));
end);
GET /search?q=horse&page=1
Callback Signatures
Horse supports multiple callback signatures for convenience:
Full Callback
The standard callback with Request, Response, and Next:
THorseCallback = procedure(Req: THorseRequest;
Res: THorseResponse;
Next: TProc);
Request and Response Only
THorseCallbackRequestResponse = procedure(Req: THorseRequest;
Res: THorseResponse);
Request Only
THorseCallbackRequest = procedure(Req: THorseRequest);
Response Only (Delphi only)
THorseCallbackResponse = procedure(Res: THorseResponse);
Route Method Chaining
Define multiple methods for the same path using Route():
THorse
.Route('/users')
.Get(DoGetUsers)
.Post(DoCreateUser)
.Route('/users/:id')
.Get(DoGetUser)
.Put(DoUpdateUser)
.Delete(DoDeleteUser);
Complete Example
program RestAPI;
uses
Horse,
System.JSON,
System.SysUtils;
begin
// List all users
THorse.Get('/users',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LList: TJSONArray;
begin
LList := TJSONArray.Create;
try
LList.Add(TJSONObject.Create.AddPair('id', '1').AddPair('name', 'John'));
LList.Add(TJSONObject.Create.AddPair('id', '2').AddPair('name', 'Jane'));
Res.Send(LList.ToString);
finally
LList.Free;
end;
end);
// Get single user
THorse.Get('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LUserId: string;
LUser: TJSONObject;
begin
LUserId := Req.Params['id'];
LUser := TJSONObject.Create;
try
LUser.AddPair('id', LUserId);
LUser.AddPair('name', 'User ' + LUserId);
Res.Send(LUser.ToString);
finally
LUser.Free;
end;
end);
// Create user
THorse.Post('/users',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LRequest: TJSONObject;
LName: string;
begin
LRequest := TJSONObject.ParseJSONValue(Req.Body) as TJSONObject;
try
LName := LRequest.GetValue('name').Value;
Res.Status(THTTPStatus.Created)
.Send('{"message": "User ' + LName + ' created"}');
finally
LRequest.Free;
end;
end);
// Update user
THorse.Put('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LUserId: string;
begin
LUserId := Req.Params['id'];
Res.Send('{"message": "User ' + LUserId + ' updated"}');
end);
// Delete user
THorse.Delete('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Status(THTTPStatus.NoContent).Send('');
end);
THorse.Listen(9000);
end.
Path Normalization
Horse automatically normalizes paths by trimming leading and trailing slashes:
// These are all equivalent:
THorse.Get('/users', Handler);
THorse.Get('users', Handler);
THorse.Get('/users/', Handler);
THorse.Get('users/', Handler);
The internal TrimPath function in Horse.Core.pas:229 ensures consistent path handling.