Comunicacao com o servidor - ES2-UFPI/EsqueceramAI GitHub Wiki

Comunicação com o servidor: fetch, JSON e CORS

Neste tutorial você vai ver como o front-end (HTML + JavaScript) conversa com o back-end (Python + Flask) do Esqueceram AI, usando fetch e JSON, e por que o navegador bloqueia requisições por CORS.

📺 Vídeo-aula: Comunicação com o servidor: fetch, JSON e CORS, por Samara.

Assista ao vídeo

O que será construído

Uma página que lista e cadastra itens perdidos, conversando com uma API Flask. Os itens ficam em memória (a persistência com PostgreSQL tem tutorial próprio).

Pré-requisitos

Estrutura de pastas:

tutorial-fetch/
├── backend/
│   └── app.py
└── frontend/
    ├── index.html
    └── app.js

Preparar o ambiente do back-end

Dentro da pasta backend/:

python -m venv .venv

# Windows (PowerShell):
.venv\Scripts\activate
# Linux/Mac:
source .venv/bin/activate

pip install flask flask-cors

O ambiente virtual isola as bibliotecas deste projeto. Quando ativo, aparece (.venv) no começo da linha do terminal.

Criar a API (backend/app.py)

Comece sem o CORS, para ver o erro acontecer no passo 4.

from flask import Flask, jsonify, request

app = Flask(__name__)

itens = [{"id": 1, "nome": "Garrafa azul", "local": "Biblioteca"}]

@app.get("/")
def raiz():
    return jsonify({"status": "API Esqueceram AI no ar"})

@app.get("/api/itens")
def listar():
    return jsonify(itens)

@app.post("/api/itens")
def criar():
    dados = request.get_json(silent=True)
    if not dados or not dados.get("nome") or not dados.get("local"):
        return jsonify({"erro": "nome e local são obrigatórios"}), 400
    item = {"id": len(itens) + 1, "nome": dados["nome"], "local": dados["local"]}
    itens.append(item)
    return jsonify(item), 201

if __name__ == "__main__":
    app.run(debug=True, port=5000)

Rode com python app.py e abra http://localhost:5000/api/itens. Deve aparecer o JSON com a "Garrafa azul".

  • GET /api/itens devolve a lista em JSON.
  • POST /api/itens recebe {"nome": ..., "local": ...}, valida e cria o item. Responde 201 (criado) ou 400 (dados inválidos).

Criar o front-end

frontend/index.html

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <title>Esqueceram AI - Teste de API</title>
</head>
<body>
  <h1>Itens perdidos</h1>
  <form id="form">
    <input id="nome" placeholder="Nome do item" required>
    <input id="local" placeholder="Local" required>
    <button type="submit">Cadastrar</button>
  </form>
  <p id="msg"></p>
  <ul id="lista"></ul>
  <script src="app.js"></script>
</body>
</html>

frontend/app.js

const API = "http://localhost:5000/api";

function mostrar(texto) {
  document.getElementById("msg").textContent = texto;
}

async function carregar() {
  try {
    const resp = await fetch(`${API}/itens`);
    if (!resp.ok) throw new Error(`Erro ${resp.status}`);
    const itens = await resp.json();
    const lista = document.getElementById("lista");
    lista.innerHTML = "";
    itens.forEach((i) => {
      const li = document.createElement("li");
      li.textContent = `${i.nome} — ${i.local}`;
      lista.appendChild(li);
    });
  } catch (e) {
    mostrar("Falha ao carregar: " + e.message);
  }
}

document.getElementById("form").addEventListener("submit", async (ev) => {
  ev.preventDefault();
  const corpo = {
    nome: document.getElementById("nome").value,
    local: document.getElementById("local").value,
  };
  try {
    const resp = await fetch(`${API}/itens`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(corpo),
    });
    const dados = await resp.json();
    if (!resp.ok) throw new Error(dados.erro || resp.status);
    ev.target.reset();
    mostrar("Item cadastrado!");
    carregar();
  } catch (e) {
    mostrar("Falha ao cadastrar: " + e.message);
  }
});

carregar();

Pontos importantes:

  • O fetch não lança erro em respostas 400/500. Por isso verificamos resp.ok e lançamos o erro manualmente.
  • Para enviar JSON é preciso o cabeçalho Content-Type: application/json e o corpo convertido com JSON.stringify.
  • Usamos textContent (e não innerHTML) para evitar injeção de HTML malicioso.
  • A URL da API fica na constante API, para trocar facilmente no deploy.

Servir o front-end e provocar o erro de CORS

Com o Flask rodando, abra outro terminal dentro de frontend/:

python -m http.server 5500

Acesse http://localhost:5500 (não abra o arquivo com duplo clique), aperte F12, vá na aba Console e recarregue com Ctrl+Shift+R. Vai aparecer um erro dizendo que o acesso foi bloqueado pela política de CORS, e a página mostra "Falha ao carregar: Failed to fetch".

Por que acontece? A página está na porta 5500 e a API na 5000, e portas diferentes são origens diferentes. Observe o terminal do Flask: ele registra GET /api/itens 200, ou seja, o servidor respondeu, mas o navegador bloqueou a leitura da resposta. O navegador só libera se o servidor informar, nos cabeçalhos, que aquela origem é permitida.

Corrigir o CORS com flask-cors

No app.py, altere o começo:

from flask import Flask, jsonify, request
from flask_cors import CORS

app = Flask(__name__)
CORS(app, origins=["http://localhost:5500", "http://127.0.0.1:5500"])

Salve (o Flask recarrega sozinho) e recarregue http://localhost:5500. A lista deve aparecer, e o cadastro funciona.

Na aba Network do F12, repare que o POST com JSON é precedido por uma requisição OPTIONS, o preflight: o navegador pergunta se pode fazer a requisição antes de enviá-la, e o flask-cors responde automaticamente.

⚠️ Nunca use CORS(app) sem restrição em produção, pois isso permite que qualquer site acesse a API. Liste apenas as origens do front-end.

Testar a validação (erro 400)

No Console do navegador, com a página aberta:

fetch("http://localhost:5000/api/itens", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ nome: "Teste" })
}).then(r => r.json().then(d => console.log(r.status, d)));

A resposta é 400 com {"erro": "nome e local são obrigatórios"}, que é a mensagem exibida ao usuário no formulário.

Problemas comuns

Sintoma Causa provável Solução
Not Found ao abrir localhost:5000 A rota / não existe Acesse /api/itens ou adicione a rota /
Erro de CORS não aparece A página foi aberta pela porta 5000 ou por file:// Abra http://localhost:5500
Erro de CORS não some Origem diferente da liberada (localhost x 127.0.0.1) Liste as duas origens no CORS(...)
Failed to fetch mesmo com CORS Flask parado ou porta errada Confira o terminal e a constante API
Alterações não aparecem Código não salvo Salve o arquivo (o Flask recarrega sozinho)

Próximos passos

Este tutorial usa JSON e itens em memória para explicar a comunicação básica. Nas rotas reais, o cadastro sem imagem poderá usar JSON, mas o cadastro com fotografia deverá usar multipart/form-data. A persistência em PostgreSQL e o upload de imagens para o Firebase Storage serão tratados na implementação do backend.

⚠️ **GitHub.com Fallback** ⚠️