Flask é um microframework web para Python — leve, flexível e direto ao ponto. Diferente de frameworks como Django, Flask não impõe estrutura, ORM ou sistema de templates específico. Você começa com o mínimo e adiciona apenas o que precisa. Isso o torna ideal para APIs, microserviços e projetos onde o controle total da arquitetura é importante.
Instalação e Primeiro Servidor
pip install flask
# app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def inicio():
return "Olá, Flask!"
@app.route("/sobre")
def sobre():
return "Sistema de gestão escolar v1.0"
if __name__ == "__main__":
app.run(debug=True)
python app.py
# * Running on http://127.0.0.1:5000
debug=True ativa o reloader automático — o servidor reinicia ao salvar o arquivo — e exibe erros detalhados no browser. Nunca use em produção.
Rotas e Métodos HTTP
from flask import Flask, request, jsonify
app = Flask(__name__)
# Rota com parâmetro de URL
@app.route("/usuarios/<int:usuario_id>")
def buscar_usuario(usuario_id):
return jsonify({"id": usuario_id, "nome": f"Usuário {usuario_id}"})
# Parâmetro string
@app.route("/produtos/<string:slug>")
def buscar_produto(slug):
return jsonify({"slug": slug})
# Múltiplos métodos
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
dados = request.get_json()
email = dados.get("email")
senha = dados.get("senha")
return jsonify({"mensagem": f"Login de {email} processado."})
return jsonify({"mensagem": "Envie POST com email e senha."})
# Rota com valor padrão
@app.route("/pagina/")
@app.route("/pagina/<int:numero>")
def pagina(numero=1):
return jsonify({"pagina": numero})
Request: Acessando Dados da Requisição
from flask import request
@app.route("/dados", methods=["POST"])
def receber_dados():
# JSON
dados_json = request.get_json()
# Query parameters — ?nome=Ana&idade=25
nome = request.args.get("nome", "Anônimo")
idade = request.args.get("idade", type=int)
# Form data
campo = request.form.get("campo")
# Headers
token = request.headers.get("Authorization")
# Arquivos
arquivo = request.files.get("documento")
# Informações da requisição
ip = request.remote_addr
metodo = request.method
url = request.url
return jsonify({
"ip": ip,
"metodo": metodo,
"nome": nome,
})
Responses: Controlando a Resposta
from flask import jsonify, make_response, redirect, url_for, abort
# JSON com status code
@app.route("/criado", methods=["POST"])
def criar():
return jsonify({"id": 1, "mensagem": "Criado"}), 201
# Response customizada
@app.route("/customizado")
def customizado():
resposta = make_response(
jsonify({"dados": "aqui"}),
200
)
resposta.headers["X-Custom-Header"] = "valor"
resposta.headers["Cache-Control"] = "no-cache"
return resposta
# Redirecionamento
@app.route("/antigo")
def antigo():
return redirect(url_for("inicio"), 301)
# Abortando com erro
@app.route("/restrito")
def restrito():
abort(403) # lança Forbidden automaticamente
Tratamento de Erros
from flask import jsonify
@app.errorhandler(404)
def nao_encontrado(erro):
return jsonify({
"erro": "Recurso não encontrado.",
"codigo": 404
}), 404
@app.errorhandler(403)
def proibido(erro):
return jsonify({
"erro": "Acesso negado.",
"codigo": 403
}), 403
@app.errorhandler(500)
def erro_interno(erro):
return jsonify({
"erro": "Erro interno do servidor.",
"codigo": 500
}), 500
@app.errorhandler(Exception)
def erro_generico(erro):
return jsonify({
"erro": str(erro),
"codigo": 500
}), 500
Blueprints: Organizando a Aplicação
Blueprints permitem dividir a aplicação em módulos independentes:
projeto/
├── app.py
├── config.py
└── routes/
├── __init__.py
├── alunos.py
└── disciplinas.py
# routes/alunos.py
from flask import Blueprint, jsonify, request
alunos_bp = Blueprint("alunos", __name__, url_prefix="/alunos")
# Banco em memória para exemplo
_alunos = {}
_proximo_id = 1
@alunos_bp.route("/", methods=["GET"])
def listar():
nota_min = request.args.get("nota_min", 0, type=float)
resultado = [
a for a in _alunos.values()
if a["nota"] >= nota_min
]
return jsonify(resultado)
@alunos_bp.route("/<int:aluno_id>", methods=["GET"])
def buscar(aluno_id):
aluno = _alunos.get(aluno_id)
if not aluno:
return jsonify({"erro": "Aluno não encontrado."}), 404
return jsonify(aluno)
@alunos_bp.route("/", methods=["POST"])
def criar():
global _proximo_id
dados = request.get_json()
if not dados or not dados.get("nome") or not dados.get("email"):
return jsonify({"erro": "nome e email são obrigatórios."}), 400
aluno = {
"id": _proximo_id,
"nome": dados["nome"],
"email": dados["email"],
"nota": dados.get("nota", 0.0),
}
_alunos[_proximo_id] = aluno
_proximo_id += 1
return jsonify(aluno), 201
@alunos_bp.route("/<int:aluno_id>", methods=["PUT"])
def atualizar(aluno_id):
aluno = _alunos.get(aluno_id)
if not aluno:
return jsonify({"erro": "Aluno não encontrado."}), 404
dados = request.get_json()
aluno.update({
"nome": dados.get("nome", aluno["nome"]),
"email": dados.get("email", aluno["email"]),
"nota": dados.get("nota", aluno["nota"]),
})
return jsonify(aluno)
@alunos_bp.route("/<int:aluno_id>", methods=["DELETE"])
def deletar(aluno_id):
if aluno_id not in _alunos:
return jsonify({"erro": "Aluno não encontrado."}), 404
del _alunos[aluno_id]
return "", 204
# app.py
from flask import Flask
from routes.alunos import alunos_bp
def criar_app():
app = Flask(__name__)
# Registrando blueprints
app.register_blueprint(alunos_bp)
return app
app = criar_app()
if __name__ == "__main__":
app.run(debug=True)
Middleware e Before/After Request
from flask import request, g
import time
@app.before_request
def antes_da_requisicao():
g.inicio = time.perf_counter() # g é o contexto global da requisição
print(f"→ {request.method} {request.path}")
@app.after_request
def depois_da_requisicao(resposta):
duracao = time.perf_counter() - g.get("inicio", time.perf_counter())
resposta.headers["X-Tempo-Ms"] = f"{duracao * 1000:.2f}"
print(f"← {resposta.status_code} em {duracao*1000:.1f}ms")
return resposta
@app.teardown_request
def encerrar_requisicao(excecao=None):
# Executado ao final — com ou sem erro
# Ideal para fechar conexões de banco
pass
Configuração
# config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
SECRET_KEY = os.getenv("SECRET_KEY", "dev-inseguro")
DEBUG = False
TESTING = False
class ConfigDesenvolvimento(Config):
DEBUG = True
DATABASE_URL = "sqlite:///dev.db"
class ConfigProducao(Config):
DATABASE_URL = os.getenv("DATABASE_URL")
SECRET_KEY = os.getenv("SECRET_KEY") # obrigatório em produção
class ConfigTestes(Config):
TESTING = True
DATABASE_URL = "sqlite:///:memory:"
configs = {
"desenvolvimento": ConfigDesenvolvimento,
"producao": ConfigProducao,
"testes": ConfigTestes,
}
# app.py
def criar_app(ambiente="desenvolvimento"):
app = Flask(__name__)
app.config.from_object(configs[ambiente])
return app
Exemplo Completo: API REST de Escola
from flask import Flask, jsonify, request
from datetime import datetime
app = Flask(__name__)
# Simulando banco em memória
banco = {
"alunos": {
1: {"id": 1, "nome": "Ana Silva", "email": "ana@email.com", "nota": 9.5},
2: {"id": 2, "nome": "Bruno Costa", "email": "bruno@email.com", "nota": 7.0},
3: {"id": 3, "nome": "Carla Souza", "email": "carla@email.com", "nota": 5.5},
},
"proximo_id": 4
}
def validar_aluno(dados, obrigatorio=True):
erros = {}
if obrigatorio or "nome" in dados:
if not dados.get("nome") or len(dados["nome"]) < 2:
erros["nome"] = "Nome deve ter ao menos 2 caracteres."
if obrigatorio or "email" in dados:
if not dados.get("email") or "@" not in dados["email"]:
erros["email"] = "E-mail inválido."
if "nota" in dados:
try:
nota = float(dados["nota"])
if not 0 <= nota <= 10:
erros["nota"] = "Nota deve ser entre 0 e 10."
except (ValueError, TypeError):
erros["nota"] = "Nota deve ser um número."
return erros
@app.route("/alunos", methods=["GET"])
def listar_alunos():
alunos = list(banco["alunos"].values())
nota_min = request.args.get("nota_min", type=float)
aprovados = request.args.get("aprovados", type=bool)
if nota_min is not None:
alunos = [a for a in alunos if a["nota"] >= nota_min]
if aprovados:
alunos = [a for a in alunos if a["nota"] >= 6.0]
return jsonify({
"total": len(alunos),
"alunos": alunos
})
@app.route("/alunos/<int:aluno_id>", methods=["GET"])
def buscar_aluno(aluno_id):
aluno = banco["alunos"].get(aluno_id)
if not aluno:
return jsonify({"erro": "Aluno não encontrado."}), 404
return jsonify(aluno)
@app.route("/alunos", methods=["POST"])
def criar_aluno():
dados = request.get_json()
if not dados:
return jsonify({"erro": "Body JSON obrigatório."}), 400
erros = validar_aluno(dados)
if erros:
return jsonify({"erros": erros}), 422
aluno_id = banco["proximo_id"]
aluno = {
"id": aluno_id,
"nome": dados["nome"],
"email": dados["email"],
"nota": float(dados.get("nota", 0.0)),
"criado_em": datetime.utcnow().isoformat()
}
banco["alunos"][aluno_id] = aluno
banco["proximo_id"] += 1
return jsonify(aluno), 201
@app.route("/alunos/<int:aluno_id>", methods=["PATCH"])
def atualizar_aluno(aluno_id):
aluno = banco["alunos"].get(aluno_id)
if not aluno:
return jsonify({"erro": "Aluno não encontrado."}), 404
dados = request.get_json()
if not dados:
return jsonify({"erro": "Body JSON obrigatório."}), 400
erros = validar_aluno(dados, obrigatorio=False)
if erros:
return jsonify({"erros": erros}), 422
if "nome" in dados: aluno["nome"] = dados["nome"]
if "email" in dados: aluno["email"] = dados["email"]
if "nota" in dados: aluno["nota"] = float(dados["nota"])
return jsonify(aluno)
@app.route("/alunos/<int:aluno_id>", methods=["DELETE"])
def deletar_aluno(aluno_id):
if aluno_id not in banco["alunos"]:
return jsonify({"erro": "Aluno não encontrado."}), 404
del banco["alunos"][aluno_id]
return "", 204
@app.route("/alunos/estatisticas", methods=["GET"])
def estatisticas():
alunos = list(banco["alunos"].values())
if not alunos:
return jsonify({"erro": "Nenhum aluno cadastrado."}), 404
notas = [a["nota"] for a in alunos]
aprovados = [a for a in alunos if a["nota"] >= 6.0]
return jsonify({
"total": len(alunos),
"media": round(sum(notas) / len(notas), 2),
"maior_nota": max(notas),
"menor_nota": min(notas),
"aprovados": len(aprovados),
"reprovados": len(alunos) - len(aprovados),
})
@app.errorhandler(404)
def nao_encontrado(e):
return jsonify({"erro": "Rota não encontrada."}), 404
@app.errorhandler(405)
def metodo_nao_permitido(e):
return jsonify({"erro": "Método não permitido."}), 405
if __name__ == "__main__":
app.run(debug=True, port=5000)
Testando com curl:
# Listar alunos
curl http://localhost:5000/alunos
# Criar aluno
curl -X POST http://localhost:5000/alunos \
-H "Content-Type: application/json" \
-d '{"nome": "Diego Lima", "email": "diego@email.com", "nota": 8.0}'
# Buscar por ID
curl http://localhost:5000/alunos/1
# Atualizar nota
curl -X PATCH http://localhost:5000/alunos/1 \
-H "Content-Type: application/json" \
-d '{"nota": 10.0}'
# Estatísticas
curl http://localhost:5000/alunos/estatisticas
# Deletar
curl -X DELETE http://localhost:5000/alunos/3
Resumo
- Flask é um microframework — começa mínimo e cresce conforme a necessidade
@app.route()mapeia URLs a funções; parâmetros de URL são tipados no decoradorrequestacessa JSON, query params, form data, headers e arquivos da requisiçãojsonify()serializa dicionários para JSON com headers corretos automaticamenteabort()lança erros HTTP;@app.errorhandler()personaliza as respostas de erro- Blueprints organizam rotas em módulos independentes — essencial para projetos maiores
before_requesteafter_requestimplementam middleware de forma declarativa- Separe configurações por ambiente — nunca use
debug=Trueem produção
Referências e Leituras Complementares
- Flask — documentação oficial — https://flask.palletsprojects.com/
- Quickstart do Flask — https://flask.palletsprojects.com/en/3.0.x/quickstart/
- Blueprints e aplicações modulares — https://flask.palletsprojects.com/en/3.0.x/blueprints/
- Flask-SQLAlchemy — https://flask-sqlalchemy.palletsprojects.com/
- Flask-JWT-Extended — autenticação JWT — https://flask-jwt-extended.readthedocs.io/
- GRINBERG, Miguel. Flask Web Development. 2. ed. O'Reilly Media, 2018. — o livro de referência do Flask, escrito pelo autor de extensões centrais do framework.
- PERCIVAL, Harry; GREGORY, Bob. Architecture Patterns with Python. O'Reilly Media, 2020. — uso de Flask em arquiteturas orientadas a domínio.