Node.js é uma plataforma para execução de código JavaScript no servidor que se baseia no interpretador V8 do Google Chrome.

Ele tem ganhado força nos últimos anos por diversos motivos, dois deles são:

  • É um ponto de entrada para programadores front-end ingressarem no mercado de aplicações server side sem precisar aprender outra linguagem;
  • Tem uma arquitetura voltada para serviços leves que recebem muitas requisições concorrentes, além de permitir uma escalabilidade de projetos mais simples e barata.

Este artigo mostra as principais diferenças entre programar em Node.js e PHP para ingressar com o pé direito na plataforma do ponto de vista de quem está acostumado a trabalhar com PHP.

Modelo de programação

A forma de programar em Node.js difere bastante da forma de programar em PHP. No PHP o comum é o código ser executado sequencialmente, então quando é preciso realizar uma busca em uma fonte externa, por exemplo um banco de dados, fazemos de forma similar a seguinte:

$resultado = db->query('select ...');
// resultado é usado pelo programa

Dessa forma, a aplicação inteira fica esperando a query retornar um valor para $resultado, parando todo o processo da aplicação e implicando que esse código precisa rodar em multithread para servir múltiplas requisições. Esse comportamento classifica o PHP como uma linguagem bloqueante, por padrão.

Como o Node.js executa JavaScript no servidor, uma consulta a um banco de dados pode ser feita de forma que não bloqueie o processo da aplicação:

db.query("select ...", function (resultado) {
// resultado é usado pelo programa
});

No código acima, o método query recebe como segundo parâmetro, uma função anônima, e quando o resultado for recuperado do banco de dados essa função será executada de forma assíncrona, recebendo resultado da query como seu parâmetro. Essa função que é chamada dentro de outra função é a callback function. Enquanto isso, a aplicação continua seu processo sem esperar que a query termine de executar. Esse comportamento de código assíncrono sem a necessidade de trabalhar em multithread classifica o JavaScript como uma linguagem não bloqueante.

Diferenças de infraestrutura

O PHP é uma linguagem de programação interpretada que executa em um servidor web, comumente o Apache. Como PHP é bloqueante, o Apache, por padrão, cria uma nova thread do processo sempre que uma requisição é feita ao web service. Entretanto, o Apache possui uma pool limitada de threads que pode criar. Se todas as possíveis threads do servidor estiverem em execução, quando uma requisição for feita, essa nova requisição irá esperar até que uma thread esteja disponível, correndo o risco do tempo de a requisição esgotar.

O Node foi criado com a intenção de realizar Entrada e Saída (I/O) de dados e forma que não bloqueie o fluxo da aplicação. Ele faz isso por meio da criação de event loops em uma única thread, que são executados sempre que uma requisição é feita ao serviço criar uma nova thread. JavaScript foi escolhido como linguagem implementada ao Node por ser capaz de tratar código assíncrono no navegador, o que é compatível com o seu paradigma.

Servindo uma aplicação

Ao contrário do PHP onde o servidor web (Apache) interpreta e executa os arquivos .php quando é feita uma requisição, o Node.js utiliza uma forma interna para servir suas aplicações na web. O próprio Node possui a biblioteca http, que fornece ferramentas para iniciar o servidor e ouvir uma porta. Abaixo um exemplo de como servir uma aplicação com Node.js:

const http = require('http');

const endereco = 'localhost';
const porta = 3000;

const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Olá Mundo!');
});

server.listen(porta, endereco, () => {
    console.log(`Server running at http://${endereco}:${porta}/`);
});
  • Linha 1: é feita a importação do módulo built-in http. Este é um módulo de baixo nível que têm as ferramentas necessárias para criar um servidor, tratar requisições, enviar respostas, etc.
    const http = require('http');
  • Linhas 3 e 4: são criadas as constantes que guardam os valores do endereço onde deve estar o servidor e a sua porta.
    const endereco = 'localhost';
    const porta = 3000;
  • Linhas 6 a 9: é criado o objeto server a partir de createServer do módulo http. Como parâmetro de createServer é passada uma função anônima que será executada sempre que houver uma requisição HTTP ao servidor e enviará a frase "Olá Mundo!" como resposta, com código de status HTTP 200 (OK) e com o cabeçalho da resposta definido para texto:
    const server = http.createServer((req, res) => {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Olá Mundo!\n');
    });
  • Linhas 11 a 13: é executado o método listen de server, que recebe como parâmetros a porta e o endereço para que o servidor ouça requisições http nesse endereço e porta. O terceiro parâmetro é uma função callback que será executada quando o servidor entrar no loop de execução:
    server.listen(porta, endereco, () => {
        console.log(`Server running at http://${endereco}:${porta}/`);
    });

Diferenças de performance

De maneira geral, o modelo de thread única não bloqueante do Node.js é mais performático que o modelo tradicional multithread, uma vez que para executar múltiplas threads a máquina utiliza de mais memória e processamento do que para rodar uma única thread. Por causa disso, a manutenção de servidores Node tende a ser mais barata do que a de outras plataformas, quando comparados serviços de mesmo tamanho.

Conforme a aplicação aumenta é natural surgir a necessidade de servir a mesma em mais de uma máquina, uma vez que o número de conexões simultâneas em um serviço Node é maior que em aplicações multithread. As aplicações em Node costumam precisar de menos servidores, variando de acordo com a natureza da aplicação.

Por padrão então, aplicações em Node.js tendem a ser mais performáticas que aplicações PHP no que diz respeito ao tratamento de múltiplas requisições, o que torna o Node mais adequado para aplicações com grandes fluxos de dados.

Como o servidor Node utiliza um único processo separado em event loops, o uso do processador não é totalmente otimizado em comparação com aplicações PHP, o que torna o Node não recomendado para aplicações que realizam muito processamento por parte do servidor.

Por fim, é interessante citar que existem formas de escrever código não bloqueante em PHP. A biblioteca PHPReact, por exemplo, provê formas de implementar programação orientada a eventos. Sua proposta é servir como biblioteca de baixo nível para a construção de componentes que trabalham de forma assíncrona semelhante ao Node.js. Esse tipo de recurso trás o PHP à luz do sistema de event loops utilizado pelo Node e reduz comprovadamente a diferença de performance entre o Node e o PHP quando implementado.

Considerações finais

Concluímos então que o Node.js é uma ferramenta poderosíssima para a criação de aplicações web server side que demandam múltiplas requisições e que lidam com grandes volumes de dados. Dito isso, vemos que seu maior poder está em reaproveitar o conhecimento de programadores front-end em JavaScript que conseguem se renovar como desenvolvedores fullstack sem precisar aprender uma nova linguagem de programação. Isso é um avanço inédito e não surpreende que a comunidade JavaScript seja uma das que mais cresce no mundo do desenvolvimento de softwares para web.