# Horse Database Pooling

> Guide for setting up thread-safe database connection pooling (FireDAC / UniDAC) in multithreaded Horse applications.

- Skill: `hashload/horse-database-pooling` (Agent Skill)
- Install (CLI): `npx skillmds@latest add hashload/horse-database-pooling`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hashload/horse-database-pooling/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: hashload (https://skillmd.com/u/hashload)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hashload/horse-database-pooling

---


# Horse Database Pooling

## 1. The Multithreaded Database Rule
Horse handles each incoming HTTP request in a separate thread. Therefore, sharing a single database connection components (like a global `TFDConnection` or query component placed on a global DataModule/Controller) across requests **will cause race conditions, data corruption, and Access Violations**.

*   **Rule**: Every route handler thread must instantiate its own database connection (or request one from a thread-safe connection pool) and free it immediately after use.

---

## 2. Setting Up FireDAC Connection Pooling
FireDAC provides built-in, highly optimized connection pooling. To configure and use it:

### Phase A: Defining the Connection Definition (Global Setup)
Configure the connection definition globally in your application bootstrap (`program` block or startup class) using `TFDManager`:

```pascal
uses
  FireDAC.Stan.Intf, FireDAC.Phys.PG, FireDAC.Comp.Client;

procedure SetupConnectionPool;
var
  LParams: TStrings;
begin
  LParams := TStringList.Create;
  try
    LParams.Values['DriverID'] := 'PG'; // PostgreSQL example
    LParams.Values['Server'] := 'localhost';
    LParams.Values['Database'] := 'my_db';
    LParams.Values['User_Name'] := 'postgres';
    LParams.Values['Password'] := 'secret';
    LParams.Values['Pooled'] := 'True'; // CRITICAL: Enables pooling
    LParams.Values['POOL_MaximumItems'] := '50'; // Set maximum connections in pool
    
    // Register the definition with FDManager
    FDManager.AddConnectionDef('MyPooledPGDef', 'PG', LParams);
  finally
    LParams.Free;
  end;
end;
```

### Phase B: Acquiring and Releasing Connections in Handlers
In your route handlers, instantiate the `TFDConnection` locally, referencing the pooled definition name. Always protect resources with `try...finally`:

```pascal
procedure GetUsersHandler(Req: THorseRequest; Res: THorseResponse; Next: TProc);
var
  LConnection: TFDConnection;
  LQuery: TFDQuery;
begin
  LConnection := TFDConnection.Create(nil);
  LQuery := TFDQuery.Create(nil);
  try
    // Reference the pooled definition - this will instantly pull a connection from the pool
    LConnection.ConnectionDefName := 'MyPooledPGDef';
    LConnection.Connected := True;
    
    LQuery.Connection := LConnection;
    LQuery.SQL.Text := 'SELECT id, name FROM users';
    LQuery.Open;
    
    // Johnson middleware takes ownership of the JSON object/array
    Res.Send(LQuery.ToJSONArray);
  finally
    LQuery.Free;
    LConnection.Free; // Automatically releases connection back to the pool
  end;
end;
```

---

## 3. Setting Up UniDAC Connection Pooling
If using Devart UniDAC, enable pooling via the connection string parameter or using `TUniConnection.SpecificOptions`:

```pascal
procedure GetUsersUniDACHandler(Req: THorseRequest; Res: THorseResponse; Next: TProc);
var
  LConnection: TUniConnection;
  LQuery: TUniQuery;
begin
  LConnection := TUniConnection.Create(nil);
  LQuery := TUniQuery.Create(nil);
  try
    LConnection.ProviderName := 'PostgreSQL';
    LConnection.Server := 'localhost';
    LConnection.Database := 'my_db';
    LConnection.Username := 'postgres';
    LConnection.Password := 'secret';
    
    // Enable Pooling in UniDAC
    LConnection.Pooling := True;
    LConnection.PoolingOptions.MaxPoolSize := 50;
    
    LConnection.Connect;
    
    LQuery.Connection := LConnection;
    LQuery.SQL.Text := 'SELECT id, name FROM users';
    LQuery.Open;
    
    Res.Send(LQuery.ToJSONArray);
  finally
    LQuery.Free;
    LConnection.Free;
  end;
end;

---

## 3.5. Automated Connection Management via Request Services (IoC)
With Horse's native Request Scope IoC container, you can register a lazy factory for database connections. This ensures the connection is only opened if/when a route resolves it, and guarantees it is freed automatically when the request ends.

```pascal
// Setup connection factory inside a global middleware
THorse.Use(procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
  begin
    Req.Services.AddFactory(TFDConnection,
      function: TObject
      begin
        Result := TFDConnection.Create(nil);
        TFDConnection(Result).ConnectionDefName := 'MyPooledPGDef';
        TFDConnection(Result).Connected := True;
      end);
    Next();
  end);

// Usage in Route Handler (No manual connection free required!)
THorse.Get('/users', procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
  var
    LConnection: TFDConnection;
    LQuery: TFDQuery;
  begin
    LConnection := TFDConnection(Req.Services.Resolve(TFDConnection));
    LQuery := TFDQuery.Create(nil);
    try
      LQuery.Connection := LConnection;
      LQuery.SQL.Text := 'SELECT id, name FROM users';
      LQuery.Open;
      Res.Send(LQuery.ToJSONArray);
    finally
      LQuery.Free;
    end; // LConnection is freed automatically by Horse when the request finishes!
  end);
```

---

## 4. Best Practices for Database Handlers
1.  **Keep Queries Short**: Hold database connections open for the absolute shortest time possible. Perform non-database calculations before opening or after closing the connection.
2.  **Always use `try...finally`**: Ensure connection/query components are freed even if an exception occurs during SQL execution.
3.  **Read-Only Operations**: Set `ReadOnly := True` on your queries where possible to improve pooling performance.

