Python

Introdução ao Desenvolvimento Web com Flask Já leu

9 min de leitura

Introdução ao Desenvolvimento Web com Flask
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 ap

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 decorador
  • request acessa JSON, query params, form data, headers e arquivos da requisição
  • jsonify() serializa dicionários para JSON com headers corretos automaticamente
  • abort() lança erros HTTP; @app.errorhandler() personaliza as respostas de erro
  • Blueprints organizam rotas em módulos independentes — essencial para projetos maiores
  • before_request e after_request implementam middleware de forma declarativa
  • Separe configurações por ambiente — nunca use debug=True em 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.
Comentários

Mais em Python

Tuplas e Sets: imutabilidade e unicidade
Tuplas e Sets: imutabilidade e unicidade

Python oferece mais de uma forma de agrupar dados. Enquanto listas s&atilde;o...

WebSockets e Comunicação em Tempo Real em Python (Ver se é duplicidade)
WebSockets e Comunicação em Tempo Real em Python (Ver se é duplicidade)

HTTP é excelente para o modelo requisição-resposta — o cliente pergunta, o se...

Dominando o Python
Dominando o Python

Se voc&ecirc; j&aacute; pesquisou sobre programa&ccedil;&atilde;o nos &uacute...