Delphi-spec-kit Horse Framework
Patterns for developing REST APIs with Horse framework in Delphi
install
source · Clone the upstream repo
git clone https://github.com/delphicleancode/delphi-spec-kit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/delphicleancode/delphi-spec-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.gemini/skills/horse-framework" ~/.claude/skills/delphicleancode-delphi-spec-kit-horse-framework-279c12 && rm -rf "$T"
manifest:
.gemini/skills/horse-framework/SKILL.mdsource content
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 (
) or manualboss install horse
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: , |
| 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 middleware |
| Controller | Static class methods with |
| Middleware | Standalone procedures or class methods |
| Parameters | Path: , Query: |
Horse Add-on Packages
| Package | Usage | Boss Installation |
|---|---|---|
| JSON middleware | |
| CORS | |
| JWT Authentication | |
| Basic Auth | |
| Error handler | |
| Upload/download | |
Checklist for Horse Projects
-
registered middleware for automatic JSON?Jhonson -
registered middleware?CORS -
middleware for error handling?HandleException - 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
simulating calls?TRESTClient