Fastify com Node.js é um tema importante para quem desenvolve serviços modernos com JavaScript no servidor. Não basta fazer uma demonstração funcionar no computador local: uma solução profissional precisa ter contratos claros, tratamento de falhas, segurança, testes, observabilidade e um processo de implantação previsível.
Neste guia, você aprenderá a aplicar Fastify com Node.js de forma prática, entendendo a arquitetura, os principais componentes, os riscos mais comuns e as decisões que tornam o projeto mais fácil de manter. O objetivo é sair de um exemplo isolado e chegar a uma base que possa evoluir sem depender de improvisos.
O que é Fastify com Node.js?
Fastify é um framework web para Node.js orientado a baixo overhead, plugins encapsulados e validação baseada em JSON Schema. Ele oferece roteamento, serialização, hooks, logging e uma arquitetura de plugins que facilita compor aplicações sem transformar tudo em um arquivo central.
Em aplicações Node.js, essa abordagem se encaixa bem em sistemas orientados a eventos e operações de entrada e saída. Para revisar os fundamentos da plataforma, leia o que é Node.js. Se você ainda está montando seu primeiro backend, o tutorial sobre como criar uma API com Node.js oferece uma visão complementar.
Quando vale a pena usar?
Ele é indicado para APIs REST, backends de aplicações, gateways internos e serviços que precisam de boa produtividade sem abrir mão de controle. Também é uma boa escolha quando a equipe quer schemas para validar entradas e serializar respostas. Para páginas simples ou funções muito pequenas, o módulo HTTP nativo pode ser suficiente.
A decisão deve considerar o problema real, a experiência da equipe, a infraestrutura disponível e o custo de operação. Uma tecnologia pode ser excelente e ainda assim ser inadequada quando aumenta a complexidade sem gerar benefício mensurável. Comece pequeno, defina critérios de sucesso e valide o comportamento sob carga e falhas.
Preparando o projeto
Crie uma pasta dedicada, inicialize o package.json e fixe uma versão suportada do Node.js no arquivo de configuração do projeto. Use variáveis de ambiente para endereços, credenciais e opções de execução, mas valide tudo no início do processo para evitar falhas tardias.
npm init -y
npm install fastify
npm install --save-dev node:testSepare dependências de produção das ferramentas de desenvolvimento. Ative lint, formatação e testes no pipeline de integração contínua. O artigo sobre deploy com GitHub Actions mostra como automatizar verificações antes que uma alteração chegue ao servidor.
Exemplo inicial
O exemplo abaixo cria uma instância, declara um schema para parâmetros e registra uma rota. O handler recebe apenas dados já validados pelo framework.
import Fastify from 'fastify';
const app = Fastify({ logger: true });
app.get('/users/:id', {
schema: {
params: {
type: 'object',
required: ['id'],
properties: {
id: { type: 'string', minLength: 1 }
}
}
}
}, async request => {
return {
id: request.params.id,
active: true
};
});
await app.listen({ port: 3000, host: '0.0.0.0' });A opção logger habilita logs estruturados. O schema impede IDs vazios e também pode definir o formato da resposta. Em produção, mova a rota para um plugin, injete o repositório e converta erros conhecidos em respostas estáveis.
Estrutura recomendada
Organize o Fastify em plugins por domínio. Um plugin pode registrar rotas, decorators, hooks e dependências, mantendo encapsulamento. Crie um plugin para banco de dados, outro para autenticação e plugins de rota por contexto de negócio. Use prefixos para versionar a API sem duplicar a aplicação inteira.
- config: leitura e validação de ambiente;
- domain: regras de negócio independentes do transporte;
- application: casos de uso e orquestração;
- infrastructure: banco, filas, rede e integrações;
- interfaces: HTTP, comandos, eventos ou tarefas agendadas;
- tests: unidades, integração e cenários de contrato.
Essa separação reduz acoplamento e permite substituir bibliotecas sem reescrever regras de negócio. Evite criar camadas vazias apenas para seguir um padrão: cada módulo deve proteger uma responsabilidade concreta.
Contratos e validação
Dados externos são sempre não confiáveis. Valide corpo, parâmetros, headers, eventos e respostas de serviços terceiros. Defina tamanho máximo, formatos permitidos e mensagens de erro consistentes. Schemas executáveis aproximam documentação e comportamento real; veja também o guia de validação com Zod no TypeScript.
Defina schemas de entrada e de resposta. A serialização de resposta reduz dados acidentais e melhora desempenho. Não confie apenas em tipos TypeScript, pois eles desaparecem em tempo de execução. Para regras de negócio, faça validações adicionais no caso de uso.
Tratamento de erros
Classifique falhas em categorias: entrada inválida, autenticação, autorização, recurso ausente, conflito, indisponibilidade temporária e erro interno. A resposta pública deve ser estável e não pode expor stack trace, consulta SQL, segredo ou detalhes da infraestrutura.
Use setErrorHandler para mapear erros de validação e exceções de domínio. Mantenha um código de erro interno estável e gere um requestId em todas as respostas. Erros inesperados devem ser registrados com contexto, mas a mensagem pública precisa ser genérica.
app.setErrorHandler((error, request, reply) => {
request.log.error({ err: error }, 'request_failed');
if (error.validation) {
return reply.code(400).send({
code: 'INVALID_REQUEST',
requestId: request.id
});
}
return reply.code(500).send({
code: 'INTERNAL_ERROR',
requestId: request.id
});
});Segurança
A segurança deve fazer parte da arquitetura desde o início. Aplique privilégio mínimo, valide todas as fronteiras e trate credenciais como dados sensíveis. Para Fastify com Node.js, priorize:
- limite o tamanho do body e rejeite content-types inesperados;
- configure CORS por origem e não use curingas com credenciais;
- autentique em hooks e autorize no nível do recurso;
- serialize respostas para evitar exposição de campos sensíveis;
- use HTTPS no proxy ou diretamente no processo;
- aplique rate limiting em rotas caras e de autenticação.
Registre eventos de segurança sem armazenar tokens completos, senhas ou dados pessoais desnecessários. Revise dependências e mantenha um processo claro para corrigir vulnerabilidades.
Desempenho e capacidade
Otimização começa com medição. Defina indicadores como latência, taxa de erro, uso de memória, CPU, tamanho de filas e tempo de dependências. O guia sobre como otimizar APIs RESTful em Node.js detalha princípios que também se aplicam aqui.
- use schemas de resposta para serialização eficiente;
- evite operações de CPU longa no event loop;
- configure keep-alive e timeouts coerentes com o proxy;
- reutilize pools de banco e clientes HTTP;
- não registre payloads grandes em todas as requisições;
- meça p95 e p99, não apenas médias.
Teste com carga representativa e inclua cenários de falha. Uma solução que funciona apenas com dependências saudáveis não está pronta para produção.
Observabilidade
O logger integrado usa Pino e inclui dados da requisição. Acrescente campos de negócio com parcimônia, propague requestId para dependências e registre o resultado do caso de uso. Exponha métricas por rota, status e duração sem criar cardinalidade ilimitada.
Use logs estruturados em JSON e inclua um identificador de correlação. Métricas devem mostrar volume, sucesso, falhas e duração. Traces distribuídos ajudam a encontrar gargalos quando uma operação atravessa vários serviços, mas precisam de amostragem para controlar custo.
Testes automatizados
Use app.inject para testar rotas sem abrir uma porta de rede. Construa a aplicação em uma função, injete dependências falsas e feche a instância após cada suíte. Testes de contrato devem confirmar status, headers e formato da resposta.
import test from 'node:test';
import assert from 'node:assert/strict';
import { buildApp } from './app.js';
test('GET /users/:id', async () => {
const app = buildApp();
const response = await app.inject({
method: 'GET',
url: '/users/123'
});
assert.equal(response.statusCode, 200);
assert.deepEqual(response.json(), {
id: '123',
active: true
});
await app.close();
});Evite testes dependentes de ordem, relógio real ou serviços externos instáveis. Injete relógio, geradores de identificadores e clientes de infraestrutura. Para fundamentos, consulte testes unitários com Jest.
Implantação e operação
Execute uma instância por contêiner e permita que o orquestrador faça escala horizontal. Defina trustProxy somente quando conhece a cadeia de proxies. Ajuste requestTimeout, keepAliveTimeout e connectionTimeout conforme o ambiente, e nunca deixe valores infinitos sem justificativa.
Implemente graceful shutdown: ao receber um sinal de encerramento, pare de aceitar trabalho novo, conclua o que estiver em andamento dentro de um prazo e feche conexões. Configure health checks que diferenciem processo vivo de serviço pronto para receber tráfego.
Erros comuns
- Registrar tudo no arquivo principal: isso elimina encapsulamento e dificulta testes.
- Confiar apenas no TypeScript: dados de rede ainda precisam de validação em runtime.
- Ignorar schemas de resposta: campos internos podem vazar e a serialização perde eficiência.
- Criar plugins com dependências ocultas: declare decorators e ordem de registro claramente.
- Usar logs sem requestId: investigar uma falha distribuída fica muito mais difícil.
Checklist antes de publicar
- rotas agrupadas em plugins;
- schemas de entrada e resposta;
- error handler central;
- autenticação e autorização testadas;
- limites de body e timeouts definidos;
- logs e métricas com correlação;
- testes com app.inject;
- shutdown e health checks validados.
Referências oficiais
Conteúdos relacionados
- O que é Node.js
- como criar uma API com Node.js
- otimizar APIs RESTful em Node.js
- testes unitários com Jest
Conclusão
Fastify oferece uma base rápida e organizada para APIs Node.js. Seu maior benefício não é apenas benchmark: a combinação de plugins, hooks, schemas, serialização e testes por injeção favorece uma arquitetura modular. O resultado depende de usar esses recursos com limites, contratos e operação bem definidos.
O caminho mais seguro é implementar uma versão pequena, observável e testável, medir seu comportamento e evoluir com base em dados. Quando contratos, limites e falhas são tratados explicitamente, Fastify com Node.js deixa de ser apenas uma funcionalidade e se torna uma parte confiável da plataforma.




