Fundamentos do Node.js
Implementando Roteamento Básico na API (Sem Frameworks)
Aprenda sobre implementando roteamento básico na api (sem frameworks)
Implementando Roteamento Básico na API (Sem Frameworks)
Olá, futuros desenvolvedores Node.js! 👋 Nesta aula prática, vamos mergulhar no coração de como as APIs funcionam, implementando nosso próprio sistema de roteamento sem o auxílio de frameworks. Isso pode parecer um desafio, mas é uma maneira fantástica de entender os fundamentos que frameworks como Express.js abstraem para nós. Ao final, você terá uma compreensão sólida de como o Node.js lida com requisições HTTP e como direcioná-las para a lógica correta.
1. Introdução ao Roteamento Sem Frameworks 🚀
Quando você acessa uma URL em um navegador ou faz uma requisição HTTP para uma API, você está, na verdade, pedindo ao servidor para executar uma ação específica e retornar uma resposta. O roteamento é o processo de mapear uma requisição (baseada na URL e no método HTTP) para uma função de tratamento de requisição (também conhecida como handler).
Em Node.js, o módulo http é a base para criar servidores web. Ele nos fornece o objeto request (requisição) e o objeto response (resposta). Para implementar roteamento sem frameworks, usaremos as propriedades url e method do objeto request para decidir qual lógica de negócios deve ser executada.
Por que aprender isso? 🤔
- Fundamentos Sólidos: Entender como o roteamento funciona "por baixo dos panos" o tornará um desenvolvedor Node.js mais completo e capaz de depurar e otimizar aplicações.
- Controle Total: Você terá controle granular sobre cada aspecto do tratamento da requisição.
- Base para Frameworks: A lógica que construiremos aqui é a mesma lógica que frameworks como Express.js utilizam internamente, apenas de forma mais abstrata e organizada.
Vamos colocar a mão na massa!
2. Explicação Detalhada com Exemplos 🛠️
O módulo http é o ponto de partida. Ele nos permite criar um servidor que escuta em uma porta específica. Quando uma requisição chega, ele dispara um evento, passando os objetos req (request) e res (response) para a função de callback que definimos.
const http = require('http');
const server = http.createServer((req, res) => {
// Aqui é onde toda a magia do roteamento acontece!
// req: objeto de requisição, contém informações sobre a requisição do cliente.
// res: objeto de resposta, usado para enviar dados de volta ao cliente.
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Servidor rodando na porta ${PORT}`);
console.log(`Acesse: http://localhost:${PORT}`);
});O Objeto req (Request)
As propriedades mais importantes para o roteamento são:
req.url: A URL que o cliente solicitou (ex:/users,/products/1).req.method: O método HTTP da requisição (ex:GET,POST,PUT,DELETE).
Com essas duas informações, podemos construir nossa lógica de roteamento.
O Objeto res (Response)
Usamos o objeto res para enviar a resposta de volta ao cliente:
res.writeHead(statusCode, headers): Define o código de status HTTP (ex:200para sucesso,404para não encontrado) e os cabeçalhos da resposta (ex:Content-Type: application/json).res.end(data): Finaliza a resposta e envia o corpo dos dados (se houver) de volta ao cliente.
Lógica de Roteamento Simples
Vamos usar uma estrutura if/else if ou switch para verificar req.url e req.method.
Exemplo:
const http = require('http');
const server = http.createServer((req, res) => {
// Define o cabeçalho padrão para JSON
res.setHeader('Content-Type', 'application/json');
// Rota para a página inicial (GET /)
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200);
res.end(JSON.stringify({ message: 'Bem-vindo à nossa API básica!' }));
}
// Rota para obter usuários (GET /users)
else if (req.url === '/users' && req.method === 'GET') {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
res.writeHead(200);
res.end(JSON.stringify(users));
}
// Rota para criar um novo usuário (POST /users)
else if (req.url === '/users' && req.method === 'POST') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString(); // Converte o buffer para string
});
req.on('end', () => {
const newUser = JSON.parse(body); // Assume que o corpo é JSON
// Aqui você adicionaria o usuário a um banco de dados, etc.
console.log('Novo usuário recebido:', newUser);
res.writeHead(201); // 201 Created
res.end(JSON.stringify({ message: 'Usuário criado com sucesso!', user: newUser }));
});
}
// Rota para obter produtos (GET /products)
else if (req.url === '/products' && req.method === 'GET') {
const products = [
{ id: 101, name: 'Laptop' },
{ id: 102, name: 'Mouse' },
];
res.writeHead(200);
res.end(JSON.stringify(products));
}
// Rota não encontrada (404)
else {
res.writeHead(404);
res.end(JSON.stringify({ message: 'Rota não encontrada' }));
}
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Servidor rodando na porta ${PORT}`);
console.log(`Acesse: http://localhost:${PORT}`);
});Este exemplo demonstra como podemos verificar o url e o method para direcionar a requisição. Para requisições POST, PUT, etc., precisamos ler o corpo da requisição, que é um stream de dados. O evento data é disparado quando há pedaços de dados, e o evento end é disparado quando todos os dados foram recebidos.
3. Código de Exemplo Completo (Baseado na Documentação Oficial) 📚
Embora não exista um "código oficial" para roteamento sem frameworks (pois é uma pattern que você implementa usando as APIs do http), o exemplo a seguir é construído estritamente com base no uso do módulo http conforme documentado. Ele encapsula os conceitos que discutimos.
Crie um arquivo chamado server.js:
// server.js
const http = require('http');
const url = require('url'); // Módulo para parsear URLs (útil para query params)
// Simulação de banco de dados
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
let products = [
{ id: 101, name: 'Laptop', price: 1200 },
{ id: 102, name: 'Mouse', price: 25 },
];
const server = http.createServer((req, res) => {
// Define cabeçalhos CORS para permitir requisições de qualquer origem (em ambiente de desenvolvimento)
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Lidar com requisições OPTIONS (preflight requests)
if (req.method === 'OPTIONS') {
res.writeHead(204); // No Content
res.end();
return;
}
// Define o cabeçalho padrão para JSON
res.setHeader('Content-Type', 'application/json');
// Parseia a URL para obter o pathname e os query parameters
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
const query = parsedUrl.query;
// --- Roteamento ---
// Rota: /
if (pathname === '/' && req.method === 'GET') {
res.writeHead(200);
res.end(JSON.stringify({ message: 'Bem-vindo à API Node.js sem frameworks!' }));
}
// Rota: /users
else if (pathname === '/users') {
if (req.method === 'GET') {
res.writeHead(200);
res.end(JSON.stringify(users));
} else if (req.method === 'POST') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const newUser = JSON.parse(body);
newUser.id = users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1;
users.push(newUser);
res.writeHead(201); // Created
res.end(JSON.stringify({ message: 'Usuário criado com sucesso!', user: newUser }));
} catch (error) {
res.writeHead(400); // Bad Request
res.end(JSON.stringify({ message: 'Dados inválidos no corpo da requisição.' }));
}
});
} else {
res.writeHead(405); // Method Not Allowed
res.end(JSON.stringify({ message: 'Método não permitido para /users' }));
}
}
// Rota: /users/:id
else if (pathname.startsWith('/users/')) {
const userId = parseInt(pathname.split('/')[2]); // Extrai o ID da URL
const user = users.find(u => u.id === userId);
if (!user) {
res.writeHead(404);
res.end(JSON.stringify({ message: 'Usuário não encontrado.' }));
return;
}
if (req.method === 'GET') {
res.writeHead(200);
res.end(JSON.stringify(user));
} else if (req.method === 'PUT') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const updatedData = JSON.parse(body);
Object.assign(user, updatedData); // Atualiza o objeto do usuário
res.writeHead(200);
res.end(JSON.stringify({ message: 'Usuário atualizado com sucesso!', user: user }));
} catch (error) {
res.writeHead(400);
res.end(JSON.stringify({ message: 'Dados inválidos no corpo da requisição.' }));
}
});
} else if (req.method === 'DELETE') {
users = users.filter(u => u.id !== userId);
res.writeHead(200);
res.end(JSON.stringify({ message: 'Usuário deletado com sucesso!' }));
} else {
res.writeHead(405); // Method Not Allowed
res.end(JSON.stringify({ message: 'Método não permitido para /users/:id' }));
}
}
// Rota: /products
else if (pathname === '/products') {
if (req.method === 'GET') {
// Exemplo de como usar query parameters: /products?name=Laptop
let filteredProducts = products;
if (query.name) {
filteredProducts = products.filter(p => p.name.toLowerCase().includes(query.name.toLowerCase()));
}
res.writeHead(200);
res.end(JSON.stringify(filteredProducts));
} else {
res.writeHead(405); // Method Not Allowed
res.end(JSON.stringify({ message: 'Método não permitido para /products' }));
}
}
// Rota não encontrada (404)
else {
res.writeHead(404);
res.end(JSON.stringify({ message: 'Rota não encontrada.' }));
}
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`🚀 Servidor rodando em http://localhost:${PORT}`);
console.log('Testar com:');
console.log('GET / ➡️ http://localhost:3000/');
console.log('GET /users ➡️ http://localhost:3000/users');
console.log('POST /users ➡️ http://localhost:3000/users (com JSON no body)');
console.log('GET /users/1 ➡️ http://localhost:3000/users/1');
console.log('PUT /users/1 ➡️ http://localhost:3000/users/1 (com JSON no body)');
console.log('DELETE /users/1 ➡️ http://localhost:3000/users/1');
console.log('GET /products ➡️ http://localhost:3000/products');
console.log('GET /products?name=laptop ➡️ http://localhost:3000/products?name=laptop');
});Para executar este código:
- Salve o código acima como
server.js. - Abra seu terminal na pasta onde salvou o arquivo.
- Execute:
node server.js - Use ferramentas como Postman, Insomnia ou
curlpara testar as rotas.
Exemplo de curl:
GET /:curl http://localhost:3000/GET /users:curl http://localhost:3000/usersPOST /users:curl -X POST -H "Content-Type: application/json" -d '{"name":"Charlie", "email":"charlie@example.com"}' http://localhost:3000/usersGET /users/1:curl http://localhost:3000/users/1PUT /users/1:curl -X PUT -H "Content-Type: application/json" -d '{"name":"Alice Updated"}' http://localhost:3000/users/1DELETE /users/1:curl -X DELETE http://localhost:3000/users/1GET /products?name=mouse:curl http://localhost:3000/products?name=mouse
4. Exercícios e Desafios 🧠
Agora é a sua vez de praticar! Modifique o arquivo server.js que você criou para implementar as seguintes funcionalidades:
Tarefas Essenciais:
-
1. Implementar Rota DELETE para Produtos:
- Crie uma rota
DELETE /products/:idpara remover um produto pelo seu ID. - Retorne um status
200 OKe uma mensagem de sucesso, ou404 Not Foundse o produto não existir.
- Crie uma rota
-
2. Implementar Rota PUT para Produtos:
- Crie uma rota
PUT /products/:idpara atualizar os dados de um produto existente. - O corpo da requisição deve conter os dados a serem atualizados (ex:
{"name": "Teclado Mecânico", "price": 80}). - Retorne o produto atualizado com status
200 OK, ou404 Not Foundse o produto não existir.
- Crie uma rota
-
3. Adicionar Validação Básica (POST /users):
- Na rota
POST /users, adicione uma verificação para garantir que onewUsertenha as propriedadesnameeemail. - Se alguma propriedade estiver faltando, retorne um status
400 Bad Requeste uma mensagem de erro apropriada.
- Na rota
Desafios Extras (Opcional):
-
4. Roteamento de Rotas Aninhadas (Exemplo: /users/:userId/orders):
- Crie uma rota
GET /users/:userId/ordersque simule a busca por pedidos de um usuário específico. - Retorne um array de pedidos (mesmo que seja um array vazio ou hardcoded) ou
404 Not Foundse o usuário não existir.
- Crie uma rota
-
5. Refatorar o Roteamento para Funções Separadas:
- Para cada grupo de rotas (ex:
/users,/products), crie uma função separada que recebareqerese contenha a lógica de roteamento para aquele grupo. - Isso ajuda a organizar o código à medida que a API cresce.
- Exemplo:
function handleUserRoutes(req, res, pathname, method) { // Lógica para /users e /users/:id } // No createServer: // if (pathname.startsWith('/users')) { // handleUserRoutes(req, res, pathname, method); // }
- Para cada grupo de rotas (ex:
-
6. Adicionar Tratamento de Erros Global:
- Implemente um mecanismo básico para capturar erros que ocorrem dentro dos seus handlers de rota e enviar uma resposta de erro consistente (ex:
500 Internal Server Error).
- Implemente um mecanismo básico para capturar erros que ocorrem dentro dos seus handlers de rota e enviar uma resposta de erro consistente (ex:
5. Resumo e Próximos Passos 🎯
Parabéns! 🎉 Você acabou de construir uma API Node.js com roteamento manual. Isso é uma conquista e tanto!
Nesta aula, você aprendeu:
- Como usar o módulo
httppara criar um servidor básico. - A importância de
req.urlereq.methodpara o roteamento. - Como enviar respostas usando
res.writeHead()eres.end(). - Como lidar com diferentes métodos HTTP (GET, POST, PUT, DELETE) e extrair dados do corpo da requisição.
- Como implementar roteamento dinâmico (com parâmetros na URL) e
query parameters.
Embora o roteamento manual seja excelente para entender os fundamentos, ele pode se tornar complexo e repetitivo em aplicações maiores. É exatamente por isso que frameworks como Express.js são tão populares! Eles fornecem uma camada de abstração elegante para roteamento, middleware, tratamento de erros e muito mais, tornando o desenvolvimento de APIs muito mais rápido e organizado.
Próximos Passos:
- Explore o Express.js: Na próxima aula, vamos introduzir o Express.js e você verá como ele simplifica drasticamente o que acabamos de fazer manualmente.
- Aprofunde-se em HTTP: Estude mais sobre códigos de status HTTP, cabeçalhos e métodos para construir APIs mais robustas.
- Validação de Dados: Comece a pensar em como você validaria dados de entrada de forma mais robusta em cenários reais (bibliotecas como
JoiouYupsão ótimas para isso).
Continue praticando e explorando! Você está no caminho certo para dominar o Node.js. 💪