O que é Flask?
Flask é um framework web minimalista para Python. Ele fornece um objeto de aplicação, uma maneira de mapear URLs para funções e um modelo de requisição e resposta. Todo o restante — bancos de dados, autenticação, formulários, jobs em segundo plano — é uma decisão que você toma posteriormente, geralmente adicionando uma extensão.
Essa simplicidade é justamente o objetivo. O Django tem uma opinião sobre como um projeto deve ser estruturado; o Flask quase não tem. Para uma API pequena, uma ferramenta interna ou um primeiro projeto web, essa liberdade é um recurso. Você consegue ler o framework inteiro em uma tarde e entender cada linha do seu próprio app.
O Flask é construído sobre duas bibliotecas consolidadas: Werkzeug para a infraestrutura WSGI e Jinja2 para templates. Ambas são maduras e bem documentadas, e saber que elas estão por baixo explica a maior parte do comportamento do Flask.
O menor app Flask
Uma aplicação Flask completa pode caber em poucas linhas.
# app.py
from flask import Flask
app = Flask(__name__)
@app.get("/")
def index():
return "Hello, world"
if __name__ == "__main__":
app.run(debug=True)
Flask(__name__) cria a aplicação e informa onde encontrar templates e arquivos estáticos. O decorator vincula uma URL à função abaixo dele. app.run() inicia o servidor de desenvolvimento. O guard if __name__ == "__main__" evita que o servidor seja iniciado quando o módulo é importado por um teste ou por um servidor WSGI.
Roteamento com decorators
O decorator @app.route é o coração do Flask. Ele registra uma regra de URL e a função que responde a ela.
@app.route("/posts")
def list_posts():
return "All posts"
@app.route("/posts/<int:post_id>")
def get_post(post_id):
return f"Post {post_id}"
As regras podem conter partes variáveis escritas entre colchetes angulares. O Flask faz a correspondência da URL, converte o valor e o passa para a função como um argumento nomeado (keyword argument). Se nenhuma regra for correspondida, o Flask retorna automaticamente um erro 404.
Variáveis de URL e métodos HTTP
Os conversores tornam as variáveis type-safe antes da execução do seu código. Os mais comuns são string (o padrão), int, float, path (que permite barras) e uuid.
@app.get("/users/<username>")
def profile(username):
return f"Profile for {username}"
@app.get("/files/<path:filepath>")
def serve_file(filepath):
return f"File at {filepath}"
O Flask 2.0 adicionou atalhos para cada método. @app.get, @app.post, @app.put, @app.patch e @app.delete são mais claros do que passar uma lista de methods, e uma única função pode responder a vários métodos.
@app.route("/posts", methods=["GET", "POST"])
def posts():
if request.method == "POST":
return create_post()
return list_posts()
O objeto request
Os dados recebidos ficam no objeto global request, que o Flask preenche para a requisição atual. Ele é um proxy, portanto, importá-lo apenas uma vez no topo do módulo é seguro.
from flask import request
@app.get("/search")
def search():
query = request.args.get("q", "")
page = request.args.get("page", 1, type=int)
return {"query": query, "page": page}
@app.post("/posts")
def create_post():
payload = request.get_json()
title = payload["title"]
return {"title": title}, 201
request.args contém os valores da query-string, request.form contém os posts de formulários, request.get_json() analisa o corpo JSON, request.files contém os uploads e request.headers fornece os headers brutos. Como esses são mapeamentos de múltiplos valores, use .get() e forneça valores padrão em vez de tentar acessar os índices cegamente.
Respostas e JSON
Uma view pode retornar uma string, um dicionário (que o Flask serializa para JSON), uma tupla de (body, status) ou (body, status, headers), ou um objeto Response completo.
from flask import jsonify, make_response
@app.post("/posts")
def create_post():
post = {"id": 1, "title": "Hello"}
return jsonify(post), 201
@app.get("/ping")
def ping():
response = make_response({"pong": True})
response.headers["Cache-Control"] = "no-store"
return response
jsonify é a maneira explícita de retornar JSON: ele serializa os dados e define o content type. Retornar um dicionário também funciona, mas jsonify deixa a intenção mais clara e lida com mais tipos de dados.
Templates com Jinja2
render_template carrega um arquivo da pasta templates/ e o renderiza com os dados que você passa. O Jinja2 escapa variáveis por padrão, o que protege contra XSS.
from flask import render_template
@app.get("/posts/<int:post_id>")
def get_post(post_id):
post = {"id": post_id, "title": "Hello", "body": "First post"}
return render_template("post.html", post=post)
<!-- templates/post.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{{ post.title }}</title>
</head>
<body>
<h1>{{ post.title }}</h1>
<p>{{ post.body }}</p>
</body>
</html>
O Jinja suporta {% extends %} para herança, {% include %} para fragmentos, {% for %} e {% if %} para lógica, e filtros como {{ value|upper }}. Mantenha os templates focados na apresentação; se a lógica crescer, mova-a para a view.
Arquivos estáticos
Por padrão, o Flask serve qualquer conteúdo na pasta static/ em /static/.... Referencie-a com url_for, que constrói a URL a partir do nome do endpoint.
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
Em produção, utilize um reverse proxy ou CDN para servir arquivos estáticos em vez do processo Python. Isso é mais rápido e libera o app para processar as requisições.
Blueprints
Um Blueprint é uma coleção de rotas e templates que podem ser registrados em um app. É assim que um app Flask de módulo único cresce para se tornar um projeto sem virar uma bagunça.
# blog/views.py
from flask import Blueprint, jsonify
bp = Blueprint("blog", __name__, url_prefix="/blog")
@bp.get("/")
def index():
return jsonify(posts=[])
@bp.get("/<int:post_id>")
def detail(post_id):
return jsonify(id=post_id)
# app.py
from blog.views import bp as blog_bp
app.register_blueprint(blog_bp)
Blueprints possuem seu próprio prefixo de URL, templates e arquivos estáticos, e um único blueprint pode ser registrado várias vezes com prefixos diferentes. Eles também facilitam os testes e a revisão de código, pois cada funcionalidade é autocontida.
A factory de aplicação
Assim que você começa a usar blueprints, o próximo passo natural é criar uma função factory para construir o app. Esta é a estrutura padrão para qualquer projeto que seja maior que uma demo.
# app.py
from flask import Flask
from blog.views import bp as blog_bp
def create_app(config_object="config.Config"):
app = Flask(__name__)
app.config.from_object(config_object)
app.register_blueprint(blog_bp)
return app
# wsgi.py
from app import create_app
app = create_app()
A factory torna os testes triviais: você constrói um app com uma configuração de teste, executa o cliente e depois o descarta. Ela também evita a importação de um app global parcialmente configurado, que é a raiz da maioria dos problemas de importação circular no Flask.
Extensões
Como o núcleo é minimalista, o ecossistema fornece o restante. Algumas extensões cobrem a maioria dos projetos:
- Flask-SQLAlchemy integra o ORM do SQLAlchemy e gerencia a sessão por requisição.
- Flask-Migrate encapsula o Alembic para que as alterações de schema sejam versionadas.
- Flask-Login gerencia sessões de usuário e o proxy
current_user. - Flask-WTF adiciona formulários, proteção CSRF e upload de arquivos.
- Flask-CORS controla requisições cross-origin para APIs.
# extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
# app.py
from extensions import db, migrate
def create_app():
app = Flask(__name__)
app.config.from_object("config.Config")
db.init_app(app)
migrate.init_app(app, db)
return app
O padrão init_app permite que uma extensão seja criada sem um app e vinculada posteriormente, o que mantém a factory limpa e a extensão importável por models.
Configuração
A configuração é um mapeamento simples em app.config. Carregue-a a partir de uma classe, de um arquivo ou do ambiente, e mantenha segredos fora do controle de versão.
# config.py
import os
class Config:
SECRET_KEY = os.environ["SECRET_KEY"]
SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
JSON_SORT_KEYS = False
app.config.from_object("config.Config")
app.config.from_prefixed_env()
from_prefixed_env() lê variáveis de ambiente de FLASK_*, o que é conveniente em containers. Use classes ou arquivos diferentes por ambiente e nunca faça commit da SECRET_KEY real — ela assina sessões e tokens CSRF.
Tratamento de erros
Registre manipuladores de erro (error handlers) para retornar respostas consistentes em todo o app. Os handlers podem ser globais ou limitados a um blueprint.
from flask import jsonify
from werkzeug.exceptions import HTTPException
@app.errorhandler(404)
def not_found(error):
return jsonify(error="not_found"), 404
@app.errorhandler(Exception)
def unhandled(error):
if isinstance(error, HTTPException):
return error
app.logger.exception(error)
return jsonify(error="internal_error"), 500
O handler para HTTPException é importante: os erros nativos do Flask também são exceções e, caso contrário, um handler genérico de Exception transformaria um erro 404 em um 500.
Testes
O cliente de teste do Flask faz requisições no mesmo processo, sem a necessidade de um servidor ou de rede.
import pytest
from app import create_app
@pytest.fixture
def client():
app = create_app("config.TestConfig")
with app.test_client() as client:
yield client
def test_health(client):
response = client.get("/health")
assert response.status_code == 200
assert response.get_json() == {"status": "ok"}
def test_missing_post(client):
response = client.get("/posts/999")
assert response.status_code == 404
A factory e o test_client() trabalham juntos: cada teste recebe uma instância nova do app, evitando que o estado vaze entre os testes. Sobrescreva a configuração para os testes e utilize um banco de dados em memória quando for adicionar um.
WSGI e deployment
O Flask é uma aplicação WSGI, o que significa que qualquer servidor WSGI pode executá-lo. O servidor de desenvolvimento serve apenas para fins de desenvolvimento.
pip install gunicorn
gunicorn --workers 4 --bind 0.0.0.0:8000 "app:create_app()"
Em produção, execute o Gunicorn ou uWSGI atrás do Nginx, finalize o TLS no proxy e sirva arquivos estáticos a partir do proxy ou de um CDN. Configure DEBUG=False, configure o logging e leia os secrets do ambiente. Containerizar a aplicação torna o deployment portátil entre diferentes hosts.
Melhores práticas
- Comece com um único módulo, depois migre para factories e blueprints conforme o projeto crescer.
- Use
url_forpara construir URLs em vez de fixar caminhos no código (hardcoding). - Mantenha as configurações em classes ou variáveis de ambiente, nunca diretamente no código.
- Use
jsonifye códigos de status explícitos para respostas de API. - Valide cada dado de entrada antes que ele chegue à sua lógica de negócio.
- Registre manipuladores de erro (error handlers) para que as falhas retornem um formato consistente.
- Use o cliente de teste e uma instância nova do app para cada teste.
- Nunca execute o servidor de desenvolvimento em produção.
Erros comuns
- Deixar o
debug=Trueativado em produção, o que expõe um debugger interativo. - Importar um
appglobal em todos os lugares, criando importações circulares. - Ler o
request.jsonsem tratar corpos malformados ou ausentes. - Colocar URLs fixas (hardcoded) nos templates em vez de usar
url_for. - Construir um
app.pymonolítico com centenas de rotas. - Esquecer a proteção CSRF ao utilizar formulários.
- Armazenar segredos no
config.pye fazer o commit do arquivo. - Presumir que o Flask possui um ORM ou sistema de autenticação nativo e acabar reinventando ambos de forma ineficiente.
Próximos passos
O Flask é a maneira mais rápida de aprender o que um web framework realmente faz, pois quase nada fica oculto. Quando você precisar de um ORM, migrations, auth e um painel administrativo sem ter que montar tudo do zero, leia o guia do Django. Se quiser validação automática e documentação de API a partir de type hints, mude para o FastAPI. Para projetar os endpoints que você acabou de criar, o guia de REST APIs é o companheiro ideal. Se você adicionar um banco de dados, o roadmap de backend cobre SQL, a linguagem que sustenta o ORM.