Pular para conteúdo

Solução 05 - Implementação de APIs ⚙️

Navegação

← Exercício 05 | Próxima Solução →

🟢 Respostas Fáceis

1. Responsabilidade do Controller

Resposta 1

Principal função do Controller:

O Controller é o **orquestrador** da requisição HTTP. Suas responsabilidades:

- ✅ **Receber** requisições HTTP
- ✅ **Validar** parâmetros de entrada
- ✅ **Chamar** serviços de negócio
- ✅ **Formatar** e retornar respostas
- ❌ **NÃO** contém regras de negócio
- ❌ **NÃO** acessa banco diretamente

**🏗️ Arquitetura em Camadas:**
```mermaid
graph TD
    A[HTTP Request] --> B[Controller]
    B --> C[Service/Use Case]
    C --> D[Repository]
    D --> E[Database]

    B --> F[Response Formatter]
    F --> G[HTTP Response]

    style B fill:#e3f2fd
    style C fill:#fff3e0
    style D fill:#f3e5f5
```

2. Handler no Contexto de Rotas

Resposta 2

Handler em Backend:

Um **Handler** é a **função específica** que processa uma rota:

```javascript
// Handler = função que "manipula" a requisição
app.get('/usuarios', usuarioHandler);  // ← Handler

function usuarioHandler(req, res) {    // ← Esta é a função Handler
    // Lógica de processamento
    res.json({ usuarios: [...] });
}
```

**🔄 Fluxo:**
1. **Rota** define o caminho (`/usuarios`)
2. **Handler** define o que fazer quando alguém acessar
3. **Middleware** pode ser executado antes do Handler

🟡 Respostas Médias

3. Path Params vs Query Params

Resposta 3

Diferenciação com Exemplos:

| Tipo | Exemplo URI | Uso | Localização no Código |
|------|-------------|-----|---------------------|
| **Path Params** | `/usuarios/123/pedidos/456` | **Identificar recursos** específicos | `req.params.id` |
| **Query Params** | `/usuarios?page=2&limit=10` | **Filtrar/configurar** busca | `req.query.page` |

**🎯 Exemplos Práticos:**
```http
# Path Params - Identificação obrigatória
GET /clientes/789                    # cliente específico
DELETE /produtos/456                 # produto específico
PUT /pedidos/123/status             # status de pedido específico

# Query Params - Filtros opcionais
GET /produtos?categoria=eletronicos&preco_max=1000
GET /pedidos?status=entregue&data_inicio=2024-01-01
GET /clientes?cidade=sao-paulo&ativo=true
```

**📝 Implementação em Express.js:**
```javascript
// Path Params
app.get('/usuarios/:id/pedidos/:pedidoId', (req, res) => {
    const userId = req.params.id;           // 123
    const pedidoId = req.params.pedidoId;   // 456
});

// Query Params
app.get('/produtos', (req, res) => {
    const categoria = req.query.categoria;   // "eletronicos"
    const precoMax = req.query.preco_max;    // "1000"
    const page = req.query.page || 1;        // padrão: 1
});
```

4. Status Code Explícito no Controller

Resposta 4

Por que sempre definir Status Code:

**❌ Problemas sem Status Code explícito:**
- **Cliente confuso** sobre resultado da operação
- **Caching inadequado** pelos proxies/CDNs
- **Logs imprecisos** para monitoramento
- **Integração quebrada** com outros sistemas

**✅ Benefícios do Status Code explícito:**
```javascript
// ❌ Ruim - Status implícito (200)
app.post('/usuarios', (req, res) => {
    const usuario = criarUsuario(req.body);
    res.json(usuario);  // Status 200 - ERRADO para criação!
});

// ✅ Bom - Status explícito
app.post('/usuarios', (req, res) => {
    const usuario = criarUsuario(req.body);
    res.status(201).json(usuario);  // 201 Created - CORRETO!
});
```

**📊 Impact no Comportamento da API:**

| Operação | Status Implícito | Status Correto | Impacto |
|----------|------------------|----------------|---------|
| **POST /usuarios** | 200 OK | 201 Created | Cache e semântica |
| **DELETE /usuarios/123** | 200 OK | 204 No Content | Clareza sobre vazio |
| **PUT /usuarios/999** | 200 OK | 404 Not Found | Error handling |

🔴 Resposta Desafio

5. Cenário Real - PUT /produtos/123

Resposta 5

Implementação Completa:

**🔍 Captura dos Dados:**
```javascript
app.put('/produtos/:id', (req, res) => {
    // 1. Capturar o ID da URL
    const idDaUrl = req.params.id;        // ← "123" como string

    // 2. Capturar nome do produto do Body
    const { nome, preco, categoria } = req.body;  // ← JSON payload

    // 3. Capturar query params (se houver)
    const forcUpdate = req.query.force;   // ← ?force=true

    console.log({
        idDaUrl,       // "123"
        nome,          // "Notebook Dell"
        preco,         // 2500.00
        categoria,     // "informatica"
        forcUpdate     // "true"
    });
});
```

**📋 Localização dos Dados:**

| Dado | Objeto | Exemplo | Tipo |
|------|--------|---------|------|
| **ID do produto** | `req.params.id` | `"123"` | String |
| **Nome novo** | `req.body.nome` | `"Notebook Dell"` | String |
| **Preço** | `req.body.preco` | `2500.00` | Number |
| **Flags opcionais** | `req.query.force` | `"true"` | String |

**⚔️ Conflito ID URL vs Body:**
```javascript
app.put('/produtos/:id', async (req, res) => {
    const idDaUrl = parseInt(req.params.id);
    const { id: idDoBody, ...dadosAtualizacao } = req.body;

    // ❌ CONFLITO DETECTADO
    if (idDoBody && idDoBody !== idDaUrl) {
        return res.status(400).json({
            erro: "ID inconsistente",
            detalhes: {
                id_url: idDaUrl,
                id_body: idDoBody,
                solucao: "Remova o ID do body ou garanta que sejam iguais"
            }
        });
    }

    // ✅ VALIDAÇÃO PASSOU
    try {
        const produtoExistente = await buscarProduto(idDaUrl);
        if (!produtoExistente) {
            return res.status(404).json({
                erro: "Produto não encontrado",
                id: idDaUrl
            });
        }

        // Atualizar apenas com dados do body (sem ID)
        const produtoAtualizado = await atualizarProduto(
            idDaUrl,
            dadosAtualizacao
        );

        return res.status(200).json(produtoAtualizado);

    } catch (erro) {
        return res.status(500).json({
            erro: "Erro interno do servidor",
            message: erro.message
        });
    }
});
```

**🧪 Exemplo de Requisição:**
```http
PUT /produtos/123 HTTP/1.1
Content-Type: application/json

{
    "nome": "Notebook Dell Inspiron 15",
    "preco": 2899.99,
    "categoria": "informatica",
    "especificacoes": {
        "ram": "16GB",
        "storage": "512GB SSD"
    }
}
```

**📱 Implementação com Validação Avançada:**
```javascript
const { body, validationResult } = require('express-validator');

const validarProduto = [
    body('nome').isString().isLength({ min: 3, max: 100 }),
    body('preco').isNumeric().custom(value => value > 0),
    body('categoria').isIn(['informatica', 'casa', 'roupas'])
];

app.put('/produtos/:id', validarProduto, async (req, res) => {
    // Verificar erros de validação
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(422).json({
            erro: "Dados inválidos",
            detalhes: errors.array()
        });
    }

    // ... resto da implementação
});
```

**🔄 Fluxo Completo:**
```mermaid
sequenceDiagram
    participant C as Cliente
    participant API as Controller
    participant V as Validador
    participant S as Service
    participant DB as Database

    C->>+API: PUT /produtos/123 + body
    API->>API: Extrair req.params.id
    API->>API: Extrair req.body.*
    API->>+V: Validar dados

    alt Dados inválidos
        V-->>-API: 422 Validation Error
        API-->>-C: 422 + erros detalhados
    else Dados válidos
        V-->>-API: ✅ Validação OK
        API->>+S: Atualizar produto
        S->>+DB: UPDATE produtos
        DB-->>-S: ✅ Atualizado
        S-->>-API: Produto atualizado
        API-->>-C: 200 + produto
    end
```

Implementação em Python (FastAPI)

```python from fastapi import FastAPI, HTTPException, Path from pydantic import BaseModel

class ProdutoUpdate(BaseModel):
    nome: str
    preco: float
    categoria: str

@app.put("/produtos/{produto_id}")
async def atualizar_produto(
    produto_id: int = Path(..., gt=0),
    produto: ProdutoUpdate
):
    # ID vem automaticamente do path
    produto_existente = await buscar_produto(produto_id)
    if not produto_existente:
        raise HTTPException(404, "Produto não encontrado")

    produto_atualizado = await atualizar_produto_db(
        produto_id,
        produto.dict()
    )

    return produto_atualizado  # FastAPI retorna 200 automaticamente
```

!!! tip "Dicas para Próximos Estudos" - Pratique validação robusta com express-validator ou Joi - Implemente middleware de error handling customizado - Use OpenAPI decorators para documentação automática - Configure rate limiting por endpoint

Navegação

← Exercício 05 | Próxima Solução →