image

Seguindo na série de NodeJS. Já vimos como instalar o Node, criamos uma aplicação com o Express e já entendemos como funcionam as rotas e o view engine. Vamos agora entender um dos conceitos mais poderosos no Express: Middlewares.

Middleware, em poucas palavras e de maneira bem simples, é um código que roda no pipeline de requisição http. O request chega, passa pelo middleware, e em algum dos componentes desse middleware ele é tratado e a resposta é enviada. Normalmente configuramos os middlewares no arquivo app.js, que configura toda a aplicação. Toda vez que encontrar uma chamada do método “use” da aplicação do express (normalmente “app.use”) você verá um middleware. Vejamos os middlewares presentes na aplicação básica do Express, assim que é criada. Aqui está o seu app.js:

var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');

var app = express();

app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/users', user.list);

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

Vemos o uso de 6 middlewares, juntos, e mais um somente em ambiente de desenvolvimento (express.errorHandler). De onde eles vêm? Vocês podem ver que todos aparecem como extensões do objeto “express”, de forma indireta ou direta. No entanto, o projeto do Express não é o responsável por esses middlewares, eles ficam em outro projeto (com uma exceção), em outro componente npm chamado “Connect”. O Connect é a base dos middlewares do Express.

Vá à página do Connect e você verá diversos outros middlewares não listados no app.js que o Express cria. Dos que temos disponíveis nesse arquivo, via objeto “express”, todos (menos um) vem do Connect. O que o Express faz na prática é expor os middlewares do Connect como se fossem dele, para que você não tenha que referenciar ou instalar o Connect diretamente, nem requerer ele no app.js. Você pode utilizar o Connect diretamente, sem o Express, e aí vai ter que instalar ele via NPM, e vai usar o objeto do Connect para referenciar os middlewares. Com Express isso não é necessário.

Vejamos então o que faz cada um dos middlewares que estamos utilizando:

  • Favicon. Entrega um favicon, que por padrão é o do Express, mas você pode trocar informado o caminho de um arquivo.
  • Logger. Mostra na console onde o node está rodando as chamadas recebidas pelo server. Costumo deixar desligado em produção. Muito útil durante desenvolvimento. Fica assim:
  • BodyParser. Fundamental, faz o parse durante um post, independente se é um form sem file upload, com file upload, ou um post ajax. Na verdade é a reunião de 3 outros middlewares, json, urlencoded e multipart.
  • MethodOverride. Permite usar métodos http em browsers ou situações em que esses métodos não seriam suportados. Por exemplo, browsers em geral não enviam de formulários o método PUT, mas se você colocar um elemento hidden com nome “_method” e valor “PUT”, o express vai entender que é um PUT, por causa desse middleware.
  • app.router. Esse é um middleware do Express diretamente, não do Connect. Ele coloca as suas rotas (app.get, app.post, etc), no pipeline dos middlewares. Sua chamada é opcional, e já vou explicar porque.
  • Static. Serve arquivos estáticos, podendo mapear um diretório físico a um diretório virtual. O padrão é entregar qualquer arquivo que esteja no diretório “public”. É por esse motivo que você consegue carregar o arquivo de folha de estilos que está em no diretório “public/stylesheets/style.css” chamando http://localhost:3000/stylesheets/style.css.
  • ErrorHandler. Quando acontece uma exception ele mostra ela. Muito útil durante desenvolvimento e perigoso em produção (por expor partes internas do server), você o vê quando tem um problema na aplicação e ele mostra o stack trace todo no navegador.

É importante entender que os middlewares são executados em ordem. Cada request que chega passa por cada um dos middlewares, até que um deles lide com o request, ou passe o request para o middleware seguinte. Assim, se você colocar 50 middlewares até chegar no middleware que recebe os posts da sua aplicação você terá um overhead considerável. Com Express você é quem decide o peso da sua aplicação. Quanto mais middlewares, mais pesada. Isso permite ter uma app extremamente leve que faz somente o que precisa.

E porque a ordem importa? É fácil explicar. Imagine que você tem o arquivo public/carrinho/index.html, mas também tem uma rota que aponta para um comportamento dinâmico em “/carrinho”, via “app.get(‘/carrinho’, algumafuncao)”. Quem vai responder para “/carrinho”, a sua rota, ou o arquivo estático? Depende da ordem dos middlewares. Se você colocou o middleware Static antes do app.router, o arquivo estático será servido, se colocou depois será a sua rota. É simples assim. Lembre-se que eu disse que o cadastro do middleware de rotas com “app.use(app.router)” era opcional? Pois é, isso porque o express vai utilizar suas rotas mesmo se você não o chamar, mas vai colocá-las no final do pipeline de middlewares. Se você quiser ela em outro lugar, você tem que configurá-la com app.use. Por convenção recomendo sempre colocar o app.use de rotas pra ficar explícito. E evite colocar arquivos diretamente no diretório “public” que poderiam conflitar com suas rotas, no caso, seria melhor colocar o arquivo em “public/html/carrinho/index.html”.

Diversos outros middlewares estão disponíveis. Há middlewares para sessão, gestão de cookies, cache, autenticação, todos já disponíveis no Connect e portanto no Express, além de inúmeros outros feitos pela comunidade. As possibilidades são infinitas. O interessante é que com uma linha de configuração você pode incluir um novo middleware e alterar completamente o comportamento da sua aplicação. Por exemplo, pra integrar autenticação via usuário/senha, Facebook, Twitter, Github, etc, eu utilizo o middleware Everyauth, que já faz tudo pra mim e tenho apenas que fazer uma configuração mínima (exemplo aqui). É simples assim.

E o que é um middleware? Você deve estar imaginando que é algo super complexo, certo? Mas não é. Uma linguagem funcional como JavaScript permite tornar algumas coisas bem simples e esse é um caso: um middleware é uma função que recebe um request, um response, e uma outra função que chamará o próximo middleware. Veja, por exemplo, o código do middleware favicon aqui. O método express.favicon() vai retornar uma função que recebe (req, res, next) e serve o arquivo. Simples assim. Assim, se você notar que tem um comportamento que precisa que aconteça no meio do pipeline, você pode simplesmente escrever uma função com essa assinatura e chamar app.use nela no arquivo app.js e pronto: já terá o seu middleware funcionando.

A ideia de middlewares não é nova, apesar de ser implementada de formas diferentes. O Rails usa, o OWIN usa, e diversos outros frameworks web usam. É importante enteder o que eles fazem e como funcionam para tirar o melhor proveito do ambiente que está disponível. Dê uma boa lida na página do Connect para ver suas opções.

E com isso saímos das entranhas do Express e do Connect. Nos próximos posts vamos falar de coisas mais práticas.

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.