Comunicacao com o servidor - ES2-UFPI/EsqueceramAI GitHub Wiki
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.
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).
- Python 3.10 ou superior, Git e VS Code (veja Preparando o ambiente de desenvolvimento).
Estrutura de pastas:
tutorial-fetch/
├── backend/
│ └── app.py
└── frontend/
├── index.html
└── app.js
Dentro da pasta backend/:
python -m venv .venv
# Windows (PowerShell):
.venv\Scripts\activate
# Linux/Mac:
source .venv/bin/activate
pip install flask flask-corsO ambiente virtual isola as bibliotecas deste projeto. Quando ativo, aparece (.venv) no começo da linha do terminal.
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/itensdevolve a lista em JSON. -
POST /api/itensrecebe{"nome": ..., "local": ...}, valida e cria o item. Responde 201 (criado) ou 400 (dados inválidos).
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
fetchnão lança erro em respostas 400/500. Por isso verificamosresp.oke lançamos o erro manualmente. - Para enviar JSON é preciso o cabeçalho
Content-Type: application/jsone o corpo convertido comJSON.stringify. - Usamos
textContent(e nãoinnerHTML) para evitar injeção de HTML malicioso. - A URL da API fica na constante
API, para trocar facilmente no deploy.
Com o Flask rodando, abra outro terminal dentro de frontend/:
python -m http.server 5500Acesse 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.
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 useCORS(app)sem restrição em produção, pois isso permite que qualquer site acesse a API. Liste apenas as origens do front-end.
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.
| 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) |
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.
