Fundamentos do Node.js
Construindo uma API REST Simples com o Módulo 'http'
Aprenda sobre construindo uma api rest simples com o módulo 'http'
Construindo uma API REST Simples com o Módulo 'http'
Olá, futuro desenvolvedor Node.js! 👋 Nesta aula prática, vamos mergulhar no coração do Node.js para entender como construir uma API RESTful do zero usando o módulo http nativo. Este é um passo fundamental para compreender como as aplicações web funcionam por baixo dos panos, antes de explorarmos frameworks mais avançados.
1. Introdução: O Que É Uma API REST e o Papel do Módulo http?
Uma API REST (Representational State Transfer) é um conjunto de princípios arquitetônicos para a construção de serviços web. Ela permite que diferentes sistemas se comuniquem usando requisições HTTP padrão (GET, POST, PUT, DELETE, etc.) para manipular recursos (dados).
O módulo http do Node.js é a fundação para qualquer aplicação de rede baseada em HTTP. Ele nos permite criar servidores web que podem ouvir requisições, processá-las e enviar respostas. Embora frameworks como Express.js facilitem muito essa tarefa, entender o http nativo nos dá uma base sólida e nos ajuda a depurar e otimizar nossas aplicações.
Nesta aula, você aprenderá a:
- Criar um servidor HTTP básico.
- Lidar com diferentes tipos de requisição (GET, POST).
- Extrair informações da requisição (URL, método, corpo).
- Construir respostas HTTP (status, cabeçalhos, corpo).
- Implementar um CRUD (Create, Read, Update, Delete) simples para um recurso.
Vamos começar! 🚀
2. Explicação Detalhada com Exemplos
2.1. O Módulo http: Criando Seu Primeiro Servidor
O módulo http fornece funcionalidades para criar tanto clientes quanto servidores HTTP. Para criar um servidor, usamos o método http.createServer(), que recebe uma função de callback. Esta função será executada toda vez que uma requisição HTTP chegar ao nosso servidor.
A função de callback recebe dois objetos essenciais:
request(oureq): Contém todas as informações sobre a requisição HTTP recebida (URL, método, cabeçalhos, corpo, etc.).response(oures): Usado para enviar a resposta HTTP de volta ao cliente (status, cabeçalhos, corpo).
Após criar o servidor, precisamos fazê-lo "escutar" em uma porta específica usando server.listen().
// server.js
const http = require('http'); // Importa o módulo HTTP
// Cria o servidor
const server = http.createServer((req, res) => {
// Define o código de status HTTP 200 (OK)
res.statusCode = 200;
// Define o cabeçalho Content-Type como texto puro
res.setHeader('Content-Type', 'text/plain');
// Envia a resposta "Olá, Mundo!" e finaliza a requisição
res.end('Olá, Mundo!\n');
});
// A porta onde o servidor irá escutar
const port = 3000;
// O host (endereço IP) onde o servidor estará disponível
const hostname = '127.0.0.1'; // localhost
// Inicia o servidor e o faz escutar na porta e hostname especificados
server.listen(port, hostname, () => {
console.log(`Servidor rodando em http://${hostname}:${port}/`);
console.log('Pressione Ctrl+C para parar o servidor.');
});Para rodar este código, salve-o como server.js e execute no seu terminal:
node server.jsAbra seu navegador e acesse http://localhost:3000. Você verá "Olá, Mundo!". 🎉
2.2. Entendendo os Objetos req e res
Objeto request (req)
O objeto req é uma instância de http.IncomingMessage. Ele nos dá acesso a:
req.url: A URL da requisição (ex:/users,/products/1).req.method: O método HTTP da requisição (ex:GET,POST,PUT,DELETE).req.headers: Um objeto contendo os cabeçalhos da requisição.
Objeto response (res)
O objeto res é uma instância de http.ServerResponse. Ele nos permite controlar a resposta:
res.statusCode: Define o código de status HTTP (ex:200para OK,404para Não Encontrado,500para Erro Interno).res.setHeader(name, value): Define um cabeçalho HTTP. É crucial para indicar o tipo de conteúdo (ex:Content-Type: application/json).res.write(chunk): Escreve dados no corpo da resposta. Pode ser chamado múltiplas vezes.res.end(data): Finaliza a resposta. Opcionalmente, pode enviar a última parte dos dados.
2.3. Roteamento Básico: Lidando com Diferentes URLs e Métodos
Uma API REST precisa responder de forma diferente dependendo da URL e do método HTTP. Podemos usar if/else if ou um switch para implementar um roteamento simples.
Vamos criar uma API para gerenciar uma lista de "tarefas" (tasks).
// api-tasks.js
const http = require('http');
let tasks = [
{ id: 1, title: 'Aprender Node.js', completed: false },
{ id: 2, title: 'Construir uma API REST', completed: true },
{ id: 3, title: 'Fazer exercícios', completed: false }
];
let nextId = 4; // Para simular IDs únicos
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json'); // Sempre responder com JSON
// Roteamento
if (req.url === '/tasks' && req.method === 'GET') {
// GET /tasks: Listar todas as tarefas
res.statusCode = 200;
res.end(JSON.stringify(tasks));
} else if (req.url === '/tasks' && req.method === 'POST') {
// POST /tasks: Criar uma nova tarefa
let body = '';
req.on('data', chunk => {
body += chunk.toString(); // Converte Buffer para string
});
req.on('end', () => {
try {
const newTask = JSON.parse(body);
newTask.id = nextId++;
newTask.completed = newTask.completed || false; // Garante que completed seja false se não informado
tasks.push(newTask);
res.statusCode = 201; // Created
res.end(JSON.stringify(newTask));
} catch (error) {
res.statusCode = 400; // Bad Request
res.end(JSON.stringify({ message: 'Requisição inválida. Verifique o JSON.' }));
}
});
} else if (req.url.startsWith('/tasks/') && req.method === 'GET') {
// GET /tasks/:id: Obter uma tarefa específica
const id = parseInt(req.url.split('/')[2]);
const task = tasks.find(t => t.id === id);
if (task) {
res.statusCode = 200;
res.end(JSON.stringify(task));
} else {
res.statusCode = 404; // Not Found
res.end(JSON.stringify({ message: 'Tarefa não encontrada.' }));
}
} else if (req.url.startsWith('/tasks/') && req.method === 'PUT') {
// PUT /tasks/:id: Atualizar uma tarefa específica
const id = parseInt(req.url.split('/')[2]);
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
try {
const updatedTaskData = JSON.parse(body);
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex !== -1) {
tasks[taskIndex] = { ...tasks[taskIndex], ...updatedTaskData, id }; // Mantém o ID original
res.statusCode = 200;
res.end(JSON.stringify(tasks[taskIndex]));
} else {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'Tarefa não encontrada.' }));
}
} catch (error) {
res.statusCode = 400;
res.end(JSON.stringify({ message: 'Requisição inválida. Verifique o JSON.' }));
}
});
} else if (req.url.startsWith('/tasks/') && req.method === 'DELETE') {
// DELETE /tasks/:id: Excluir uma tarefa específica
const id = parseInt(req.url.split('/')[2]);
const initialLength = tasks.length;
tasks = tasks.filter(t => t.id !== id);
if (tasks.length < initialLength) {
res.statusCode = 204; // No Content (sucesso, mas sem corpo na resposta)
res.end();
} else {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'Tarefa não encontrada.' }));
}
} else {
// Rota não encontrada
res.statusCode = 404;
res.end(JSON.stringify({ message: 'Rota não encontrada.' }));
}
});
const port = 3000;
const hostname = '127.0.0.1';
server.listen(port, hostname, () => {
console.log(`API de Tarefas rodando em http://${hostname}:${port}`);
});2.4. Lidando com Dados JSON no Corpo da Requisição (POST/PUT)
Quando um cliente envia dados no corpo da requisição (com POST ou PUT), esses dados chegam em "chunks" (pedaços) através do evento 'data' do objeto req. Precisamos coletar todos esses chunks e, quando a requisição terminar (evento 'end'), concatená-los e, se for JSON, fazer o JSON.parse().
Observe o exemplo acima na rota POST /tasks e PUT /tasks/:id.
req.on('data', chunk => { body += chunk.toString(); });: Coleta os dados.req.on('end', () => { ... JSON.parse(body) ... });: Processa os dados quando a requisição termina.
Importante: Sempre envolva o JSON.parse() em um bloco try-catch para lidar com JSON malformado, retornando um status 400 Bad Request.
3. Código de Exemplo Oficial (Baseado na Documentação)
O exemplo anterior já segue as melhores práticas e o estilo da documentação oficial do Node.js para o módulo http. A documentação foca em exemplos modulares e específicos para cada funcionalidade (como createServer, setHeader, on('data')), e o código acima integra esses conceitos para construir uma API funcional.
4. Exercícios/Desafios: Construa Sua Própria API de Produtos! 🛠️
Agora é a sua vez de colocar a mão na massa! Usando o código da API de tarefas como base, você irá construir uma API similar para gerenciar uma lista de produtos.
Tarefas:
- Crie um novo arquivo chamado
api-products.js. - Inicialize uma lista de produtos (ex:
let products = []) e umnextIdpara gerar IDs. - Implemente a rota
GET /products:- Deve retornar todos os produtos.
- Status:
200 OK.
- Implemente a rota
GET /products/:id:- Deve retornar um produto específico pelo ID.
- Se o produto não for encontrado, retorne
404 Not Foundcom uma mensagem JSON.
- Implemente a rota
POST /products:- Deve criar um novo produto.
- O corpo da requisição deve ser um JSON contendo, no mínimo,
nameeprice. - Atribua um
idúnico ao novo produto. - Retorne o produto criado.
- Status:
201 Created. - Lide com erros de JSON inválido (
400 Bad Request).
- Implemente a rota
PUT /products/:id:- Deve atualizar um produto existente pelo ID.
- O corpo da requisição deve ser um JSON com os campos a serem atualizados (ex:
name,price). - Se o produto não for encontrado, retorne
404 Not Found. - Retorne o produto atualizado.
- Status:
200 OK. - Lide com erros de JSON inválido (
400 Bad Request).
- Implemente a rota
DELETE /products/:id:- Deve excluir um produto pelo ID.
- Se o produto for excluído com sucesso, retorne
204 No Content(sem corpo na resposta). - Se o produto não for encontrado, retorne
404 Not Found.
- Adicione um console.log para indicar que o servidor está rodando e em qual porta.
- Teste sua API usando ferramentas como
curlou Postman/Insomnia.
Dicas para Testar com curl:
GET todos os produtos:
curl http://localhost:3000/productsGET um produto específico (substitua 1 pelo ID):
curl http://localhost:3000/products/1POST para criar um produto:
curl -X POST -H "Content-Type: application/json" -d '{"name":"Notebook Gamer", "price":5000}' http://localhost:3000/productsPUT para atualizar um produto (substitua 1 pelo ID):
curl -X PUT -H "Content-Type: application/json" -d '{"price":4800}' http://localhost:3000/products/1DELETE para remover um produto (substitua 1 pelo ID):
curl -X DELETE http://localhost:3000/products/15. Resumo e Próximos Passos
Parabéns! 🎉 Você construiu uma API REST funcional usando apenas o módulo http nativo do Node.js. Você aprendeu a:
- Criar um servidor HTTP.
- Processar requisições e construir respostas.
- Implementar roteamento básico.
- Lidar com dados JSON no corpo da requisição.
Esta é uma base poderosa, mas como você deve ter percebido, o roteamento e o tratamento de erros podem se tornar complexos rapidamente em aplicações maiores. É exatamente por isso que frameworks como Express.js, Koa.js e Fastify foram criados! Eles abstraem grande parte dessa complexidade, oferecendo ferramentas para:
- Roteamento mais elegante e poderoso.
- Middleware para pré-processamento de requisições.
- Tratamento de erros centralizado.
- Análise de corpo da requisição (JSON, formulários) de forma automática.
No próximo módulo, exploraremos o Express.js para ver como ele simplifica o desenvolvimento de APIs REST, construindo sobre os fundamentos que você aprendeu hoje. Mantenha o bom trabalho! 💪