<h2>Introdução ao HTTP Nativo em Node.js</h2>
<p>Node.js oferece o módulo <code>http</code> nativo, permitindo criar servidores web poderosos sem dependências externas. Muitos desenvolvedores pulam direto para frameworks como Express, mas entender como o HTTP funciona no nível mais baixo é essencial para dominar programação backend. Nesta aula, exploraremos desde a criação de um servidor básico até o tratamento profissional de requisições, sem usar nenhum framework.</p>
<h2>Criando Seu Primeiro Servidor</h2>
<h3>Servidor Mínimo Funcional</h3>
<p>O Node.js inclui o módulo <code>http</code> por padrão. Para criar um servidor, precisamos apenas importá-lo e definir um handler para requisições:</p>
<pre><code class="language-javascript">const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Olá, Mundo!');
});
server.listen(3000, () => {
console.log('Servidor rodando em http://localhost:3000');
});</code></pre>
<p>Esse código cria um servidor que responde a qualquer requisição com "Olá, Mundo!". O callback recebe dois parâmetros: <code>req</code> (requisição) e <code>res</code> (resposta). Salve como <code>servidor.js</code> e execute com <code>node servidor.js</code>. Acesse <code>http://localhost:3000</code> no navegador para ver funcionando.</p>
<h3>Entendendo Request e Response</h3>
<p>O objeto <code>req</code> contém informações sobre a requisição: método HTTP, caminho, headers, etc. O objeto <code>res</code> é usado para enviar dados ao cliente. A sequência típica é: <code>writeHead()</code> para definir status e headers, depois <code>write()</code> para enviar corpo, e <code>end()</code> para finalizar. Sempre termine com <code>end()</code>, caso contrário o cliente ficará aguardando.</p>
<h2>Roteamento e Métodos HTTP</h2>
<h3>Implementando Rotas Básicas</h3>
<p>A maioria dos servidores precisa diferenciar requisições por caminho (rota) e método HTTP. Vamos criar um roteador simples:</p>
<pre><code class="language-javascript">const http = require('http');
const server = http.createServer((req, res) => {
const { method, url } = req;
if (url === '/' && method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ mensagem: 'Home' }));
}
else if (url === '/usuarios' && method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify([{ id: 1, nome: 'João' }]));
}
else if (url === '/usuarios' && method === 'POST') {
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 2, nome: 'Maria' }));
}
else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ erro: 'Rota não encontrada' }));
}
});
server.listen(3000, () => {
console.log('Servidor rodando em http://localhost:3000');
});</code></pre>
<p>Aqui diferenciamos por <code>method</code> e <code>url</code>. GET em <code>/usuarios</code> retorna uma lista, POST cria um novo usuário. Rotas não encontradas retornam 404. Este é o padrão base de qualquer servidor HTTP.</p>
<h2>Processando Corpo de Requisições</h2>
<h3>Recebendo e Parsing de JSON</h3>
<p>Requisições POST e PUT frequentemente contêm dados no corpo. Precisamos ler o stream e fazer parsing:</p>
<pre><code class="language-javascript">const http = require('http');
function parseBody(req) {
return new Promise((resolve, reject) => {
let data = '';
req.on('data', chunk => {
data += chunk.toString();
});
req.on('end', () => {
try {
resolve(JSON.parse(data));
} catch {
reject(new Error('JSON inválido'));
}
});
req.on('error', reject);
});
}
const server = http.createServer(async (req, res) => {
try {
if (req.url === '/usuarios' && req.method === 'POST') {
const body = await parseBody(req);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
id: Math.random(),
nome: body.nome,
criado: new Date().toISOString()
}));
} else {
res.writeHead(404);
res.end();
}
} catch (err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ erro: err.message }));
}
});
server.listen(3000);</code></pre>
<p>O módulo <code>http</code> trabalha com streams. Quando há dados no corpo, o evento <code>data</code> é disparado com chunks. Quando termina, <code>end</code> é disparado. Convertemos tudo a string, fazemos parse JSON e retornamos uma resposta apropriada. Use o método <code>Promise</code> para trabalhar com callbacks assincronamente.</p>
<h3>Validação e Segurança Básica</h3>
<p>Sempre valide dados recebidos. Estabeleça limite de tamanho para proteger contra ataques:</p>
<pre><code class="language-javascript">function parseBody(req, maxSize = 1e6) {
return new Promise((resolve, reject) => {
let data = '';
let size = 0;
req.on('data', chunk => {
size += chunk.length;
if (size > maxSize) {
reject(new Error('Corpo muito grande'));
}
data += chunk.toString();
});
req.on('end', () => {
try {
resolve(JSON.parse(data));
} catch {
reject(new Error('JSON inválido'));
}
});
});
}</code></pre>
<p>Defina um máximo de bytes (aqui 1MB). Se exceder, rejeite imediatamente. Isso evita consumo excessivo de memória.</p>
<h2>Exemplo Prático: API REST Simples</h2>
<h3>Servidor Completo com CRUD</h3>
<p>Vamos unir tudo em uma API REST básica que gerencia usuários em memória:</p>
<pre><code class="language-javascript">const http = require('http');
let usuarios = [{ id: 1, nome: 'João' }];
function parseBody(req) {
return new Promise((resolve, reject) => {
let data = '';
req.on('data', chunk => { data += chunk; });
req.on('end', () => {
try {
resolve(JSON.parse(data || '{}'));
} catch {
reject(new Error('JSON inválido'));
}
});
});
}
function respond(res, status, data) {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
}
const server = http.createServer(async (req, res) => {
const [, rota, id] = req.url.split('/');
try {
if (rota === 'usuarios' && req.method === 'GET') {
respond(res, 200, usuarios);
}
else if (rota === 'usuarios' && req.method === 'POST') {
const { nome } = await parseBody(req);
const novoUsuario = { id: Date.now(), nome };
usuarios.push(novoUsuario);
respond(res, 201, novoUsuario);
}
else if (rota === 'usuarios' && req.method === 'DELETE') {
usuarios = usuarios.filter(u => u.id != id);
respond(res, 200, { ok: true });
}
else {
respond(res, 404, { erro: 'Não encontrado' });
}
} catch (err) {
respond(res, 400, { erro: err.message });
}
});
server.listen(3000, () => console.log('Servidor em 3000'));</code></pre>
<p>Este exemplo mostra GET (listar), POST (criar), DELETE (remover). Os dados ficam em memória, então são perdidos ao reiniciar. Para produção, use um banco de dados real.</p>
<h2>Conclusão</h2>
<p>Nesta aula, aprendemos três conceitos fundamentais: <strong>primeiro</strong>, como criar servidores HTTP nativos sem frameworks, entendendo request/response; <strong>segundo</strong>, implementar roteamento e processamento de diferentes métodos HTTP de forma manual; <strong>terceiro</strong>, trabalhar com streams e dados assincronamente, essencial para qualquer servidor profissional. Dominar HTTP nativo transforma você em um desenvolvedor mais completo, capaz de entender o que frameworks abstraem. Use esse conhecimento para escolher entre soluções prontas ou personalizadas conforme necessário.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://nodejs.org/api/http.html" target="_blank" rel="noopener noreferrer">Node.js HTTP Documentation</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP" target="_blank" rel="noopener noreferrer">MDN Web Docs - HTTP Protocol</a></li>
<li><a href="https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/" target="_blank" rel="noopener noreferrer">Node.js Official Guide</a></li>
<li><a href="https://restfulapi.net/" target="_blank" rel="noopener noreferrer">REST API Best Practices - Restfulapi.net</a></li>
<li><a href="https://javascript.info/fetch" target="_blank" rel="noopener noreferrer">JavaScript.info - Fetch API and HTTP</a></li>
</ul>