O Express tem uma ideia arquitetônica real, e quase tudo mais sobre o framework deriva dela: uma requisição e uma resposta viajam através de uma cadeia ordenada de pequenas funções, cada uma das quais pode inspecioná-las, modificá-las ou encerrar a troca.
Esse é o modelo inteiro.
Não há árvore de plugins, nem container de injeção de dependência, nem compilador de schema - apenas um pipeline, e uma aposta deliberada de que a maioria dos problemas do lado do servidor podem ser decompostos em passos pequenos e composíveis aplicados a estado compartilhado.
Esta página é o modelo mental por trás dessa aposta: de onde veio o pipeline, como o despacho realmente funciona e o que o Express intencionalmente deixou de fora para que outros frameworks - e as outras páginas desta seção - tivessem que preencher.
Noções Básicas do Express aborda a construção de rotas e middlewares na prática; Ordem do Middleware cobre as regras práticas de sequenciamento que derivam diretamente do modelo descrito aqui.
O Express despacha cada requisição através de uma cadeia ordenada de funções (req, res, next), todas operando nos mesmos objetos de requisição e resposta compartilhados.
Por que Importa: Quase todo bug específico do Express - um corpo ausente, uma resposta dupla, um manipulador de erros que nunca dispara - é realmente um mal-entendido dessa única cadeia, não uma peculiaridade do framework.
Conceitos Chave:middleware, a cadeia de despacho, next(), Router, extensão de protótipo.
Quando Usar Este Modelo: Para raciocinar sobre por que a ordem do middleware importa, decidir onde a lógica de preocupação cruzada pertence e entender o que um Router faz e não isola.
Limitações / Trade-offs: O design de cadeia linear e estado compartilhado não tem encapsulamento embutido e um teto de performance real sob taxas de requisição muito altas - ambos resultados diretos de priorizar a simplicidade sobre a estrutura.
Tópicos Relacionados: o modelo bruto de requisição/resposta HTTP, o encapsulamento de plugins do Fastify, o pipeline orientado a DI do NestJS, middleware de tratamento de erros.
O Express traça sua origem diretamente do Connect, uma biblioteca de middleware Node anterior construída sobre a mesma ideia: uma lista de funções, cada uma com a chance de agir sobre uma requisição antes de passar o controle adiante.
O Express manteve esse núcleo e adicionou roteamento (app.get, app.post, parâmetros de caminho) sobre ele, mas o modelo de execução subjacente - uma cadeia, não uma árvore ou um grafo - nunca mudou.
Um app Express é, em sua essência, uma função que você entrega a http.createServer() como o listener de requisição; veja O Modelo de Servidor HTTP do Node.js para o que esse listener realmente recebe do Node.
Quando uma requisição chega, o Express pega o par bruto IncomingMessage/ServerResponse e os estende com métodos e propriedades adicionais (req.params, res.json(), etc.) anexando o próprio protótipo do Express - os objetos que seus handlers veem são os mesmos objetos Node, apenas com mais capacidade adicionada.
Middleware é simplesmente qualquer função que corresponda à forma (req, res, next) => void - ela pode ler ou mutar o req/res compartilhado, e decide se a cadeia continua chamando next() ou termina enviando uma resposta.
function requestId(req: Request, _res: Response, next: NextFunction) { req.headers["x-request-id"] ??= crypto.randomUUID(); next(); // passa o controle para a próxima função na cadeia}
Uma analogia útil: imagine uma única linha de montagem, não um chão de fábrica ramificado - cada estação (middleware) ou estampa algo na peça que passa, ou a retira da linha completamente e a envia (envia a resposta).
Internamente, app.use() e app.get()/app.post() não constroem uma árvore de roteamento - eles anexam camadas a uma lista ordenada, cada camada envolvendo um matcher de caminho/método mais a função handler.
O despacho percorre essa lista de cima para baixo: para cada requisição recebida, o Express verifica se o caminho e o método da próxima camada correspondem, a executa se sim, e espera que essa camada chame next() antes de passar para a seguinte.
Essa é exatamente a forma de cadeia linear mostrada conceitualmente em Padrão de Middleware - o dispatcher real do Express é uma versão mais otimizada da mesma ideia, não um algoritmo diferente.
Um Router é uma subcadeia montável, não um escopo isolado: app.use('/users', usersRouter) insere a lista interna de camadas do router nesse prefixo de caminho, mas qualquer extensão de protótipo, efeito colateral de middleware global ou mutação de objeto compartilhado ainda se aplica em todos os lugares - não há limite de encapsulamento como existe no modelo de plugins do Fastify.
next(err) é o único mecanismo de ramificação da cadeia: chamar next() com um argumento pula todas as camadas normais restantes e salta diretamente para o middleware de tratamento de erros de quatro argumentos mais próximo, que é por que os manipuladores de erro devem ser registrados por último - eles são o destino de fallback para esse salto, não um hook especial que o Express chama automaticamente.
O Express 5 mudou uma peça importante dessa mecânica: Promises rejeitadas retornadas de um handler async agora são automaticamente encaminhadas para o mesmo caminho next(err), onde o Express 4 as engolia silenciosamente, a menos que você adicionasse express-async-errors ou envolvesse cada handler manualmente.
O maior ponto forte do modelo de pipeline também é sua maior restrição: como todo middleware compartilha o mesmo req/res e executa estritamente na ordem de registro, raciocinar sobre um grande app Express é realmente raciocinar sobre uma lista longa e plana - não há limite de módulo forçando você a pensar em pedaços menores da maneira que a árvore de plugins do Fastify ou o grafo de módulos do NestJS fazem.
Essa linearidade escala bem para a maioria das APIs CRUD, mas tem um teto de performance real: cada camada em uma cadeia longa adiciona uma chamada de função e uma verificação de correspondência a cada requisição, mesmo aquelas que um determinado middleware não se importa, o que é parte do motivo pelo qual a abordagem de router compilado do Fastify supera o Express sob taxas de requisição muito altas - veja Fastify vs Express ADR para o trade-off completo.
A segurança neste modelo é inteiramente uma questão do que está na cadeia e em que ordem: o Express não envia tratamento CORS, limitação de taxa ou cabeçalhos de segurança por padrão, confiando no ecossistema (helmet, cors, express-rate-limit) para preenchê-los - veja Middleware de Segurança.
A observabilidade segue a mesma filosofia - não há logging de requisição ou tracing embutido, apenas um slot app.use() perto do topo da cadeia onde um middleware de logging (ou uma instrumentação OpenTelemetry) pode ficar e ver cada requisição passar.
Abstração principal do Framework
Ponto forte
Ponto fraco
Melhor Encaixe
Express: cadeia de middleware plana
Modelo mental simples; ecossistema enorme de middleware drop-in
Sem encapsulamento; custo linear por camada; fácil de desordenar
A maioria das APIs CRUD; equipes que valorizam a amplitude do ecossistema sobre a estrutura
Fastify: árvore de plugins encapsulada
Escopos isolados; despacho compilado por schema para velocidade
Mais estrutura inicial para aprender
APIs de alto rendimento ou orientadas a schema
NestJS: grafo de módulos DI
Arquitetura imposta em escala; testável via DI
Abstração mais pesada; curva de aprendizado mais íngreme
Grandes equipes, serviços empresariais de longa duração
"Um Router isola seu middleware da mesma forma que um plugin Fastify." Ele apenas escopa quais requisições chegam a um determinado conjunto de camadas por prefixo de caminho - ele compartilha as mesmas extensões de protótipo globais e qualquer estado mutável compartilhado com o resto do app.
"A ordem do middleware é uma preferência de estilo." É um requisito de correção - um parser de corpo registrado após uma rota que lê req.body verá undefined, porque a cadeia já passou desse ponto quando o parser seria executado.
"Chamar next() pula para a próxima rota correspondente, não para o próximo middleware." Ele avança para a próxima camada na ordem de registro, correspondida independentemente para cada camada - ele não tem conhecimento de "rotas" como um conceito distinto de middleware.
"O tratamento de erros é executado automaticamente quando um handler lança uma exceção." Apenas next(err) (ou, no Express 5, uma Promise rejeitada encaminhada automaticamente) roteia para o middleware de erro - um throw síncrono em padrões mais antigos não-async pode travar o processo se não for capturado.
"Express é um framework completo como Rails ou Django." É deliberadamente minimalista - apenas roteamento e despacho; validação, integração ORM e estrutura são deixadas para o ecossistema ou para algo construído sobre ele, como NestJS.
Qual é a única abstração principal do Express, em uma frase?
Uma cadeia ordenada de funções (req, res, next), todas compartilhando os mesmos objetos de requisição e resposta, percorridas na ordem de registro para cada requisição recebida.
De onde veio a ideia da cadeia de middleware?
O Express é construído diretamente sobre o Connect, uma biblioteca Node anterior que introduziu o mesmo conceito de cadeia de middleware linear - o Express adicionou roteamento por cima sem mudar esse modelo de execução subjacente.
Como o Express realmente despacha uma requisição internamente?
Ele percorre uma lista interna ordenada de "camadas" (cada uma um matcher de caminho/método mais um handler), executando cada uma que corresponde até que uma camada encerre a resposta ou a lista seja esgotada - não é uma busca em árvore, é uma varredura linear.
Um `Router` cria um escopo isolado como um plugin Fastify?
Não - ele apenas escopa quais requisições chegam às suas camadas, por prefixo de caminho. Extensões de protótipo e qualquer estado mutável compartilhado ainda se aplicam em todo o app, ao contrário dos contextos de plugin genuinamente encapsulados do Fastify.
Por que a ordem do middleware importa tanto no Express?
Porque a cadeia é estritamente linear e com estado - um middleware posterior na cadeia só pode ver efeitos (como um corpo parseado, ou um ID de usuário anexado) de middleware que rodou antes dele, nunca depois.
Como `next(err)` realmente muda o despacho?
Ele pula todas as camadas restantes de assinatura normal e salta diretamente para o middleware de tratamento de erros de quatro argumentos mais próximo - que é por que esses handlers devem ser registrados por último, como o destino de fallback para esse salto.
O que mudou no tratamento de erros no Express 5?
Promises rejeitadas retornadas de handlers de rota async agora são encaminhadas automaticamente para next(err). No Express 4, uma rejeição não tratada dentro de um handler async era silenciosamente engolida, a menos que você adicionasse ferramentas extras ou envolvesse cada handler manualmente.
Por que o Express tem um desempenho inferior ao Fastify em taxas de requisição muito altas?
Cada camada na cadeia adiciona uma chamada de função e uma verificação de correspondência por requisição, mesmo para requisições que a camada ignora no final - um custo proporcional ao comprimento da cadeia. O Fastify compila seu roteamento e serialização antecipadamente, evitando grande parte dessa sobrecarga por requisição.
Quando não devo usar o modelo de middleware do Express?
Quando você precisa de isolamento genuíno entre módulos de funcionalidade (prefira o encapsulamento de plugins do Fastify) ou uma arquitetura imposta e testável em escala de equipe grande (prefira o grafo de módulos DI do NestJS) - a cadeia plana e com estado compartilhado do Express trabalha contra ambos os objetivos por design.
O Express é opinativo sobre a estrutura do projeto?
Não, deliberadamente não. Ele fornece apenas despacho e roteamento; parsing de corpo além dos embutidos, validação, acesso a banco de dados e layout de arquivos são deixados para o desenvolvedor ou para pacotes do ecossistema.
Por que as pessoas dizem que o Express é "apenas Connect com roteamento"?
Porque isso é quase literalmente verdade - o Express reutiliza o modelo de execução de cadeia de middleware do Connect e adiciona correspondência de app.get/app.post/parâmetros de rota por cima, em vez de inventar um mecanismo de despacho diferente.
Este modelo torna o Express inadequado para aplicações grandes?
Não inerentemente - muitas APIs de produção grandes rodam no Express com sucesso - mas a falta de estrutura imposta significa que a equipe deve impor suas próprias convenções (routers por domínio, camadas de serviço) em vez de obtê-las do framework.