Patterns for developing REST APIs with Horse framework in Delphi
Scanned 9/8/2026
Install to Claude Code
npx -y skills add delphicleancode/delphi-spec-kit --skill horse-framework --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Horse Framework?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/delphicleancode-horse-framework-delphi-spec-kit)More formats (shields.io, HTML) on the badges page.
---
name: "Horse Framework"
description: "Patterns for developing REST APIs with Horse framework in Delphi"
---
# Horse Framework — Skill
Use this skill when creating REST APIs with the **Horse** framework in Delphi.
## When to Use
- When creating a new REST API project
- When adding routes and endpoints
- When implementing middleware (JWT, CORS, logging)
- When integrating Horse with FireDAC
## About Horse
Horse is a minimalist and performant REST framework for Delphi, inspired by Express.js (Node.js). It uses the middleware chain concept and is extremely simple to configure.
- **Repository:** [github.com/HashLoad/horse](https://github.com/HashLoad/horse)
- **Installation:** Boss (`boss install horse`) or manual
## Project Structure
```
src/
├── MeuApp.dpr ← Projeto principal
├── Controllers/
│ ├── MeuApp.Controller.Customer.pas
│ ├── MeuApp.Controller.Product.pas
│ └── MeuApp.Controller.Health.pas
├── Middleware/
│ ├── MeuApp.Middleware.Auth.pas
│ ├── MeuApp.Middleware.Logger.pas
│ └── MeuApp.Middleware.CORS.pas
├── Domain/
│ ├── MeuApp.Domain.Customer.Entity.pas
│ └── MeuApp.Domain.Customer.Repository.Intf.pas
├── Application/
│ └── MeuApp.Application.Customer.Service.pas
├── Infrastructure/
│ └── MeuApp.Infra.Customer.Repository.pas
└── Config/
└── MeuApp.Config.Server.pas
```
## Basic Server Configuration
```pascal
program MeuApp;
{$APPTYPE CONSOLE}
uses
Horse,
Horse.Jhonson, // JSON middleware
Horse.CORS, // CORS middleware
Horse.HandleException,
MeuApp.Controller.Customer,
MeuApp.Controller.Health;
begin
THorse.Use(Jhonson);
THorse.Use(CORS);
THorse.Use(HandleException);
// Registrar rotas
TCustomerController.RegisterRoutes;
THealthController.RegisterRoutes;
THorse.Listen(9000,
procedure
begin
Writeln('Server running on port 9000');
end
);
end.
```
## Controller Pattern
```pascal
unit MeuApp.Controller.Customer;
interface
uses
Horse,
System.JSON;
type
TCustomerController = class
public
class procedure RegisterRoutes;
private
class procedure GetAll(AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
class procedure GetById(AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
class procedure Create(AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
class procedure Update(AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
class procedure Delete(AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
end;
implementation
uses
System.SysUtils,
MeuApp.Application.Customer.Service.Intf;
class procedure TCustomerController.RegisterRoutes;
begin
THorse.Get('/api/customers', GetAll);
THorse.Get('/api/customers/:id', GetById);
THorse.Post('/api/customers', Create);
THorse.Put('/api/customers/:id', Update);
THorse.Delete('/api/customers/:id', Delete);
end;
class procedure TCustomerController.GetAll(
AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
var
LService: ICustomerService;
LResult: TJSONArray;
begin
LService := TServiceFactory.CreateCustomerService;
LResult := LService.GetAllAsJSON;
ARes.Send<TJSONArray>(LResult).Status(THTTPStatus.OK);
end;
class procedure TCustomerController.GetById(
AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
var
LService: ICustomerService;
LId: Integer;
LResult: TJSONObject;
begin
LId := AReq.Params['id'].ToInteger;
LService := TServiceFactory.CreateCustomerService;
LResult := LService.GetByIdAsJSON(LId);
if not Assigned(LResult) then
begin
ARes.Send('Customer not found').Status(THTTPStatus.NotFound);
Exit;
end;
ARes.Send<TJSONObject>(LResult).Status(THTTPStatus.OK);
end;
class procedure TCustomerController.Create(
AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
var
LService: ICustomerService;
LBody: TJSONObject;
begin
LBody := AReq.Body<TJSONObject>;
LService := TServiceFactory.CreateCustomerService;
try
LService.CreateFromJSON(LBody);
ARes.Send('Created').Status(THTTPStatus.Created);
except
on E: EValidationException do
ARes.Send(E.Message).Status(THTTPStatus.BadRequest);
on E: EBusinessRuleException do
ARes.Send(E.Message).Status(THTTPStatus.Conflict);
end;
end;
```
## Custom Middleware
```pascal
unit MeuApp.Middleware.Auth;
interface
uses
Horse;
procedure AuthMiddleware(AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
implementation
uses
System.SysUtils,
Horse.JWT;
procedure AuthMiddleware(AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
var
LToken: string;
begin
LToken := AReq.Headers['Authorization'];
if LToken.IsEmpty then
begin
ARes.Send('Token not provided').Status(THTTPStatus.Unauthorized);
Exit;
end;
// Validar token JWT
if not ValidateJWTToken(LToken) then
begin
ARes.Send('Invalid token').Status(THTTPStatus.Unauthorized);
Exit;
end;
ANext; // continua para o próximo handler
end;
end.
```
## Logger Middleware
```pascal
unit MeuApp.Middleware.Logger;
interface
uses
Horse;
procedure LoggerMiddleware(AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
implementation
uses
System.SysUtils,
System.DateUtils;
procedure LoggerMiddleware(AReq: THorseRequest; ARes: THorseResponse; ANext: TProc);
var
LStartTime: TDateTime;
begin
LStartTime := Now;
Writeln(Format('[%s] %s %s',
[FormatDateTime('hh:nn:ss', Now),
AReq.MethodType.ToString,
AReq.RawWebRequest.PathInfo]));
ANext;
Writeln(Format('[%s] Completed in %dms',
[FormatDateTime('hh:nn:ss', Now),
MilliSecondsBetween(Now, LStartTime)]));
end;
end.
```
## Conventions for Horse
| Appearance | Convention |
|---------|-----------|
| **URLs** | Kebab-case, plural: `/api/customers`, `/api/order-items` |
| **HTTP Methods** | GET (list/search), POST (create), PUT (update), DELETE (remove) |
| **Status** | 200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Internal |
| **Response** | Always JSON via `Jhonson` middleware |
| **Controller** | Static class methods with `RegisterRoutes` |
| **Middleware** | Standalone procedures or class methods |
| **Parameters** | Path: `AReq.Params['id']`, Query: `AReq.Query['search']` |
## Horse Add-on Packages
| Package | Usage | Boss Installation |
|--------|-----|-----------------|
| `horse-jhonson` | JSON middleware | `boss install horse-jhonson` |
| `horse-cors` | CORS | `boss install horse-cors` |
| `horse-jwt` | JWT Authentication | `boss install horse-jwt` |
| `horse-basic-auth` | Basic Auth | `boss install horse-basic-auth` |
| `horse-handle-exception` | Error handler | `boss install horse-handle-exception` |
| `horse-octet-stream` | Upload/download | `boss install horse-octet-stream` |
## Checklist for Horse Projects
- [ ] `Jhonson` registered middleware for automatic JSON?
- [ ] `CORS` registered middleware?
- [ ] `HandleException` middleware for error handling?
- [ ] Routes follow RESTful convention (plural, kebab-case)?
- [ ] Controllers use Services (do not access direct repository)?
- [ ] Guard clauses in handlers before logic?
- [ ] Correct HTTP status in responses?
- [ ] Integration tests with `TRESTClient` simulating calls?
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!