O Fastify geralmente é apresentado por seus números de benchmark, mas os números são um efeito downstream de uma escolha arquitetural mais interessante: o Fastify é construído como uma árvore de contextos de plugin isolados, não uma única cadeia plana como o Express, e ele compila schemas antecipadamente em vez de interpretá-los por requisição.
Ambas as decisões foram tomadas especificamente para corrigir problemas que o modelo plano e de estado compartilhado do Express encontra em escala - acoplamento acidental entre recursos não relacionados e trabalho repetido por requisição que poderia ter sido feito uma vez.
Esta página é o modelo mental por trás dessas duas decisões: o que é um plugin, como o encapsulamento funciona e quando você o quebra deliberadamente, e por que o design orientado a schema é uma estratégia de desempenho, não apenas uma conveniência de validação.
Noções Básicas do Fastify cobre isso na prática com código funcional; Plugins do Fastify aprofunda em autoloading e convenções de diretório construídas sobre o modelo descrito aqui.
O Fastify estrutura uma aplicação como uma árvore de contextos de plugin aninhados - cada chamada register() cria um novo escopo filho isolado - e compila schemas de requisição/resposta em validadores e serializadores rápidos uma vez na inicialização.
Por Que Importa: O encapsulamento evita o acoplamento "tudo compartilha tudo" que torna grandes aplicações Express difíceis de raciocinar, e a compilação antecipada remove a sobrecarga de validação e serialização por requisição que uma abordagem interpretada paga a cada vez.
Conceitos Chave:plugin, contexto de encapsulamento, fastify-plugin, hook de ciclo de vida, compilação de schema, avvio.
Quando Usar Este Modelo: Estruturar uma aplicação como módulos de funcionalidade independentes, decidir onde um recurso compartilhado (banco de dados, autenticação) deve viver na árvore e entender por que rotas definidas por schema superam a validação ad-hoc.
Limitações / Trade-offs: O encapsulamento adiciona sobrecarga conceitual real para aplicações pequenas, e esquecer fastify-plugin quando você realmente queria escopo compartilhado é uma fonte comum de bugs do tipo "por que rotas irmãs não conseguem ver este decorador".
Tópicos Relacionados: Pipeline de middleware do Express, grafo de módulos do NestJS, validação JSON Schema, autoloading de plugins.
Um plugin do Fastify é apenas uma função - async (fastify, opts) => { ... } - registrada com fastify.register(), e é a unidade única a partir da qual o Fastify é construído: rotas, decoradores, hooks e até mesmo a aplicação raiz são, estruturalmente, plugins.
O que torna isso diferente de "apenas chamar uma função de configuração" é o encapsulamento: cada chamada register() cria um novo contexto filho que herda tudo do pai no momento da criação, mas cujas próprias adições - novos decoradores, novos hooks, novas rotas - permanecem invisíveis para plugins irmãos e para o pai.
app.register(async function userRoutes(fastify) { fastify.decorate("cache", new Map()); // visível apenas dentro de userRoutes fastify.get("/users", async () => []);}, { prefix: "/users" });// app.cache é undefined aqui - o decorador nunca saiu desse escopo filho
Imagine uma árvore, não um corredor: a aplicação raiz é o tronco, cada chamada register() gera um novo galho, e um galho pode ver tudo o que seu tronco pai já tinha, mas nada que um galho irmão cresceu independentemente.
O Fastify foi construído (2016) especificamente para abordar duas coisas que o modelo plano do Express não resolve bem por padrão: compartilhamento acidental entre recursos não relacionados e o custo de validar e serializar JSON da mesma maneira, do zero, a cada requisição.
A forma de árvore não é cosmética - é implementada como uma cadeia de protótipos real, onde cada contexto filho herda prototipicamente os decoradores e a configuração do pai no momento do registro, de modo que adições posteriores não apareçam retroativamente em contextos criados anteriormente.
O fastify-plugin (comumente importado como fp) existe precisamente para optar por sair disso: envolver um plugin com fp() diz ao Fastify para pular a criação de um novo contexto filho e, em vez disso, aplicar as adições desse plugin diretamente ao escopo pai - que é exatamente o que você quer para preocupações transversais como uma conexão de banco de dados ou um decorador de autenticação que todas as rotas precisam.
O bootstrapping dessa árvore de forma assíncrona - já que os plugins podem ser async e podem precisar aguardar uma conexão de banco de dados antes que o próximo plugin seja registrado - é tratado pelo avvio, a biblioteca que o Fastify usa internamente para garantir que os plugins terminem de registrar na ordem de dependência antes que o servidor comece a aceitar requisições, em vez de competir.
Dentro de uma única requisição, o Fastify substitui o modelo next() de cadeia única do Express por uma sequência fixa de hooks de ciclo de vida nomeados - onRequest, preParsing, preValidation, preHandler, preSerialization, onSend, onResponse - cada um um estágio distinto e nomeado em vez de uma posição indiferenciada em uma lista longa.
Essa granularidade é o que permite que a validação de schema se encaixe como seu próprio estágio bem definido (preValidation) em vez de ser apenas mais uma função de middleware competindo por posição na cadeia, como teria que ser adicionada ao Express.
A compilação de schema é a outra metade do modelo: a opção schema de uma rota é entregue a um compilador (AJV por padrão) uma vez, na inicialização, produzindo uma função de validação otimizada e um serializador JSON otimizado para essa forma específica - então o custo de "esta requisição corresponde ao schema" e "como transformar este objeto de resposta em texto JSON rapidamente" é pago uma vez por definição de rota, não uma vez por requisição.
A árvore de plugins escala de forma diferente da cadeia plana do Express: uma aplicação Fastify grande se decompõe naturalmente em um plugin por domínio de funcionalidade (usuários, pedidos, faturamento), cada um com suas próprias rotas, decoradores e hooks que simplesmente não podem vazar para um domínio irmão por acidente - a fronteira de encapsulamento faz o trabalho de isolamento que os desenvolvedores Express têm que impor por convenção.
Essa fronteira tem um custo real para aplicações pequenas, no entanto: um protótipo de cinco rotas obtém pouco benefício de uma estrutura de árvore e paga uma pequena taxa conceitual (lembrar quando usar fastify-plugin, entender por que um decorador "não está visível" em um arquivo irmão) que um aplicativo Express plano nunca precisa pensar.
O design orientado a schema se compõe além da velocidade bruta: como as rotas declaram sua forma de requisição/resposta como JSON Schema, o mesmo schema pode gerar documentação OpenAPI automaticamente (@fastify/swagger), dando ao Fastify uma história de documentação que o Express não tem equivalente sem adicionar uma camada de especificação separada - veja Validação JSON Schema.
A observabilidade se beneficia da mesma estrutura - hooks onResponse veem o status final e o tempo de cada requisição, independentemente de qual plugin a tratou, e o logger Pino integrado do Fastify (veja Logging com Pino) é conectado através do mesmo ciclo de vida em vez de ser adicionado como uma camada de middleware separada.
Abstração central do Framework
Força
Fraqueza
Melhor Encaixe
Fastify: árvore de plugins encapsulada
Escopos isolados previnem acoplamento acidental; despacho compilado por schema é rápido
Estrutura extra para aprender; fácil de usar fastify-plugin incorretamente
APIs de alta vazão ou orientadas a schema; equipes que desejam modularidade forçada sem um contêiner de DI
Express: cadeia plana de middleware
Mínimo, familiar, ecossistema enorme
Sem encapsulamento; validação tipicamente reexecutada por requisição
APIs CRUD pequenas a médias, equipes com forte dependência do ecossistema
NestJS: grafo de módulos DI
Arquitetura forçada, dependências injetadas no construtor
Abstração mais pesada; curva de aprendizado mais íngreme
Equipes grandes, serviços corporativos de longa duração
"Um plugin Fastify é basicamente o mesmo que middleware Express." Middleware é uma função em uma cadeia plana; um plugin é um contexto isolado inteiro que pode conter rotas, hooks, decoradores e plugins aninhados adicionais.
"Você deve envolver tudo em fastify-plugin." Fazer isso remove o encapsulamento em todos os lugares, o que anula o propósito - reserve-o para coisas que genuinamente precisam ser compartilhadas globalmente, como uma conexão de banco de dados ou um decorador de autenticação.
"A validação de schema torna o Fastify mais lento do que uma rota Express não validada." O oposto é tipicamente verdadeiro na prática - schemas compilam uma vez em funções otimizadas na inicialização, o que geralmente é mais rápido do que lógica de validação ad-hoc reexecutada do zero em cada requisição.
"A ordem de registro do plugin não importa porque o Fastify cuida disso." O avvio garante que os plugins terminem de registrar antes que o servidor inicie, mas o registro ainda ocorre na ordem em que você chama register() - um plugin que depende de um decorador de um irmão ainda precisa que esse irmão seja registrado primeiro, ou levantado com fastify-plugin.
"Hooks de ciclo de vida são apenas middleware com nomes diferentes." São estágios distintos e ordenados, ligados a pontos específicos no processamento da requisição (antes de parsear, antes de validar, antes de serializar) - não uma posição arbitrária em uma cadeia indiferenciada como é o middleware do Express.
Qual é a ideia arquitetural central do Fastify, em uma frase?
Uma árvore de contextos de plugin isolados, cada um criado por register(), combinada com definições de JSON Schema compiladas uma vez na inicialização em validadores e serializadores rápidos.
O que exatamente é um "plugin" do Fastify?
Qualquer função registrada com fastify.register() - rotas, decoradores, hooks e até mesmo a aplicação são estruturalmente plugins. É a unidade única a partir da qual o Fastify compõe tudo.
Como o encapsulamento funciona realmente nos bastidores?
Cada chamada register() cria um novo contexto filho que herda prototipicamente tudo o que o pai tinha naquele momento, mas quaisquer decoradores, hooks ou rotas que o filho adiciona permanecem invisíveis para os irmãos e para o pai - é implementado como uma cadeia de protótipos real, não apenas uma convenção.
Quando devo usar `fastify-plugin`?
Quando algo genuinamente precisa ser visível em todos os lugares - uma conexão de banco de dados, um decorador de autenticação compartilhado, configuração global. Ele deliberadamente pula a criação de um novo contexto filho e aplica adições ao escopo pai.
Por que o Fastify compila schemas em vez de validar sob demanda?
Como a forma da requisição e resposta de uma rota é conhecida com antecedência, o Fastify a entrega a um compilador (AJV por padrão) uma vez na inicialização, produzindo uma função otimizada - então o custo da validação e serialização é pago uma vez por definição de rota, não repetido em cada requisição de entrada.
O que é `avvio` e por que o Fastify precisa dele?
É a biblioteca interna de bootstrapping que gerencia o registro assíncrono de plugins, garantindo que os plugins terminem de carregar na ordem de dependência correta antes que o servidor comece a aceitar tráfego - importante porque os plugins podem await coisas como uma conexão de banco de dados antes que o próximo plugin seja executado.
Como os hooks de ciclo de vida do Fastify diferem do middleware Express?
Hooks são estágios nomeados e fixos (onRequest, preValidation, preHandler, onSend, etc.) ligados a pontos específicos no processamento da requisição, em vez de uma posição indiferenciada em uma cadeia plana - o que permite ao Fastify encaixar preocupações como validação de schema em um estágio bem definido em vez de uma função de middleware competindo por ordem na cadeia.
A árvore de plugins é um exagero para uma aplicação pequena?
Para um punhado de rotas, sim, em grande parte - os benefícios de isolamento aparecem à medida que uma base de código cresce além de alguns domínios de funcionalidade. Um pequeno protótipo paga uma pequena taxa conceitual por uma estrutura que ele pode ainda não precisar.
O encapsulamento alguma vez prejudica mais do que ajuda?
Pode prejudicar, quando uma equipe não o entende - "por que esta rota não pode ver aquele decorador" é uma confusão inicial comum até que o modelo de árvore/escopo se encaixe, momento em que se torna a coisa que impede o acoplamento acidental em vez disso.
Como isso se compara ao sistema de módulos do NestJS?
A árvore de plugins do Fastify é mais leve - sem contêiner de injeção de dependência, sem metadados orientados a decoradores - enquanto os módulos NestJS adicionam provedores, dependências resolvidas por DI e um ciclo de vida de requisição mais elaborado em cima de uma ideia semelhante de unidades delimitadas e composíveis.
Uma aplicação Fastify ainda pode gerar documentação de API a partir deste modelo?
Sim - como as rotas declaram sua forma de requisição/resposta como JSON Schema para o compilador, o mesmo schema pode impulsionar a geração automática de OpenAPI (@fastify/swagger), que é um benefício direto do design orientado a schema em vez de um recurso adicionado posteriormente.
A árvore de plugins afeta os testes?
Sim, favoravelmente - como um plugin é autocontido, ele pode ser registrado em uma instância Fastify() nua e testado com inject() isoladamente, sem precisar do restante da árvore de plugins da aplicação.