| name | horse-multi-instance |
| description | Guidelines for instantiating and configuring independent Horse server instances (THorseInstance) running concurrently on different ports. |
Horse Multi-Instance
Decoupled Server Contexts
Use THorseInstance when your application needs to expose multiple separate HTTP interfaces (e.g., a public REST API on port 8080 and an admin/telemetry dashboard on port 9090) within the same process.
Each THorseInstance allocates its own isolated:
- Routing tables
- Middleware pipelines
- Active requests telemetry counters
- Lifecycle hooks (
onRequest, preParsing, preValidation, onSend, onResponse)
Declaring and Configuring Instances
Always create and configure THorseInstance explicitly, registering endpoints and middlewares directly on the instance:
var
FPublicApi: THorseInstance;
FAdminPanel: THorseInstance;
begin
// 1. Public API configuration
FPublicApi := THorseInstance.Create;
FPublicApi.Use(Jhonson); // Middleware registered only for FPublicApi
FPublicApi.Get('/api/v1/ping', DoPingHandler);
// 2. Admin Panel configuration
FAdminPanel := THorseInstance.Create;
FAdminPanel.Get('/admin/metrics', DoMetricsHandler);
end;
Starting Listeners Concurrently
Since listeners enter blocking execution loops (depending on the provider and application shape), you must start the physical socket listening in separate threads:
// Start Instance 1 in a background thread
TThread.CreateAnonymousThread(
procedure
begin
FPublicApi.Listen(9001);
end).Start;
// Start Instance 2 in a background thread
TThread.CreateAnonymousThread(
procedure
begin
FAdminPanel.Listen(9002);
end).Start;
Stopping Servers and Freeing Resources
When shutting down, stop listening on each instance before freeing the objects:
FPublicApi.StopListen;
FAdminPanel.StopListen;
FPublicApi.Free;
FAdminPanel.Free;
Design Safeguards & AI Best Practices
- Never write static vars inside local middlewares: Ensure any middleware used inside a
THorseInstance relies on instance-specific configuration or request-scoped services (Req.Services).
- Backward Compatibility: If the project does not require multiple instances, continue using the static
THorse facade class. It is automatically routed to a default instance under the hood.
- FPC/Lazarus Compatibility: When writing samples or libraries for Lazarus, avoid Delphi anonymous procedures inline (
procedure begin end) for route handlers. Use standard global procedures instead.