Páginas web com Flask e Jinja2: do JSON ao HTML
Perceu Bertoletti - 20/08/2026

Até agora nossos exemplos de Flask devolveram JSON, o formato ideal para APIs consumidas por outros programas. Mas e quando você quer mostrar os dados para uma pessoa em um navegador, com uma página bonita e formatada?

Para isso o Flask usa o Jinja2, um motor de templates que já vem embutido. Com ele, você escreve páginas HTML com "espaços" que são preenchidos com dados vindos do Python. Vamos aprender na prática.

A estrutura de pastas

O Flask espera os templates dentro da pasta templates:

meu_app/
├── app.py
└── templates/
    └── index.html

Primeira página com dados

Crie o app.py:

from flask import Flask, render_template

app = Flask(__name__)

tarefas = [
    {"id": 1, "titulo": "Escrever artigo", "concluida": True},
    {"id": 2, "titulo": "Estudar Flask", "concluida": False},
    {"id": 3, "titulo": "Revisar código", "concluida": False},
]


@app.route("/")
def index():
    return render_template("index.html", tarefas=tarefas)

Agora crie o template templates/index.html:

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="utf-8">
    <title>Minhas tarefas</title>
</head>
<body>
    <h1>Minhas tarefas</h1>
    <ul>
        {% for tarefa in tarefas %}
        <li>{{ tarefa.titulo }}</li>
        {% endfor %}
    </ul>
</body>
</html>

Rode com python app.py e acesse http://localhost:5000/. A página mostra a lista de tarefas. Repare nos blocos especiais do Jinja2:

  • {% ... %} executa lógica (laços, condições);
  • {{ ... }} imprime o valor de uma variável.

Condicionais

Para marcar as tarefas concluídas, use um if:

<ul>
    {% for tarefa in tarefas %}
    <li>
        {{ tarefa.titulo }}
        {% if tarefa.concluida %}
        <strong>(concluída)</strong>
        {% else %}
        <em>(pendente)</em>
        {% endif %}
    </li>
    {% endfor %}
</ul>

Herança de layouts: não repita HTML

Você não quer copiar a estrutura do <html> para cada página. O Jinja2 resolve isso com herança de templates. Crie o layout base templates/base.html:

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="utf-8">
    <title>{% block titulo %}Meu site{% endblock %}</title>
</head>
<body>
    <header>
        <nav>
            <a href="{{ url_for('index') }}">Início</a>
            <a href="{{ url_for('sobre') }}">Sobre</a>
        </nav>
    </header>
    <main>
        {% block conteudo %}{% endblock %}
    </main>
</body>
</html>

Veja duas novidades:

  • {% block ... %} define uma área que cada página pode preencher;
  • {{ url_for('index') }} gera a URL de uma rota a partir do nome da função, evitando URLs "na mão".

Agora a página de tarefas pode ser escrita apenas com o conteúdo:

{% extends "base.html" %}

{% block titulo %}Minhas tarefas{% endblock %}

{% block conteudo %}
<h1>Minhas tarefas</h1>
<ul>
    {% for tarefa in tarefas %}
    <li>{{ tarefa.titulo }}</li>
    {% endfor %}
</ul>
{% endblock %}

E uma página "Sobre" reutiliza o mesmo layout:

{% extends "base.html" %}

{% block titulo %}Sobre{% endblock %}

{% block conteudo %}
<h1>Sobre nós</h1>
<p>Somos a comunidade de Python de São Marcos.</p>
{% endblock %}

No app.py, adicione a rota:

@app.route("/sobre")
def sobre():
    return render_template("sobre.html")

Agora as duas páginas compartilham a mesma estrutura e o mesmo menu. Qualquer mudança no layout base reflete em todas as páginas de uma vez.

Recebendo dados de formulários

O Jinja2 também serve para montar formulários. Crie o template templates/nova.html:

{% extends "base.html" %}

{% block conteudo %}
<h1>Nova tarefa</h1>
<form method="POST">
    <label for="titulo">Título:</label>
    <input type="text" name="titulo" id="titulo" required>
    <button type="submit">Salvar</button>
</form>
{% endblock %}

E a rota que recebe o formulário. No Flask, formulários usam os métodos GET (mostrar o formulário) e POST (receber os dados):

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

tarefas = []


@app.route("/nova", methods=["GET", "POST"])
def nova_tarefa():
    if request.method == "POST":
        titulo = request.form.get("titulo", "")
        tarefas.append({"id": len(tarefas) + 1, "titulo": titulo})
        return redirect(url_for("index"))
    return render_template("nova.html")

O redirect redireciona o navegador de volta para a lista de tarefas, uma boa prática para evitar que o formulário seja reenviado ao atualizar a página.

Juntando tudo

Você agora tem os três pilares de uma aplicação web clássica:

  • Python (Flask) — controla a lógica e as rotas;
  • Jinja2 — gera as páginas HTML dinamicamente;
  • Banco de dados — guarda os dados de forma permanente (veja nosso artigo sobre SQLite com Flask).

Próximos passos

Com essas bases você pode explorar:

  • CSS e JavaScript para estilizar e dar interatividade às páginas;
  • Validação de formulários e mensagens de erro amigáveis;
  • Sessões para controle de login;
  • O Django, que traz essa estrutura toda pronta e empacotada.

Transformar dados em páginas é o coração da web, e o Python faz isso de forma surpreendentemente simples.

Tem alguma página que gostaria de construir? Compartilhe nos comentários ou contribua com um artigo no nosso repositório.