Spraxium logoSpraxium

Pipeline de Roteamento e Decorators

Entenda como metadados de rota se tornam handlers executáveis em @spraxium/http. Acompanhe o fluxo dos decorators até a resolução de parâmetros, validação e modelagem de resposta.

Superfície de decorators

Decorators definem a intenção de transporte no carregamento da classe, enquanto serviços de runtime transformam essa intenção em handlers Hono executáveis durante a inicialização do servidor. Manter essa separação em mente ajuda a depurar o comportamento das rotas com rapidez, porque o decorator é sempre a fonte de verdade e o runtime apenas lê o que ele armazenou em reflect-metadata.

Todos os decorators do pacote gravam em chaves definidas em HTTP_METADATA_KEYS. @HttpController define o prefixo, decorators de método de rota adicionam entradas a um array ROUTES, @HttpUseMiddleware adiciona ao array MIDDLEWARE, @HttpGuards adiciona ao array GUARDS e decorators de parâmetro adicionam dados a um array PARAMS indexado pelo nome do handler. Esses metadados são imutáveis depois da inicialização da classe, então o runtime pode lê-los com segurança na inicialização, sem efeitos colaterais.

Resolução de parâmetros em profundidade

ParamResolver é o serviço que lê os metadados PARAMS e extrai valores do Context do Hono em cada invocação de handler. Ele suporta cinco tipos de origem, cada um mapeado para uma estratégia específica de extração.

Quando um handler não tem nenhum decorator de parâmetro, ParamResolver faz fallback para injetar o Context completo como único argumento. Esse comportamento padrão significa que você pode escrever handlers simples aceitando ctx diretamente, sem decorators, o que é útil em cenários de migração ou em handlers que precisam de controle total. A partir do momento em que aparece um decorator de parâmetro, apenas os parâmetros decorados são resolvidos.

A origem do body merece atenção especial. ParamResolver faz o parse do JSON do body apenas uma vez por invocação de handler e guarda o resultado em cache, mesmo que existam vários decorators @HttpBody no mesmo método. Quando uma classe DTO é fornecida, ValidationPipe executa plainToInstance de class-transformer para criar uma instância tipada e depois validate de class-validator com whitelist: true e forbidNonWhitelisted: true. Isso significa que propriedades inesperadas no payload são rejeitadas automaticamente antes que a regra de negócio rode.

Exemplo focado com todas as origens de parâmetro

import {
  HttpBody,
  HttpController,
  HttpCtx,
  HttpGet,
  HttpHeader,
  HttpParam,
  HttpPatch,
  HttpQuery,
  HttpStatus,
  NotFoundError,
} from '@spraxium/http';
import type { Context } from 'hono';
import { GuildHttpService } from '../services/guild-http.service';
import { UpdateGuildConfigDto } from '../dto/update-guild-config.dto';

@HttpController('/guilds')
export class GuildHttpController {
  constructor(private readonly service: GuildHttpService) {}

  @HttpGet('/:guildId')
  async getGuild(
    @HttpParam('guildId') guildId: string,
    @HttpQuery('includeMembers') includeMembers?: string,
    @HttpHeader('x-request-id') requestId?: string,
  ) {
    const guild = await this.service.getGuildSummary(guildId);
    if (!guild) throw new NotFoundError('Guild not found');
    return {
      ok: true,
      ...guild,
      includeMembers: includeMembers === 'true',
      requestId,
    };
  }

  @HttpPatch('/:guildId/config')
  @HttpStatus(200)
  async updateGuildConfig(
    @HttpParam('guildId') guildId: string,
    @HttpBody(UpdateGuildConfigDto) body: UpdateGuildConfigDto,
  ) {
    return this.service.updateConfig(guildId, body);
  }

  @HttpGet('/:guildId/raw')
  async getRawContext(@HttpCtx() ctx: Context) {
    const guildId = ctx.req.param('guildId');
    ctx.header('X-Custom-Header', 'spraxium');
    return ctx.json({ ok: true, guildId }, 200);
  }
}

Pipeline de validação de DTO

Quando @HttpBody(DtoClass) é usado, o body JSON bruto passa por ValidationPipe antes de chegar ao handler. O pipeline executa duas transformações em sequência e rejeita a requisição se a validação falhar.

Primeiro, plainToInstance(DtoClass, body) de class-transformer converte o objeto anônimo em uma instância tipada da classe. Essa etapa permite que class-validator leia os metadados de decorator da classe DTO. Depois, validate(instance, { whitelist: true, forbidNonWhitelisted: true }) executa todos os decorators de constraint na instância. A opção whitelist remove propriedades desconhecidas, e forbidNonWhitelisted rejeita ativamente requisições que as incluam.

Quando existem erros de validação, ValidationPipe lança um ValidationError com status 400. Cada entrada de erro no array details mapeia um nome de property para um registro de constraints. O handler centralizado onError intercepta isso e retorna { ok: false, error: 'Validation failed', details: [...] }. Esse fluxo automatizado significa que handlers nunca precisam validar payloads de body manualmente, e todo endpoint de mutação ganha formatos de erro consistentes sem esforço extra.

Sequência interna de execução

Resolver o grafo de services com ordenação topológica

RouteRegistry lê os tipos dos parâmetros do construtor em reflect-metadata e monta um grafo de dependências. Services são instanciados em ordem para que cada classe receba suas dependências já resolvidas. Se uma dependência circular for detectada, a inicialização falha com um erro listando as classes envolvidas.

Instanciar controllers com DI

Classes de controller são instanciadas usando o mapa de dependências já resolvido, mais eventuais valores de fallback vindos do container principal. RouteRegistry também lê nesta etapa os metadados de @HttpUseMiddleware e @HttpGuards em nível de classe, para que eles estejam disponíveis durante o binding das rotas.

Fazer o binding dos handlers de rota no Hono

RouteBuilder percorre as definições de rota de cada controller. Para cada rota, calcula o caminho completo juntando o prefixo do controller com o subcaminho do método. Ele mescla referências de middleware em nível de classe e de método, resolve essas referências por middlewareProviders e registra a cadeia completa como handler de rota do Hono. O binding acontece uma vez na inicialização, o que torna os contratos de rota imutáveis durante o runtime.

Resolver parâmetros de método a cada requisição

Quando uma requisição corresponde a uma rota, ParamResolver lê os metadados PARAMS daquele handler. Ele extrai valores de params da URL, query strings, body já parseado, headers ou do contexto completo. Se um decorator @HttpBody referenciar uma classe DTO, ValidationPipe é acionado. O array final de argumentos é passado para a função do handler via apply().

Executar o handler e modelar a resposta

O handler roda com os argumentos resolvidos. Se ele retornar um Response nativo, Hono o repassa sem mudanças. Caso contrário, RouteBuilder serializa o valor retornado com ctx.json() usando o status de @HttpStatus ou 200 por padrão. Qualquer HttpError lançado durante a execução se propaga até o handler centralizado onError.

Regras de modelagem de resposta

Handlers podem retornar dois tipos de valor: objetos simples ou instâncias nativas de Response. Entender a diferença evita confusão com status codes e content types.

Quando um handler retorna um objeto simples, RouteBuilder o envolve com ctx.json(result, statusCode), em que o status code vem de @HttpStatus ou assume 200 por padrão. O content type é definido automaticamente como application/json. Esse é o padrão recomendado para a maioria dos endpoints, porque mantém os handlers focados em dados e deixa a serialização por conta do framework.

Quando um handler retorna um Response nativo, construído com ctx.json(), ctx.text() ou new Response(...), Hono o repassa sem modificação. Isso dá controle total sobre headers, status e formato do body, o que é útil para respostas em streaming, downloads binários ou redirects. Em troca, o status code centralizado de @HttpStatus é ignorado, e a resposta contorna o caminho normal de serialização JSON.

Trate caminhos de rota como contratos públicos

Publique versionamento intencional nos caminhos quando o comportamento dos endpoints mudar. O runtime não detecta colisões de caminho automaticamente, então renomeações silenciosas quebram clientes. Mantenha prefixos de rota estáveis e explícitos entre implantações.