Criando um servidor HTTP com o módulo nativo http

1. Introdução ao módulo http do Node.js

O módulo http é um dos módulos nativos mais fundamentais do Node.js. Ele permite criar servidores web sem depender de frameworks externos como Express.js, oferecendo controle total sobre o ciclo de vida das requisições HTTP. Embora frameworks adicionem produtividade, entender o módulo http puro é essencial para compreender como o Node.js funciona por baixo dos panos.

Diferenças entre http e https: Enquanto http trabalha com conexões não criptografadas na porta 80 (ou qualquer porta), o módulo https adiciona uma camada SSL/TLS usando certificados digitais, operando tipicamente na porta 443. Ambos compartilham a mesma API, mas https exige opções de segurança como key e cert.

Importando o módulo: No Node.js, você pode usar CommonJS ou ES Modules:

// CommonJS
const http = require('http');

// ES Modules (com "type": "module" no package.json)
import http from 'http';

2. Criando um servidor básico

A estrutura mínima de um servidor HTTP envolve http.createServer() e server.listen(). O callback recebe dois objetos: req (requisição) e res (resposta).

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World\n');
});

const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Servidor rodando em http://localhost:${PORT}/`);
});

Ciclo de vida da requisição: O Node.js recebe a requisição, processa o callback, escreve os cabeçalhos e dados, e finaliza com res.end(). Este fluxo síncrono dentro do callback é bloqueante para aquela requisição específica.

3. Trabalhando com requisições (req)

O objeto req contém informações cruciais sobre a requisição recebida:

const server = http.createServer((req, res) => {
  console.log('Método:', req.method);
  console.log('URL:', req.url);
  console.log('Headers:', req.headers);

  res.end('Requisição recebida');
});

Lendo o corpo da requisição: Para métodos como POST ou PUT, o corpo chega em chunks assíncronos:

const server = http.createServer((req, res) => {
  let body = '';

  req.on('data', chunk => {
    body += chunk.toString();
  });

  req.on('end', () => {
    console.log('Corpo completo:', body);
    res.end('Dados recebidos');
  });
});

Parsing de query strings: Você pode extrair parâmetros manualmente ou usando o módulo url:

const url = require('url');

const server = http.createServer((req, res) => {
  const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
  const nome = parsedUrl.searchParams.get('nome');

  res.end(`Olá, ${nome || 'visitante'}!`);
});

4. Trabalhando com respostas (res)

O objeto res controla o que é enviado de volta ao cliente. Você pode definir cabeçalhos de duas formas principais:

// Usando writeHead (define código e cabeçalhos de uma vez)
res.writeHead(200, { 'Content-Type': 'application/json' });

// Usando setHeader (adiciona cabeçalhos individualmente)
res.setHeader('Content-Type', 'text/html');
res.setHeader('X-Powered-By', 'Node.js');

Enviando dados: Use res.write() para dados parciais e res.end() para finalizar:

const server = http.createServer((req, res) => {
  // JSON
  if (req.url === '/api') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ mensagem: 'API funcionando' }));
  }
  // HTML
  else if (req.url === '/') {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>Bem-vindo!</h1>');
  }
  // Texto puro
  else {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Página não encontrada');
  }
});

5. Roteamento manual sem frameworks

Sem Express, você precisa implementar roteamento manual com condicionais. Uma abordagem comum é usar switch ou if/else:

const server = http.createServer((req, res) => {
  const { method, url } = req;

  switch (true) {
    case method === 'GET' && url === '/':
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end('<h1>Home</h1>');
      break;

    case method === 'GET' && url === '/users':
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify([{ id: 1, nome: 'João' }]));
      break;

    case method === 'POST' && url === '/users':
      let body = '';
      req.on('data', chunk => body += chunk);
      req.on('end', () => {
        const data = JSON.parse(body);
        res.writeHead(201, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ criado: true, ...data }));
      });
      break;

    default:
      res.writeHead(404);
      res.end('Rota não encontrada');
  }
});

Parâmetros dinâmicos: Para rotas como /users/:id, você pode usar expressões regulares:

case method === 'GET' && url.startsWith('/users/'):
  const id = url.split('/')[2];
  res.end(`Usuário ID: ${id}`);
  break;

6. Tratamento de erros e status codes

Responder com códigos HTTP adequados é crucial para uma API bem comportada:

const server = http.createServer((req, res) => {
  try {
    if (req.url === '/error') {
      throw new Error('Algo deu errado!');
    }

    if (req.url === '/notfound') {
      res.writeHead(404, { 'Content-Type': 'text/plain' });
      res.end('Recurso não encontrado');
      return;
    }

    res.writeHead(200);
    res.end('OK');

  } catch (err) {
    console.error('Erro no servidor:', err);
    res.writeHead(500, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ erro: 'Erro interno do servidor' }));
  }
});

Middleware caseiro para 404: Crie uma função que captura rotas não tratadas:

function notFoundHandler(res) {
  res.writeHead(404, { 'Content-Type': 'text/html' });
  res.end('<h1>404 - Página não encontrada</h1>');
}

7. Integração com React (server-side rendering básico)

Para servir uma aplicação React em produção, você precisa servir arquivos estáticos e ter uma página HTML principal:

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

const server = http.createServer((req, res) => {
  // Servir arquivos estáticos
  let filePath = path.join(__dirname, 'build', req.url === '/' ? 'index.html' : req.url);
  const extname = path.extname(filePath);
  const mimeTypes = {
    '.html': 'text/html',
    '.js': 'application/javascript',
    '.css': 'text/css',
    '.json': 'application/json',
    '.png': 'image/png',
    '.jpg': 'image/jpg'
  };

  const contentType = mimeTypes[extname] || 'application/octet-stream';

  fs.readFile(filePath, (err, data) => {
    if (err) {
      // Fallback para SPA - sempre servir index.html
      fs.readFile(path.join(__dirname, 'build', 'index.html'), (err, data) => {
        if (err) {
          res.writeHead(500);
          res.end('Erro interno');
          return;
        }
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end(data);
      });
      return;
    }

    res.writeHead(200, { 'Content-Type': contentType });
    res.end(data);
  });
});

Diferença desenvolvimento vs produção: Em desenvolvimento, ferramentas como Vite ou Webpack têm seus próprios servidores com hot reload. Em produção, você compila o React para arquivos estáticos na pasta build e usa um servidor Node.js (ou Nginx) para servi-los.

8. Boas práticas e próximos passos

Encerramento gracioso: Sempre trate sinais para encerrar o servidor corretamente:

process.on('SIGINT', () => {
  console.log('\nEncerrando servidor...');
  server.close(() => {
    console.log('Servidor encerrado');
    process.exit(0);
  });
});

Limitações do módulo http puro: Embora poderoso, gerenciar rotas complexas, middlewares, parsing de corpo, cookies e sessões manualmente se torna trabalhoso. É aí que frameworks como Express.js entram, abstraindo essas complexidades.

Quando evoluir: Considere migrar para Express.js quando:
- Precisar de middlewares reutilizáveis (autenticação, logging, CORS)
- O número de rotas crescer significativamente
- Precisar de parsing automático de corpo (JSON, URL-encoded)
- Quiser integração mais fácil com templates ou WebSockets

O módulo http nativo é a base sobre a qual todo o ecossistema Node.js é construído. Dominá-lo proporciona uma compreensão profunda que será valiosa mesmo quando você estiver usando frameworks mais avançados.


Referências