Skip to main content

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

1

Install Horse via Boss

The easiest way to install Horse is using Boss:
boss install horse
Don’t have Boss? See the Installation Guide for alternative methods.
2

Add Horse to your project

In your uses clause, add:
uses
  Horse;

Your First Server (Delphi)

Create a new console application and add this code:
Console.dpr
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.
1

Compile and run

Press F9 to compile and run your application.
2

Test your server

Open a browser or use curl:
curl http://localhost:9000/ping
You should see:
pong

Your First Server (Lazarus)

For Lazarus/Free Pascal, use this code:
Console.lpr
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

Working with Headers

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:
Main.Form.pas
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:
  1. Check if another application is using the port
  2. Change the port number in THorse.Listen()
  3. On Windows, use netstat -ano | findstr :9000 to find the process
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

Build docs developers (and LLMs) love