Quick Start Guide
Get your first Horse web server up and running in just a few minutes. This guide will walk you through creating a simple REST API.
Prerequisites
Before you begin, ensure you have:
Delphi XE7 or later, OR Lazarus/Free Pascal
Boss package manager (recommended)
Basic knowledge of Delphi/Pascal
Installation
Install Horse via Boss
The easiest way to install Horse is using Boss:
Add Horse to your project
In your uses clause, add:
Your First Server (Delphi)
Create a new console application and add this code:
program Console;
{$APPTYPE CONSOLE}
uses
Horse,
System.SysUtils;
begin
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
THorse.Listen(9000,
procedure
begin
Writeln(Format('Server is running on %s:%d', [THorse.Host, THorse.Port]));
Readln;
end);
end.
Compile and run
Press F9 to compile and run your application.
Test your server
Open a browser or use curl: curl http://localhost:9000/ping
You should see:
Your First Server (Lazarus)
For Lazarus/Free Pascal, use this code:
program Console;
{$MODE DELPHI}{$H+}
uses
Horse;
procedure GetPing (Req: THorseRequest; Res: THorseResponse);
begin
Res.Send( 'pong' );
end ;
begin
THorse.Get( '/ping' , GetPing);
THorse.Listen( 9000 );
end .
Lazarus requires the {$MODE DELPHI}{$H+} directive for compatibility.
Building a REST API
Let’s expand our server into a simple REST API for managing users:
program UserAPI;
{$APPTYPE CONSOLE}
uses
Horse,
System.SysUtils,
System.JSON;
begin
// GET - Retrieve a user
THorse.Get('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse)
var
UserId: string;
JsonResponse: TJSONObject;
begin
UserId := Req.Params['id'];
JsonResponse := TJSONObject.Create;
try
JsonResponse.AddPair('id', UserId);
JsonResponse.AddPair('name', 'John Doe');
JsonResponse.AddPair('email', '[email protected] ');
Res.Send(JsonResponse.ToString);
finally
JsonResponse.Free;
end;
end);
// POST - Create a user
THorse.Post('/users',
procedure(Req: THorseRequest; Res: THorseResponse)
var
JsonRequest: TJSONObject;
Name, Email: string;
begin
JsonRequest := TJSONObject.ParseJSONValue(Req.Body) as TJSONObject;
try
Name := JsonRequest.GetValue<string>('name');
Email := JsonRequest.GetValue<string>('email');
Res.Status(201).Send(Format('{"message": "User %s created"}', [Name]));
finally
JsonRequest.Free;
end;
end);
// PUT - Update a user
THorse.Put('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse)
var
UserId: string;
begin
UserId := Req.Params['id'];
Res.Send(Format('{"message": "User %s updated"}', [UserId]));
end);
// DELETE - Remove a user
THorse.Delete('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse)
var
UserId: string;
begin
UserId := Req.Params['id'];
Res.Status(204).Send('');
end);
THorse.Listen(9000,
procedure
begin
Writeln('User API running on port 9000');
Readln;
end);
end.
Working with Query Parameters
Access query parameters from the URL:
THorse.Get('/search',
procedure(Req: THorseRequest; Res: THorseResponse)
var
Query, Page: string;
begin
Query := Req.Query['q'];
Page := Req.Query['page'];
Res.Send(Format('Searching for "%s" on page %s', [Query, Page]));
end);
// Test: GET /search?q=delphi&page=1
Read and set HTTP headers:
THorse.Get('/headers',
procedure(Req: THorseRequest; Res: THorseResponse)
var
UserAgent, ContentType: string;
begin
// Read request headers
UserAgent := Req.Headers['User-Agent'];
ContentType := Req.ContentType;
// Set response headers
Res
.AddHeader('X-Custom-Header', 'MyValue')
.ContentType('application/json')
.Send('{"userAgent": "' + UserAgent + '"}');
end);
Setting Status Codes
Control HTTP response status codes:
THorse.Get('/resource/:id',
procedure(Req: THorseRequest; Res: THorseResponse)
var
ResourceId: string;
begin
ResourceId := Req.Params['id'];
if ResourceId = '404' then
Res.Status(404).Send('{"error": "Not found"}')
else if ResourceId = 'error' then
Res.Status(500).Send('{"error": "Internal server error"}')
else
Res.Status(200).Send('{"id": "' + ResourceId + '"}');
end);
Using Status Enums
Horse provides status code enums for better readability:
THorse.Post('/resource',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
// Create resource logic here
Res.Status(THTTPStatus.Created).Send('{"message": "Created"}');
end);
THorse.Get('/unauthorized',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Status(THTTPStatus.Unauthorized).Send('{"error": "Unauthorized"}');
end);
VCL Application Example
Run Horse within a VCL application:
unit Main.Form;
interface
uses
Winapi.Windows, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons,
System.SysUtils;
type
TFrmMain = class(TForm)
btnStart: TBitBtn;
btnStop: TBitBtn;
edtPort: TEdit;
Label1: TLabel;
procedure btnStartClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
end;
var
FrmMain: TFrmMain;
implementation
uses Horse;
{$R *.dfm}
procedure TFrmMain.FormCreate(Sender: TObject);
begin
// Define routes
THorse.Get('ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
end;
procedure TFrmMain.btnStartClick(Sender: TObject);
begin
// Start server on specified port
THorse.Listen(StrToInt(edtPort.Text));
btnStart.Enabled := False;
btnStop.Enabled := True;
edtPort.Enabled := False;
end;
procedure TFrmMain.btnStopClick(Sender: TObject);
begin
// Stop server
THorse.StopListen;
btnStart.Enabled := True;
btnStop.Enabled := False;
edtPort.Enabled := True;
end;
procedure TFrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if THorse.IsRunning then
THorse.StopListen;
end;
end.
When using Horse in VCL applications, you must define the HORSE_VCL compilation directive in your project options.
Next Steps
Now that you have a basic Horse server running, explore these topics:
Routing Learn advanced routing patterns and parameter handling
Middleware Extend functionality with middleware
JSON Handling Work with JSON requests and responses
Deployment Deploy as Windows Service, ISAPI, or Apache module
Common Issues
If you get a “port already in use” error:
Check if another application is using the port
Change the port number in THorse.Listen()
On Windows, use netstat -ano | findstr :9000 to find the process
Access violation on shutdown
Always call THorse.StopListen before your application terminates: if THorse.IsRunning then
THorse.StopListen;
Ensure middleware is registered before route definitions: THorse.Use(Middleware); // Register first
THorse.Get('/route', Handler); // Then define routes
Example Projects
Explore complete example projects in the Horse repository:
Console application
VCL application
Windows Service
ISAPI module
Apache module
Lazarus daemon
Find them at: github.com/HashLoad/horse/tree/master/samples