Delphi Minimal API: APIs REST simples e rápidas com DMVCFramework
🇬🇧 English • 🇮🇹 Italiano • 🇪🇸 Español • 🇩🇪 Deutsch
APIs REST e aplicações web simples e rápidas em Delphi: rotas com métodos anônimos, sem classe controller, com DMVCFramework, o framework Delphi open-source mais popular.
Uma Nova Forma de Escrever APIs em Delphi com DMVCFramework
O DMVCFramework nasceu para resolver problemas “grandes” e complexos: aplicações de servidor de longa duração, com muitos endpoints, regras de negócio elaboradas e equipes que trabalham nelas por anos. É justamente por isso que o modelo controller-action sempre foi o coração do framework. Ele é robusto, testável, escalável e impõe uma estrutura clara à medida que o projeto cresce: é essa solidez que tornou o DMVCFramework o projeto open source mais difundido para criar soluções web em Delphi, e continuará sendo.
Mas, nos últimos anos, observei uma nova abordagem se somar ao ecossistema: FastAPI em Python, Minimal API no ASP.NET Core, Hono em TypeScript. Todos oferecem uma alternativa mais direta para certos cenários: microsserviços, protótipos, APIs simples.
Eu queria trazer a mesma escolha para o DMVCFramework. E funcionou.
O projeto agora tem mais de 50 contribuidores: uma comunidade crescente que torna possível trabalhar em funcionalidades ambiciosas como esta.
E não é apenas uma questão de código: a Minimal API já está integrada ao wizard da IDE, com dois novos templates no Object Repository do Delphi (veremos em detalhes mais adiante).
Olá, Mundo
Antes de tudo: quão mínima é?
AEngine.Root
.MapGet('/hello',
function: IMVCResponse
begin
Result := Ok('Hello, World!');
end);
Três linhas. Sem classe, sem atributo, sem arquivo separado.
O que ela retorna? O método anônimo tem tipo de retorno IMVCResponse, o objeto que descreve toda a resposta
HTTP (status, cabeçalhos, corpo). Ok(...) é uma factory que cria um com status 200 OK, e o corpo é
sempre um objeto JSON: ao passar uma string, ela é encapsulada no campo message. A resposta que
chega ao cliente é, portanto:
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
{"message":"Hello, World!"}
Já passando para Ok(...) um objeto ou uma lista, estes vão para o campo data do mesmo objeto JSON
({"data": ...}): é o mesmo mecanismo do modelo de controllers, aqui exposto por uma função em vez de
um método.
Como fica em um projeto real
Antes de ver o código, uma palavra sobre os filtros. Existem dois tipos: os EndpointFilter, que
se vinculam a um grupo de rotas específico via .Use(...), e os HTTPFilter, registrados no nível do
engine, que se aplicam a toda requisição independentemente do roteamento, trabalhando diretamente sobre o
transporte HTTP (Ctx.Request/Ctx.Response). Ambos são o equivalente conceitual do middleware clássico
do DMVCFramework, e vamos abordá-los em detalhes mais adiante. No exemplo abaixo usamos os primeiros: toda
rota do grupo passa por eles em ordem antes de chegar ao handler. Por ora, saiba apenas que .Use()
significa “aplicar este comportamento a tudo que vier a seguir”.
Aqui está uma API REST completa (autenticação JWT, logging, endpoints CRUD) em um único arquivo, sem nenhuma classe controller:
procedure RegisterRoutes(AEngine: TMVCEngine);
begin
// Health check público, sem filtros
AEngine.Root
.MapGet('/health',
function: IMVCResponse
begin
Result := Ok('OK');
end);
// Grupo /api/v1: autenticação e logging aplicados a todas as rotas
AEngine.Prefix('/api/v1')
.Use(RequestLoggingFilter())
.Use(BearerAuthFilter())
// Por convenção, toda interface (como IPeopleService) é resolvida pelo container de DI
.MapGet<IPeopleService>('/people',
function(Svc: IPeopleService): IMVCResponse
begin
Result := Ok(Svc.GetAll);
end)
.MapGet<string, IPeopleService>('/people/($id)',
function(ID: string; Svc: IPeopleService): IMVCResponse
begin
Result := Ok(Svc.GetByID(ID));
end)
.MapPost<TPerson, IPeopleService>('/people',
function(Person: TPerson; Svc: IPeopleService): IMVCResponse
begin
Svc.Create(Person);
Result := Created('/api/v1/people/' + Person.ID.ToString, Person);
end);
end;
É isso. Sem [MVCPath], sem [MVCHTTPMethod], sem classe controller para declarar.
Aplicações grandes: a estrutura não desaparece, você a escolhe
A pergunta justa é: “e quando eu tiver 80 rotas?”
Nenhum framework de roteamento mínimo escala bem se tudo for colocado em um único RegisterRoutes.
A diferença é que, com a Minimal API do DMVC, a estrutura não é imposta pela sintaxe,
ela é imposta por você, da forma que fizer mais sentido para o seu domínio.
O padrão natural é dividir as rotas por área funcional, cada uma em seu próprio procedimento (ou unit), passando o grupo como parâmetro:
// PeopleRoutesU.pas
procedure RegisterPeopleRoutes(AGroup: TMVCRouteGroup<TObject>);
begin
AGroup
.MapGet<IPeopleService>('/people', ...)
.MapGet<string, IPeopleService>('/people/($id)', ...)
.MapPost<TPerson, IPeopleService>('/people', ...)
.MapPut<string, TPerson, IPeopleService>('/people/($id)', ...)
.MapDelete<string, IPeopleService>('/people/($id)', ...);
end;
// OrdersRoutesU.pas
procedure RegisterOrdersRoutes(AGroup: TMVCRouteGroup<TObject>);
begin
AGroup
.MapGet<IOrderService>('/orders', ...)
.MapPost<TOrder, IOrderService>('/orders', ...);
end;
// RoutesU.pas: ponto único de montagem
procedure RegisterRoutes(AEngine: TMVCEngine);
var
lAPI: TMVCRouteGroup<TObject>;
begin
lAPI := AEngine.Prefix('/api/v1')
.Use(RequestLoggingFilter())
.Use(BearerAuthFilter());
RegisterPeopleRoutes(lAPI);
RegisterOrdersRoutes(lAPI);
end;
O resultado é uma estrutura em árvore explícita: filtros compartilhados ficam em um lugar, cada área funcional vive no seu próprio arquivo, e o ponto de montagem é uma lista de chamadas legível em dez segundos.
Com o modelo clássico de controllers, a estrutura existe mas é imposta pela sintaxe: cada recurso requer uma classe dedicada. Com a Minimal API ela é opcional: você pode começar com tudo em um arquivo e refatorar quando fizer sentido, sem reescrever nada.
Como funciona o binding de argumentos
Cada parâmetro do método anônimo é resolvido por tipo, automaticamente:
| Tipo do parâmetro | Origem |
|---|---|
TWebContext |
Contexto HTTP da requisição atual |
| Interface no container de DI | Serviço injetado |
| Classe | Corpo JSON (POST/PUT/PATCH) ou query string (GET/DELETE) |
Record com atributos [MVCFromBody] / [MVCFromQueryString] / [MVCFromContentField] |
Binding misto, campo a campo |
string, Integer, TGUID, TDateTime, … |
Parâmetro de rota se presente, caso contrário query string |
Não é necessário declarar a origem de cada parâmetro: o framework a infere a partir do tipo e do contexto.
Filtros: dois níveis, um pipeline
Uma das coisas de que mais me orgulho nesta implementação é o modelo de filtros em dois níveis.
EndpointFilter: por grupo, disparado apenas quando uma rota corresponde:
AEngine.Prefix('/admin')
.Use(
function(Ctx: TWebContext; Next: TMVCEndpointFilterNext): IMVCResponse
begin
if not IsAdmin(Ctx) then
Result := Status(403, 'Forbidden')
else
Result := Next(); // continua para o próximo filtro ou para o handler
end);
HTTPFilter: é registrado no nível do engine e envolve todo o tratamento de cada requisição, antes
mesmo de o roteamento acontecer, trabalhando diretamente sobre o transporte HTTP
(Ctx.Request/Ctx.Response). Ideal para rate limiting, arquivos estáticos, cabeçalhos de segurança,
compressão:
AEngine.UseHTTPFilter(
procedure(Ctx: TWebContext; Next: TMVCHTTPFilterNext)
begin
Ctx.Response.SetCustomHeader('X-Frame-Options', 'DENY');
Next(); // deixa o pipeline continuar
end);
Ambos os tipos se compõem na ordem de registro. A ordem é previsível, explícita e legível em um único lugar.
Não apenas APIs: também aplicações web
A Minimal API não é exclusiva para JSON. Com .AsWeb, um grupo se torna uma coleção de
rotas renderizadas no servidor que retornam HTML via TemplatePro. RenderView e ViewData
funcionam como esperado, e a sessão é apenas um filtro:
AEngine.Root.AsWeb
.Use(MemorySession(10))
.MapGet('/',
function: IMVCResponse
begin
ViewData['title'] := 'Home';
Result := RenderView('pages/home');
end);
Rotas do tipo rkWeb são automaticamente excluídas da especificação OpenAPI: o framework
sabe distinguir endpoints de API de páginas HTML.
Suporte no Wizard do IDE
O wizard não ficou de fora. Você encontrará dois novos presets no Object Repository do Delphi:
- Minimal API RESTful: API JSON pura, filtros por grupo, container de DI
- Minimal API Web App: rotas
.AsWeb, sessões, login/logout, pronto para HTMX
Um projeto funcional, zero configuração, pronto em 30 segundos.
Status
A funcionalidade está disponível no branch master a partir da versão 3.5.0-silicon-rc3.
Todos os 872 testes de integração passam tanto em Win32 quanto em Win64, em três backends de servidor distintos
(WebBroker, Indy Direct, HTTP.sys): 5.232 execuções totais entre todas as combinações de plataforma e servidor.
Em breve:
- Benchmark API Clássica vs Minimal API (spoiler: os resultados me surpreenderam)
- Integração com OpenAPI 3.x
- Um guia dedicado com padrões do mundo real, filtros avançados e integração com o container de DI
Perguntas frequentes
O que é a Minimal API no DMVCFramework?
A Minimal API é uma forma de criar APIs REST e aplicações web em Delphi definindo rotas como métodos anônimos (function: IMVCResponse) em vez de classes controller. Faz parte do DMVCFramework 3.5 e funciona como o FastAPI em Python ou a Minimal API no ASP.NET Core. As rotas são registradas com MapGet, MapPost, MapPut e MapDelete.
Preciso de uma classe controller para criar uma API REST em Delphi?
Não. Com a Minimal API do DMVCFramework você registra rotas diretamente com métodos anônimos, sem atributos [MVCPath] ou [MVCHTTPMethod] e sem classe controller. O modelo clássico de controllers continua disponível: a Minimal API é uma abordagem adicional e opcional, ideal para microsserviços, protótipos e APIs simples.
Como os serviços e os parâmetros de rota são vinculados na Minimal API do DMVCFramework?
Os parâmetros são resolvidos por tipo: interfaces a partir do container de injeção de dependência, classes a partir do corpo JSON ou da query string, e tipos escalares (string, Integer, TGUID, TDateTime) a partir do parâmetro de rota se presente, caso contrário da query string. Os nomes dos parâmetros de rota são comparados sem diferenciar maiúsculas, pois Delphi é uma linguagem case-insensitive.
A Minimal API do DMVCFramework pode renderizar aplicações web HTML, não apenas JSON?
Sim. Ao chamar .AsWeb em um grupo, ele se torna uma coleção de rotas renderizadas no servidor que retornam HTML via TemplatePro. RenderView e ViewData funcionam como esperado e a sessão é apenas um filtro. Rotas do tipo rkWeb são automaticamente excluídas da especificação OpenAPI.
Qual é a diferença entre um EndpointFilter e um HTTPFilter?
Um EndpointFilter é vinculado a um grupo de rotas específico com .Use(...) e é disparado apenas quando uma rota corresponde. Um HTTPFilter é registrado no nível do engine e envolve cada requisição antes do roteamento, trabalhando diretamente sobre o transporte HTTP (Ctx.Request/Ctx.Response). Ambos são o equivalente conceitual do middleware clássico do DMVCFramework.
Qual versão do Delphi e do DMVCFramework é necessária para a Minimal API? A Minimal API está disponível no branch master do DMVCFramework a partir da versão 3.5.0-silicon-rc3. Todos os 872 testes de integração passam tanto em Win32 quanto em Win64 contra três backends de servidor: WebBroker, Indy Direct e HTTP.sys.
🎁 O guia completo de Minimal API, de graça
Aquele «guia dedicado com padrões do mundo real, filtros avançados e integração com o container de DI» mencionado logo acima? Os apoiadores AllAccess+ ganham de graça, no dia do lançamento, sem nenhum custo adicional.
E não para por aí. Sua assinatura libera toda a biblioteca de conteúdo premium sobre Delphi: TemplatePro, DotEnv, Job Queues com FirebirdSQL, Prompt Engineering, MVCActiveRecord e muito mais material inédito e normalmente pago, tudo incluído, além de suporte prioritário e acesso antecipado a novos recursos.
Torne-se apoiador AllAccess+ →Daniele
Comments
comments powered by Disqus