‹ 首页

horse-framework

@delphicleancode · 收录于 1 周前

Patterns for developing REST APIs with Horse framework in Delphi

适合你,如果正在用Delphi开发RESTful服务

/ 下载安装
horse-framework.skill双击,或拖进 Claude 桌面版 / Cowork,即完成安装↓ .skill↓ .zip
用别的 agent?下载 .zip 解压,把文件夹放进它的技能目录
Claude Code~/.claude/skills/(项目级 .claude/skills/)
Codex CLI~/.codex/skills/
Cursor自动读取上面两处目录
其他工具见其文档的「skills」目录;两个下载是同一份文件,只是名字不同
/ 通过 npx 安装 校验哈希
npx oh-my-skill add delphicleancode/delphi-spec-kit/horse-framework
/ 通过 bash 安装
curl -fsSL https://oh-my-skill.com/install.sh | bash -s -- delphicleancode/delphi-spec-kit/horse-framework
/ 已经装过?验证本机副本,不用重装
npx oh-my-skill verify delphicleancode/delphi-spec-kit/horse-framework
安装目标可用 --agent / --scope 或 --to 明确指定;省略时只会在唯一已存在的 agent 目录上自动选择,零命中或多命中会停止并提示。content_hash 缺失或不一致均拒装。
43GitHub stars
~1.4K上下文体积 · 单文件
镜像托管

怎么用

技能原文 SKILL.md作者撰写 · MIT · 9f7fc31

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.

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);

  //Register broken
  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; //continue to the next 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
  • [ ] 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?
按 MIT 许可原样转载,未经改动 · 在 GitHub 查看 →

评论

登录即可评论;带「已验证安装」的,是发布者名下有本店的安装或持有记录。