Roteamento
July 11, 2026 · View on GitHub
Leia em English ou Português (BR).
Uma rota liga um caminho de URL + método HTTP a um callback. Este documento cobre tudo o que o Horse expressa numa definição de rota.
Para a API dos objetos de request e response passados a cada callback, veja Request e Response.
Rotas básicas
THorse expõe um método por verbo HTTP. Cada um recebe uma string de caminho e um callback:
THorse.Get ('/ping', procedure(Req: THorseRequest; Res: THorseResponse) begin Res.Send('pong'); end);
THorse.Post ('/items', procedure(Req: THorseRequest; Res: THorseResponse) begin Res.Send('created'); end);
THorse.Put ('/items/:id', ...);
THorse.Patch ('/items/:id', ...);
THorse.Delete('/items/:id', ...);
THorse.Head ('/items/:id', ...);
O roteamento por método é exato: THorse.Get só atende requisições GET para aquele caminho. Uma requisição com método errado em um caminho conhecido retorna 405 Method Not Allowed. Uma requisição com caminho desconhecido retorna 404 Not Found.
Parâmetros de caminho
Use um segmento prefixado com dois-pontos para capturar parte da URL:
THorse.Get('/users/:id',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('User: ' + Req.Params['id']);
end);
GET /users/42→User: 42(eReq.Params['id'] = '42')GET /users/→404GET /users→404
Múltiplos parâmetros num só caminho funcionam da mesma forma:
THorse.Get('/teams/:teamId/members/:memberId',
procedure(Req: THorseRequest; Res: THorseResponse)
var
T, M: string;
begin
T := Req.Params['teamId'];
M := Req.Params['memberId'];
Res.Send(Format('Team %s, member %s', [T, M]));
end);
Parâmetros de caminho são sempre strings. Converta você mesmo com StrToInt, TryStrToInt, etc.
Query strings
Query strings são acessadas via Req.Query:
// GET /search?name=Horse&category=framework
THorse.Get('/search',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('Procurando ' + Req.Query['name'] + ' em ' + Req.Query['category']);
end);
Chaves ausentes retornam string vazia. Se precisa distinguir ausente de vazio, use Req.Query.TryGetValue (retorna False quando ausente).
Grupos de rotas
Agrupe rotas relacionadas sob um prefixo comum:
THorse.Group.Prefix('/api/v1')
.Get ('/users', ListUsers)
.Post ('/users', CreateUser)
.Get ('/users/:id', GetUser)
.Put ('/users/:id', UpdateUser)
.Delete('/users/:id', DeleteUser);
O acima é idêntico a escrever THorse.Get('/api/v1/users', ...), etc. Grupos podem ter middleware próprio:
THorse.Group
.Use(JWT(SECRET)) // middleware aplica a tudo neste grupo
.Prefix('/api/v1/admin')
.Get ('/stats', GetStats)
.Post('/audit', WriteAudit);
Middlewares por Rota (Locais)
Você pode passar uma cadeia de middlewares específicos para uma única rota em formato de array (array of THorseCallback):
// Sintaxe Estática com array de middlewares
THorse.Get('/admin/dashboard', [MiddlewareAutenticacao, MiddlewareLogAcesso],
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('Painel Administrativo');
end);
// Sintaxe Fluente de Rota com array de middlewares
THorse.Route('/relatorios')
.Get([MiddlewareAutenticacao, MiddlewareLogAcesso], GetRelatorioHandler)
.Post([MiddlewareAutenticacao], PostRelatorioHandler);
Os middlewares de rota são executados após os middlewares globais e após os middlewares do grupo da rota, mas antes do callback final (handler) da rota.
Middleware wildcard
THorse.Use(...) registra middleware que roda em toda requisição, independentemente do caminho:
THorse.Use(MyLogger); // toda requisição é logada
THorse.Use('/api', RequireAuth); // apenas /api/* exige auth
Veja Middleware para a história completa.
O enum TMethodType
Internamente as rotas são armazenadas por método. O enum fica em Horse.Commons:
type
TMethodType = (mtAny, mtGet, mtPut, mtPost, mtHead, mtDelete, mtPatch);
mtAny é o wildcard usado pelo middleware (THorse.Use) — casa com qualquer método.
Nota:
OPTIONS,TRACEeCONNECTnão estão emTMethodType. Eles roteiam comomtAny(casando apenas com middleware wildcard). O middlewareHorse.CORSaproveita isso para interceptarOPTIONSno preflight. Se precisa distinguir pelo verbo cru no seu middleware, useReq.Method: stringouReq.RawWebRequest.Method.
Regras de pattern matching
- Case-sensitive no caminho:
/Userse/userssão rotas diferentes. - Sem normalização de barra final:
/userse/users/são rotas diferentes. Defina a convenção do projeto e mantenha. - Primeiro registro vence em padrões idênticos — registrar
/users/:idduas vezes é duplicata; a segunda emite erro em runtime nas versões recentes do Horse (Duplicate route detected: [GET] /users/:id). - Segmentos de parâmetro não aceitam múltiplos dois-pontos —
/users/:id:nameé inválido; use dois segmentos (/users/:id/:name).
Sub-recursos
Não há um mount embutido como no Express, mas você expressa sub-recursos com grupos:
procedure RegisterUsersRoutes(AGroup: THorseCoreGroup);
begin
AGroup
.Get ('', ListUsers)
.Post ('', CreateUser)
.Get ('/:id', GetUser);
end;
// No main:
RegisterUsersRoutes(THorse.Group.Prefix('/api/v1/users'));
O mesmo RegisterUsersRoutes pode ser montado sob múltiplos prefixos (/api/v1/users e /api/v2/users) sem duplicação — útil para versionamento de API quando v2 só adiciona endpoints.
Listar rotas (para debug)
O Horse não traz um helper tipo printRoutes(), mas você itera a árvore do router diretamente. Mais simples é imprimir cada rota ao registrar:
procedure RegisterAndLog(const Method, Path: string; Cb: THorseCallback);
begin
case Method of
'GET': THorse.Get (Path, Cb);
'POST': THorse.Post (Path, Cb);
// ...
end;
WriteLn(Method, ' ', Path);
end;
Para apps não-triviais, mantenha o registro de rotas centralizado em uma unit — o layout fica óbvio num único arquivo.
Roteador Radix (Opcional - Performance Extrema)
Para aplicações de grande escala ou APIs de alta performance com centenas de rotas, o Horse inclui opcionalmente um roteador baseado em Árvore de Prefixos (Radix Tree).
Ao contrário do roteador linear padrão que busca as rotas em (buscando uma a uma até casar), o roteador Radix analisa o caminho da URL em , onde é o comprimento da rota digitada. Isso resulta em throughput extremo e tempo de resposta constante, independentemente de quantas rotas existam na sua aplicação.
Como ativar o Radix Router
Chame o método estático THorse.UseRadixRouter logo no início da inicialização da sua aplicação (antes de registrar qualquer rota):
begin
THorse.UseRadixRouter;
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('pong');
end);
THorse.Listen(9000);
end.
Roteamento Avançado (Parâmetros Opcionais e Regex)
A partir da versão atual, o Horse suporta nativamente restrições de rotas baseadas em Expressões Regulares e parâmetros opcionais de caminho de URL em todos os roteadores (THorseRouterTree e THorseRadixRouter).
1. Parâmetros Opcionais
Ao adicionar um ponto de interrogação ? ao final de um parâmetro de caminho (ex: :id?), você sinaliza ao Horse que esse parâmetro pode estar ausente na URL requisitada:
THorse.Get('/users/:id?',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
if Req.Params.Items['id'].IsEmpty then
Res.Send('Retornando todos os usuarios')
else
Res.Send('Retornando o usuario ' + Req.Params.Items['id']);
end);
- Requisitar
GET /users/123-> Match! RetornaRetornando o usuario 123(Req.Params.Items['id'] = '123') - Requisitar
GET /users-> Match! RetornaRetornando todos os usuarios(Req.Params.Items['id'] = '')
2. Restrições com Expressões Regulares (Regex)
Você pode restringir a correspondência de um parâmetro passando um padrão Regex entre parênteses logo após o nome do parâmetro:
// Esta rota so sera acionada se o parametro 'id' contiver apenas numeros decimais
THorse.Get('/users/:id(\d+)',
procedure(Req: THorseRequest; Res: THorseResponse)
begin
Res.Send('Usuario numerico: ' + Req.Params.Items['id']);
end);
- Requisitar
GET /users/456-> Match! RetornaUsuario numerico: 456 - Requisitar
GET /users/john-> Não coincide com a Regex (continua a busca por outras rotas parametrizadas compatíveis ou retorna404 Not Found).
3. Coexistência e Ambiguidade de Rotas
O mecanismo de roteamento resolve a ambiguidade de rotas dando precedência da seguinte forma (da mais específica para a mais genérica):
- Rota Estática Exata: ex:
GET /users/new - Rota Paramétrica com Regex: ex:
GET /users/:id(\d+) - Rota Paramétrica Opcional/Texto Geral: ex:
GET /users/:id?
Veja também
- Request e Response — o que fazer dentro do callback.
- Middleware —
THorse.Use, oNextproc, ordem de registro. - Providers — como o transporte entrega a requisição ao seu callback.