Introdução ao Node.js: rodando JS no servidor

1. O que é Node.js e por que ele existe?

Historicamente, o JavaScript estava confinado ao navegador. Era a linguagem que dava vida às páginas web, mas não tinha como interagir com o sistema de arquivos, criar servidores ou acessar bancos de dados. Tudo mudou em 2009, quando Ryan Dahl pegou o motor V8 do Google Chrome — o mesmo que interpreta JavaScript no navegador — e o executou fora dele. Nasceu o Node.js.

A diferença fundamental é simples: enquanto o navegador oferece APIs como window, document e fetch para manipular a interface do usuário, o Node.js fornece módulos como fs (file system), http e path para construir aplicações server-side. Em vez de manipular o DOM, você lê arquivos, escuta portas de rede e gerencia processos.

2. Instalação e primeiro "Hello World"

A maneira mais comum de instalar o Node.js é através do site oficial (nodejs.org). Você encontrará duas versões: LTS (Long Term Support), recomendada para produção, e Current, com as features mais recentes. Gerenciadores de versão como o nvm (Node Version Manager) permitem alternar entre versões facilmente.

Após a instalação, crie um arquivo hello.js:

console.log("Olá, servidor!");

Execute no terminal:

node hello.js

Pronto: você acabou de rodar JavaScript no servidor. O Node.js também oferece um REPL interativo. Basta digitar node no terminal sem nenhum arquivo e começar a testar expressões:

> 2 + 2
4
> const msg = "Node.js é incrível"
> msg.toUpperCase()
'NODE.JS É INCRÍVEL'

3. O modelo assíncrono e event-loop

O Node.js é single-threaded, mas isso não significa que executa uma tarefa por vez. Graças ao event-loop e ao I/O não bloqueante, ele pode lidar com milhares de conexões simultâneas sem criar uma thread para cada uma.

Compare a leitura síncrona e assíncrona de um arquivo:

const fs = require('fs');

// Síncrono (bloqueante)
const dadosSync = fs.readFileSync('arquivo.txt', 'utf-8');
console.log('Leitura síncrona concluída:', dadosSync);

// Assíncrono com callback
fs.readFile('arquivo.txt', 'utf-8', (erro, dados) => {
  if (erro) throw erro;
  console.log('Leitura assíncrona concluída:', dados);
});

console.log('Este log aparece antes da leitura assíncrona!');

Com Promises e async/await, o código assíncrono fica mais legível:

const fs = require('fs').promises;

async function lerArquivo() {
  try {
    const dados = await fs.readFile('arquivo.txt', 'utf-8');
    console.log('Dados lidos:', dados);
  } catch (erro) {
    console.error('Erro ao ler arquivo:', erro);
  }
}

lerArquivo();

4. Módulos nativos do Node.js

O Node.js vem com módulos nativos poderosos. O fs permite manipular arquivos:

const fs = require('fs');

// Escrever em um arquivo
fs.writeFileSync('mensagem.txt', 'Conteúdo salvo com Node.js!');

// Ler diretório
fs.readdir('.', (err, arquivos) => {
  console.log('Arquivos no diretório atual:', arquivos);
});

O módulo path resolve problemas de caminhos entre sistemas operacionais:

const path = require('path');

const caminhoCompleto = path.join(__dirname, 'arquivos', 'dados.json');
console.log('Caminho completo:', caminhoCompleto);
console.log('Extensão:', path.extname('imagem.jpg'));

5. Criando um servidor HTTP básico

Com o módulo http, você cria um servidor web sem frameworks:

const http = require('http');

const servidor = http.createServer((req, res) => {
  // Configurando cabeçalhos
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });

  if (req.url === '/') {
    res.end('<h1>Bem-vindo ao servidor Node.js!</h1>');
  } else if (req.url === '/api') {
    // Retornando JSON
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ mensagem: 'API funcionando', status: 'ok' }));
  } else {
    res.writeHead(404);
    res.end('<h1>404 - Página não encontrada</h1>');
  }
});

servidor.listen(3000, () => {
  console.log('Servidor rodando em http://localhost:3000');
});

Execute e acesse http://localhost:3000. Você criou um servidor HTTP funcional com poucas linhas de código.

6. Gerenciamento de dependências com npm

O npm (Node Package Manager) é o gerenciador de pacotes oficial. Inicie um projeto:

npm init -y

Isso cria o arquivo package.json. Instale pacotes:

npm install lodash
npm install express

O package.json agora lista as dependências, e a pasta node_modules armazena os pacotes baixados. Use-os no código:

const _ = require('lodash');
const numeros = [4, 2, 8, 1, 9];
console.log('Números ordenados:', _.sortBy(numeros));

7. Node.js no ecossistema React

React depende do Node.js para desenvolvimento e build. Ferramentas como Create React App, Vite e Next.js usam Node.js para:

  • Compilar JSX para JavaScript puro
  • Servir arquivos em ambiente de desenvolvimento com hot reload
  • Gerar builds otimizados para produção

Exemplo: servindo um build estático do React com Node.js puro:

const http = require('http');
const fs = require('fs');
const path = require('path');

const servidor = http.createServer((req, res) => {
  let caminhoArquivo = req.url === '/' ? 'index.html' : req.url.slice(1);
  caminhoArquivo = path.join(__dirname, 'build', caminhoArquivo);

  fs.readFile(caminhoArquivo, (err, conteudo) => {
    if (err) {
      res.writeHead(404);
      res.end('Arquivo não encontrado');
      return;
    }
    res.writeHead(200);
    res.end(conteudo);
  });
});

servidor.listen(5000, () => {
  console.log('Aplicação React servida em http://localhost:5000');
});

8. Boas práticas e próximos passos

Em desenvolvimento, use nodemon para reiniciar automaticamente o servidor a cada alteração. Em produção, considere:

  • Gerenciamento de erros com try/catch e logs estruturados
  • Variáveis de ambiente para configurações sensíveis
  • Process managers como PM2 para manter o servidor ativo
// Exemplo de log estruturado
const logger = {
  info: (msg) => console.log(`[INFO] ${new Date().toISOString()} - ${msg}`),
  erro: (msg) => console.error(`[ERRO] ${new Date().toISOString()} - ${msg}`)
};

logger.info('Servidor iniciado com sucesso');

Referências