Fundamentos do Node.js

0/23 aulas0%
pratica

Implementando Roteamento Básico na API (Sem Frameworks)

Aprenda sobre implementando roteamento básico na api (sem frameworks)

40 min
Aula 3 de 5

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: 200 para sucesso, 404 para 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:

  1. Salve o código acima como server.js.
  2. Abra seu terminal na pasta onde salvou o arquivo.
  3. Execute: node server.js
  4. Use ferramentas como Postman, Insomnia ou curl para testar as rotas.

Exemplo de curl:

  • GET /: curl http://localhost:3000/
  • GET /users: curl http://localhost:3000/users
  • POST /users: curl -X POST -H "Content-Type: application/json" -d '{"name":"Charlie", "email":"charlie@example.com"}' http://localhost:3000/users
  • GET /users/1: curl http://localhost:3000/users/1
  • PUT /users/1: curl -X PUT -H "Content-Type: application/json" -d '{"name":"Alice Updated"}' http://localhost:3000/users/1
  • DELETE /users/1: curl -X DELETE http://localhost:3000/users/1
  • GET /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/:id para remover um produto pelo seu ID.
    • Retorne um status 200 OK e uma mensagem de sucesso, ou 404 Not Found se o produto não existir.
  • 2. Implementar Rota PUT para Produtos:

    • Crie uma rota PUT /products/:id para 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, ou 404 Not Found se o produto não existir.
  • 3. Adicionar Validação Básica (POST /users):

    • Na rota POST /users, adicione uma verificação para garantir que o newUser tenha as propriedades name e email.
    • Se alguma propriedade estiver faltando, retorne um status 400 Bad Request e uma mensagem de erro apropriada.

Desafios Extras (Opcional):

  • 4. Roteamento de Rotas Aninhadas (Exemplo: /users/:userId/orders):

    • Crie uma rota GET /users/:userId/orders que 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 Found se o usuário não existir.
  • 5. Refatorar o Roteamento para Funções Separadas:

    • Para cada grupo de rotas (ex: /users, /products), crie uma função separada que receba req e res e 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);
      // }
  • 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).

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 http para criar um servidor básico.
  • A importância de req.url e req.method para o roteamento.
  • Como enviar respostas usando res.writeHead() e res.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 Joi ou Yup são ótimas para isso).

Continue praticando e explorando! Você está no caminho certo para dominar o Node.js. 💪

© 2025 Escola All Dev. Todos os direitos reservados.

Implementando Roteamento Básico na API (Sem Frameworks) - Fundamentos do Node.js | escola.all.dev.br