Horse framework
Skill delphicleancode/delphi-spec-kit/.gemini/skills/horse-framework
An opinionated ecosystem of rules, skills, and steerings to elevate Delphi development to a state-of-the-art level with Artificial Intelligence.
npx -y skills add delphicleancode/delphi-spec-kit --skill horse-frameworkAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Patterns for developing REST APIs with Horse framework in Delphi
SKILL.md
7.2 KB, as published. Nobody here has run it
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
- 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
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
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
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
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
-
Jhonsonregistered middleware for automatic JSON? -
CORSregistered middleware? -
HandleExceptionmiddleware 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
TRESTClientsimulating calls?