“Se o #nginx não estiver na frente do seu servidor Node, provavelmente você está fazendo algo errado.” – Bryan Hughes no Twitter
O Node.js é a principal ferramenta para criar aplicações de servidor em JavaScript, a linguagem de programação mais popular do mundo. Com funcionalidades tanto de servidor web quanto de servidor de aplicações, o Node.js é hoje considerado uma ferramenta essencial para todos os tipos de desenvolvimento e entrega baseados em microsserviços. (Baixe gratuitamente um relatório da Forrester sobre Node.js e NGINX.)
O Node.js pode substituir ou complementar o Java ou o .NET no desenvolvimento de aplicações de backend.
O Node.js opera em uma única thread e usa E/S não bloqueante, o que permite escalar e dar suporte a dezenas de milhares de operações simultâneas. Ele compartilha essas características arquitetônicas com o NGINX e resolve o problema C10K — dar suporte a mais de 10.000 conexões simultâneas — que o NGINX também foi criado para resolver. O Node.js é conhecido por seu alto desempenho e pela produtividade que proporciona aos desenvolvedores.
Então, o que poderia dar errado?
O Node.js tem alguns pontos fracos e vulnerabilidades que podem tornar os sistemas baseados em Node.js propensos a apresentar baixo desempenho ou até mesmo falhas. Os problemas surgem com mais frequência quando uma aplicação web baseada em Node.js passa por um rápido crescimento de tráfego.
Além disso, o Node.js é uma ótima ferramenta para criar e executar a lógica da aplicação que gera o conteúdo principal e variável da sua página da web. No entanto, ele não é tão adequado para disponibilizar conteúdo estático — como imagens e arquivos JavaScript — nem para fazer o balanceamento de carga entre vários servidores.
Para aproveitar ao máximo o Node.js, é necessário armazenar conteúdo estático em cache, usar proxy e balancear a carga entre vários servidores de aplicações, além de gerenciar a disputa por portas entre clientes, o Node.js e serviços auxiliares, como servidores que executam o Socket.IO. O NGINX pode ser usado para todas essas finalidades, o que o torna uma excelente ferramenta para otimizar o desempenho do Node.js.
Use estas dicas para melhorar o desempenho de aplicações Node.js:
- Implemente um servidor proxy reverso
- Armazenar arquivos estáticos em cache
- Distribuir o tráfego entre vários servidores
- Fazer proxy de conexões WebSocket
- Implemente SSL/TLS e HTTP/2
Observação: Uma solução rápida para melhorar o desempenho de aplicações Node.js é modificar a configuração do Node.js para aproveitar os servidores modernos com vários núcleos. Consulte a documentação do Node.js para saber como fazer o Node.js iniciar processos filhos separados — em quantidade igual ao número de CPUs do seu servidor web. Como que por mágica, cada processo é então alocado a uma única CPU, proporcionando um grande aumento de desempenho.
Dica 1 – Implemente um servidor proxy reverso
Nós, da NGINX, Inc., sempre ficamos um pouco horrorizados quando vemos servidores de aplicativos diretamente expostos ao tráfego de entrada da Internet e usados como base de sites de alto desempenho. Isso inclui muitos sites baseados em WordPress, por exemplo, bem como sites em Node.js.
O Node.js, mais do que a maioria dos servidores de aplicações, foi projetado para ser escalável, e seu componente de servidor web consegue lidar razoavelmente bem com um grande volume de tráfego da Internet. No entanto, servir conteúdo web não é a razão de ser do Node.js — não foi para isso que ele realmente foi criado.
Se você tem um site com alto volume de tráfego, o primeiro passo para melhorar o desempenho da aplicação é colocar um servidor proxy reverso à frente do seu servidor Node.js. Isso protege o servidor Node.js da exposição direta ao tráfego da Internet e oferece muita flexibilidade para usar vários servidores de aplicação, balancear a carga entre eles e armazenar conteúdo em cache.

Colocar o NGINX à frente de uma configuração de servidor existente como um servidor proxy reverso, além de usá-lo para outras finalidades, é um dos principais casos de uso do NGINX, adotado por dezenas de milhões de sites em todo o mundo.
Há vantagens específicas em usar o NGINX como servidor proxy reverso para Node.js, incluindo:
- Simplificação do gerenciamento de privilégios e das atribuições de portas
- Veicular imagens estáticas com mais eficiência (veja a próxima dica)
- Gerenciando falhas do Node.js com sucesso
- Mitigação de ataques de DoS
Observação: estes tutoriais explicam como usar o NGINX como servidor proxy reverso em ambientes Ubuntu 14.04 ou CentOS, além de oferecerem uma visão geral útil para quem pretende colocar o NGINX à frente do Node.js.
Dica 2 – Armazene arquivos estáticos em cache
À medida que o uso de um site baseado em Node.js aumenta, o servidor começa a ficar sobrecarregado. Nesse ponto, há duas coisas que você deve fazer:
- Aproveite ao máximo o servidor Node.js
- Facilite a adição de servidores de aplicativos e o balanceamento de carga entre eles
Na verdade, isso é fácil de fazer. Comece implementando o NGINX como um servidor proxy reverso, conforme descrito na dica anterior. Isso facilita a implementação de cache, balanceamento de carga (quando há vários servidores Node.js) e muito mais.
O site da Modulus, uma plataforma de contêineres de aplicações, tem um artigo útil sobre como turbinar o desempenho de aplicações Node.js com o NGINX. Com o Node.js fazendo todo o trabalho sozinho, o site do autor conseguiu atender, em média, quase 900 requisições por segundo. Com o NGINX como servidor proxy reverso, fornecendo conteúdo estático, o mesmo site atendeu a mais de 1.600 requisições por segundo — uma melhoria de desempenho de quase 2x.
Dobrar o desempenho dá a você tempo para tomar medidas adicionais a fim de comportar um crescimento ainda maior, como revisar (e possivelmente aprimorar) o design do seu site, otimizar o código da sua aplicação e implantar servidores de aplicação adicionais.
A seguir está o código de configuração que funciona para um site hospedado no Modulus:
server { listen 80;
server_name static-test-47242.onmodulus.net;
root /mnt/app;
index index.html index.htm;
location /static/ {
try_files $uri $uri/ =404;
}
location /api/ {
proxy_pass http://node-test-45750.onmodulus.net;
}
}Este artigo detalhado de Patrick Nommensen, da NGINX, Inc., explica como ele armazena em cache o conteúdo estático de seu blog pessoal, que é executado na plataforma de código aberto para blogs Ghost, uma aplicação Node.js. Embora alguns detalhes sejam específicos do Ghost, grande parte do código pode ser reutilizada em outras aplicações Node.js.
Por exemplo, no bloco location do NGINX, provavelmente será conveniente impedir que determinados conteúdos sejam armazenados em cache. Normalmente, não é recomendável armazenar em cache a interface administrativa de uma plataforma de blogs. Veja a seguir um código de configuração que desativa (ou impede) o armazenamento em cache da interface administrativa do Ghost:
location ~ ^/(?:ghost|signout) { proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://ghost_upstream;
add_header Cache-Control "no-cache, private, no-store,
must-revalidate, max-stale=0, post-check=0, pre-check=0";
}Para obter informações gerais sobre como disponibilizar conteúdo estático, consulte o Guia de Administração do NGINX Plus. O Guia de Administração inclui instruções de configuração, várias opções para responder a tentativas bem-sucedidas ou malsucedidas de localizar um arquivo e abordagens de otimização para obter um desempenho ainda melhor.
O armazenamento em cache de arquivos estáticos no servidor NGINX reduz significativamente a carga de trabalho do servidor de aplicações Node.js, permitindo que ele alcance um desempenho muito superior.
Dica 3 – Implemente um balanceador de carga para Node.js
O verdadeiro segredo para alcançar um alto desempenho — ou seja, um desempenho praticamente ilimitado — em aplicações Node.js é executar vários servidores de aplicação e distribuir a carga entre todos eles.
O balanceamento de carga do Node.js pode ser particularmente complexo, pois o Node.js permite um alto nível de interação entre o código JavaScript executado no navegador e o código JavaScript executado no servidor de aplicações Node.js, usando objetos JSON como meio de troca de dados. Isso significa que uma determinada sessão de cliente é executada continuamente em um servidor de aplicações específico, e manter a persistência da sessão é inerentemente difícil quando há vários servidores de aplicações.
Uma das principais vantagens da Internet e da web é seu alto grau de ausência de estado, que permite que as solicitações dos clientes sejam atendidas por qualquer servidor com acesso ao arquivo solicitado. O Node.js contraria esse modelo sem estado e funciona melhor em um ambiente com estado, no qual o mesmo servidor responde de forma consistente às solicitações de cada cliente específico.
Esse requisito pode ser melhor atendido pelo NGINX Plus, em vez do NGINX Open Source. As duas versões do NGINX são bastante semelhantes, mas uma diferença importante é a compatibilidade com diferentes algoritmos de balanceamento de carga.
O NGINX oferece suporte a métodos de balanceamento de carga sem estado:
- Round Robin – Uma nova solicitação é encaminhada ao próximo servidor de uma lista.
- Menor número de conexões – Uma nova solicitação é encaminhada ao servidor com o menor número de conexões ativas.
- Hash de IP – Uma nova solicitação é encaminhada ao servidor atribuído ao hash do endereço IP do cliente.
Apenas um desses métodos, o IP Hash, encaminha de forma confiável as solicitações de um determinado cliente para o mesmo servidor, o que beneficia as aplicações Node.js. No entanto, o IP Hash pode facilmente fazer com que um servidor receba um número desproporcional de solicitações em detrimento dos demais, conforme descrito nesta publicação do blog sobre técnicas de balanceamento de carga. Esse método oferece suporte à manutenção de estado, mas pode resultar em uma distribuição abaixo do ideal das solicitações entre os recursos dos servidores.
Ao contrário do NGINX, o NGINX Plus oferece suporte à persistência de sessão. Com a persistência de sessão em uso, o mesmo servidor recebe de forma confiável todas as solicitações de um determinado cliente. Assim, são maximizadas tanto as vantagens do Node.js, com sua comunicação com estado entre cliente e servidor, quanto as do NGINX Plus, com sua capacidade avançada de balanceamento de carga.
Assim, você pode usar o NGINX ou o NGINX Plus para oferecer balanceamento de carga entre vários servidores Node.js. No entanto, somente com o NGINX Plus é possível obter o máximo desempenho de balanceamento de carga e, ao mesmo tempo, manter o estado de forma compatível com o Node.js. As verificações de integridade da aplicação e os recursos de monitoramento integrados ao NGINX Plus também são úteis nesse caso.
O NGINX Plus também oferece suporte à drenagem de sessões, que permite que um servidor de aplicações conclua normalmente as sessões atuais após receber uma solicitação para se retirar de serviço.
Dica 4 – Faça proxy de conexões WebSocket
O HTTP, em todas as suas versões, foi projetado para comunicações do tipo “pull”, nas quais o cliente solicita arquivos ao servidor. O WebSocket é uma ferramenta que permite comunicações do tipo “push” e “push/pull”, nas quais o servidor pode enviar proativamente arquivos que o cliente não solicitou.
O protocolo WebSocket facilita uma interação mais robusta entre cliente e servidor, reduzindo a quantidade de dados transferidos e minimizando a latência. Quando necessário, é possível estabelecer uma conexão full-duplex, na qual tanto o cliente quanto o servidor iniciam e recebem solicitações conforme necessário.
O protocolo WebSocket tem uma interface JavaScript robusta e, por isso, é uma opção natural para o Node.js como servidor de aplicações e, no caso de aplicações web com volumes moderados de transações, também como servidor web. Quando os volumes de transações aumentam, faz sentido inserir o NGINX entre os clientes e o servidor web Node.js, usando o NGINX ou o NGINX Plus para armazenar arquivos estáticos em cache e balancear a carga entre vários servidores de aplicações.
O Node.js é frequentemente usado em conjunto com o Socket.IO, uma API WebSocket que se tornou bastante popular em aplicações Node.js. Isso pode sobrecarregar a porta 80 (para HTTP) ou a porta 443 (para HTTPS), e a solução é usar um proxy para o servidor Socket.IO. Você pode usar o NGINX como servidor proxy, conforme descrito acima, e também obter funcionalidades adicionais, como cache de arquivos estáticos, balanceamento de carga e muito mais.

A seguir está o código de um arquivo de aplicativo Node server.js que escuta na porta 5000. Ele atua como um servidor proxy (não como um servidor web) e encaminha as solicitações para a porta correta:
var io = require('socket.io').listen(5000);
io.sockets.on('connection', function (socket) {
socket.on('set nickname', function (name) {
socket.set('nickname', name, function () {
socket.emit('ready');
});
});
socket.on('msg', function () {
socket.get('nickname', function (err, name) {
console.log('Chat message by ', name);
});
});
});No seu arquivo index.html, adicione o código a seguir para se conectar à aplicação do servidor e criar uma conexão WebSocket entre a aplicação e o navegador do usuário:
<script src="/socket.io/socket.io.js"></script><script>// <![CDATA[
var socket = io(); // your initialization code here.
// ]]>
</script>Para obter instruções completas, incluindo a configuração do NGINX, consulte nosso post no blog sobre como usar o NGINX e o NGINX Plus com Node.js e Socket.IO. Para saber mais sobre possíveis questões de arquitetura e infraestrutura para aplicações web desse tipo, consulte nosso post no blog sobre aplicações web em tempo real e WebSocket.
Dica 5 – Implemente SSL/TLS e HTTP/2
Cada vez mais sites estão usando SSL/TLS para proteger todas as interações dos usuários no site. Cabe a você decidir se e quando fará essa mudança, mas, caso decida fazê-la, o NGINX oferece suporte à transição de duas maneiras:
- Você pode encerrar uma conexão SSL/TLS com o cliente no NGINX depois de configurar o NGINX como proxy reverso. O servidor Node.js envia e recebe solicitações e conteúdo não criptografados de e para o servidor proxy reverso NGINX.
- Os primeiros indícios apontam que o uso do HTTP/2, a nova versão do protocolo HTTP, pode compensar em grande parte ou totalmente a perda de desempenho que, de outra forma, seria causada pelo uso de SSL/TLS. O NGINX oferece suporte a HTTP/2, e você pode encerrar as conexões HTTP/2 juntamente com as conexões SSL/TLS, eliminando novamente a necessidade de alterações nos servidores de aplicações Node.js.
Entre as etapas de implementação necessárias estão a atualização da URL no arquivo de configuração do Node.js, o estabelecimento e a otimização de conexões seguras na configuração do NGINX e, se desejado, o uso de SPDY ou HTTP/2. Adicionar suporte a HTTP/2 significa que as versões de navegadores compatíveis com HTTP/2 se comunicarão com seu aplicativo usando o novo protocolo; as versões mais antigas continuarão usando HTTP/1.x.

O código de configuração a seguir é para um blog Ghost que usa SPDY, conforme descrito aqui. Ele inclui recursos avançados, como o grampeamento OCSP. Para considerações sobre o uso do NGINX para terminação SSL, incluindo a opção de grampeamento OCSP, consulte aqui. Para uma visão geral dos mesmos tópicos, consulte aqui.
Você precisa fazer apenas pequenas alterações para configurar seu aplicativo Node.js e migrar do SPDY para o HTTP/2, seja agora ou quando o suporte ao SPDY for descontinuado no início de 2016.
server { server_name domain.com;
listen 443 ssl spdy;
spdy_headers_comp 6;
spdy_keepalive_timeout 300;
keepalive_timeout 300;
ssl_certificate_key /etc/nginx/ssl/domain.key;
ssl_certificate /etc/nginx/ssl/domain.crt;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 24h;
ssl_buffer_size 1400;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/trust.crt;
resolver 8.8.8.8 8.8.4.4 valid=300s;
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';
add_header X-Cache $upstream_cache_status;
location / {
proxy_cache STATIC;
proxy_cache_valid 200 30m;
proxy_cache_valid 404 1m;
proxy_pass http://ghost_upstream;
proxy_ignore_headers X-Accel-Expires Expires Cache-Control;
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
proxy_hide_header X-powered-by;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
expires 10m;
}
location /content/images {
alias /path/to/ghost/content/images;
access_log off;
expires max;
}
location /assets {
alias /path/to/ghost/themes/uno-master/assets;
access_log off;
expires max;
}
location /public {
alias /path/to/ghost/built/public;
access_log off;
expires max;
}
location /ghost/scripts {
alias /path/to/ghost/core/built/scripts;
access_log off;
expires max;
}
location ~ ^/(?:ghost|signout) {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://ghost_upstream;
add_header Cache-Control "no-cache, private, no-store,
must-revalidate, max-stale=0, post-check=0, pre-check=0";
proxy_set_header X-Forwarded-Proto https;
}
}
Conclusão
Este post descreve algumas das melhorias de desempenho mais importantes que você pode implementar em suas aplicações Node.js. Ele se concentra na adição do NGINX ao conjunto de tecnologias da sua aplicação junto com o Node.js — usando o NGINX como servidor proxy reverso, para armazenar arquivos estáticos em cache, balancear a carga, intermediar conexões WebSocket e encerrar os protocolos SSL/TLS e HTTP/2.
A combinação do NGINX com o Node.js é amplamente reconhecida como uma forma de criar novas aplicações compatíveis com microsserviços ou adicionar flexibilidade e recursos a aplicações existentes baseadas em SOA que usam Java ou Microsoft .NET. Este post ajuda você a otimizar suas aplicações Node.js e, se desejar, a colocar em prática a parceria entre o Node.js e o NGINX.
Sobre o autor

Publicações relacionadas no blog

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade
Descubra como o F5 NGINX One aprimora o gerenciamento e a observabilidade de ambientes híbridos e multicloud ao oferecer um console web centralizado baseado em SaaS, com insights em tempo real, conformidade automatizada e gerenciamento simplificado de configurações.

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa
Descubra como transformar uma definição de esquema OpenAPI em uma configuração NGINX totalmente funcional, operando como um API Gateway com a segurança de um Web Application Firewall e um Portal do Desenvolvedor, usando uma abordagem declarativa de API.

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor
Picos de tráfego e sobrecarga de servidores são preocupações sérias para varejistas online em qualquer época, mas especialmente durante a “Cyber Season” de compras de fim de ano. Detalhamos como você pode aproveitar as soluções de entrega de aplicações de nível de produção da NGINX para se preparar para aumentos repentinos de tráfego.

Distribuição segura de chaves privadas SSL com o NGINX
Descrevemos três maneiras progressivamente mais seguras de proteger chaves privadas SSL ao configurar o NGINX para processar tráfego HTTPS: permitir acesso de leitura apenas ao usuário root, criptografar as chaves com senhas armazenadas separadamente e distribuir as senhas a partir de um repositório central.
Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX
O módulo JavaScript do NGINX lançado com o NGINX Plus R15 oferece suporte a subsolicitações, possibilitando uma classe totalmente nova de casos de uso para o NGINX Plus. Este exemplo completo também usa a API do NGINX Plus e os módulos de chave-valor para converter solicitações de API dos clientes em solicitações separadas para as APIs dos microsserviços que compõem um aplicativo.
Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus
Confira dicas para evitar o esgotamento de portas efêmeras com ajustes no kernel do Linux, conexões keepalive e conexões vinculadas dinamicamente a endereços IP locais.