| name | horse-app-structure |
| description | Guide for setting up Horse applications, bootstrap program (.dpr), basic console initialization, and registering modules. |
Horse App Structure
Application Bootstrap (.dpr)
A standard Horse application is typically configured as a Delphi Console Application ({$APPTYPE CONSOLE}).
program MyHorseApp;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Horse,
Horse.Jhonson,
Horse.CORS;
begin
// 1. Opt-in for the High-Performance Radix Tree Router (highly recommended)
THorse.UseRadixRouter;
// 2. Register Global Middlewares
THorse
.Use(CORS)
.Use(Jhonson);
// 3. Register Routes / Controllers
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Send('pong');
end);
// 4. Start Server with Listen Callback (prevents sequential execution blocking)
THorse.Listen(9000,
procedure
begin
Writeln('Server is running on port ' + THorse.Port.ToString);
end);
end.
Modular Architecture
For production grade projects, keep the .dpr file clean by delegating route definitions to specialized Controller classes:
- Keep Controllers stateless: Do not store request-specific variables as fields in a controller class unless they are thread-safe.
- Uses Sequence: Ensure modules containing business services or repository layers are decoupled from the HTTP transport layer (
Horse units).
Thread-Safe Data Access & Database Connections
Horse is inherently multithreaded (each request runs on a separate worker thread). Accessing a database inside a route handler requires strict thread safety to avoid memory corruption, race conditions, or application crashes (Access Violations):
- NEVER share connection components globally: Do not put a global
TFDConnection or query component in your Controller or DataModule and share it across handlers.
- Local instantiation (Thread-local): Always create the connection and query components inside the route handler, and release them in a
try...finally block (Delphi XE7+ compatible):
procedure GetCustomerHandler(Req: THorseRequest; Res: THorseResponse; Next: TProc);
var
LConnection: TFDConnection;
LQuery: TFDQuery;
begin
LConnection := TFDConnection.Create(nil);
LQuery := TFDQuery.Create(nil);
try
// Configure connection locally (preferably utilizing FireDAC Connection Pooling)
LConnection.ConnectionDefName := 'MyPooledConnectionDef';
LConnection.Connected := True;
LQuery.Connection := LConnection;
LQuery.SQL.Text := 'SELECT * FROM CUSTOMERS WHERE ID = :ID';
LQuery.ParamByName('ID').AsInteger := Req.Params.Field('id').AsInteger;
LQuery.Open;
Res.Send(LQuery.ToJSONArray);
finally
LQuery.Free;
LConnection.Free;
end;
end;