Programa
FastHTML e MongoDB oferecem uma abordagem ágil e nativa em Python para o desenvolvimento web moderno. Neste tutorial, vamos construir um aplicativo de gerenciador de tarefas reativo e em tempo real, demonstrando um ciclo CRUD completo (Create, Read, Update, Delete) em um único arquivo Python, fácil de manter. Para colocar a mão na massa com MongoDB em Python, recomendo o curso Introduction to MongoDB in Python.
O que é FastHTML?
FastHTML é um framework minimalista e de alta performance, construído sobre as bases do FastAPI. Ele introduz um paradigma de HTML em Python, permitindo que você crie frontends inteiros com funções Python reutilizáveis, em vez de templates tradicionais.
Seu grande diferencial está na integração nativa com HTMX. Usando atributos HTML simples para acionar atualizações no servidor, o HTMX permite criar experiências de single-page application sem a complexidade de um stack pesado em JavaScript.
O que é MongoDB?
MongoDB é o principal banco NoSQL de uso geral baseado em documentos. Seu esquema flexível e o uso de documentos BSON (semelhantes a JSON) o tornam ideal para desenvolvimento moderno e iterativo.
Para Python, usamos o driver assíncrono oficial, Motor, que oferece uma interface não bloqueante — perfeita para a arquitetura de alto desempenho do FastAPI e do FastHTML.
Por que este stack se destaca
A combinação dessas tecnologias cria um ambiente de desenvolvimento extremamente produtivo:
- Segurança de tipos com Pydantic e MongoDB: o FastHTML aproveita o Pydantic para modelagem e validação de dados. Esses modelos se mapeiam naturalmente à estrutura de documentos do MongoDB, oferecendo uma experiência "code-first" que elimina boilerplate pesado de ORMs.
- Performance assíncrona de ponta a ponta: ao combinar o Motor com o núcleo assíncrono do FastHTML, as operações no banco não bloqueiam o event loop. Isso garante alta concorrência e baixa latência — essenciais para apps reativos e em tempo real.
- Menos troca de contexto: você gerencia esquema do banco, lógica de backend e componentes de frontend dentro de um ecossistema Python unificado, acelerando bastante a entrega.
Configuração e conexão
Você vai precisar de alguns pré-requisitos para acompanhar este tutorial:
- Python 3.8+
- Instância MongoDB: instalação local ou um cluster MongoDB Atlas (recomendado para recursos em tempo real)
- Conhecimentos básicos: familiaridade com decorators em Python e estrutura básica de HTML.
Inicialização do projeto
Para mostrar a eficiência deste stack, vamos implementar todo o aplicativo — incluindo configuração do banco, modelos de dados e rotas reativas — em um único arquivo app.py.
Instalação
Vamos precisar do framework FastHTML, do Motor (driver MongoDB assíncrono oficial) e do Uvicorn para o servidor ASGI.
Configuração da conexão com o MongoDB
Usamos AsyncIOMotorClient para estabelecer uma conexão não bloqueante. Assim, enquanto o app aguarda o I/O do banco, ele continua processando outras requisições concorrentes.
import os
from motor.motor_asyncio import AsyncIOMotorClient
from fasthtml.common import *
from bson import ObjectId
from pydantic import Field, BaseModel
from pymongo.errors import ServerSelectionTimeoutError, ConnectionFailure
# configure MongoDB
MONGO_URI = os.environ.get("MONGO_URI", "mongodb://localhost:27017/") # your Mongo URI
DB_NAME = "fasthtml_tasks_db"
COLLECTION_NAME = "tasks"
# Initialize the async client and reference our collections
client = AsyncIOMotorClient(MONGO_URI)
db = client[DB_NAME]
collection = db[COLLECTION_NAME]
# FastHTML Application Initialization
app = FastHTML()
Definindo o modelo de dados
Em um fluxo orientado a documentos, o esquema vive no seu código de aplicação. Usamos o Pydantic v2 para fazer a ponte entre o ObjectId BSON do MongoDB e strings Python padrão. Isso garante que todo documento que entra ou sai do banco seja validado conforme nossos requisitos.
Definimos uma classe PyObjectId personalizada. Isso é necessário porque o Pydantic não sabe lidar nativamente com o tipo ObjectId do MongoDB. Com __get_pydantic_core_schema__, instruímos o Pydantic a tratar esse tipo como string em JSON, mas validá-lo como BSON no banco.
python
from pydantic import BaseModel, Field
from pydantic_core import core_schema
from pydantic.json_schema import JsonSchemaValue
class PyObjectId(ObjectId):
"""Custom type to bridge MongoDB ObjectId and Pydantic v2 validation."""
@classmethod
def __get_pydantic_core_schema__(cls, source_type, handler):
# Defines how Pydantic should validate this type
return core_schema.no_info_plain_validator_function(cls.validate)
@classmethod
def validate(cls, v):
if isinstance(v, ObjectId): return v
if isinstance(v, str) and ObjectId.is_valid(v): return ObjectId(v)
raise ValueError("Invalid ObjectId")
@classmethod
def __get_pydantic_json_schema__(cls, _core_schema, handler) -> JsonSchemaValue:
# Ensures the OpenAPI/JSON schema reflects this as a string
return {"type": "string"}
class Task(BaseModel):
"""Pydantic model representing the Task document schema."""
# Maps MongoDB's internal '_id' to a developer-friendly 'id' field
id: PyObjectId | None = Field(None, alias='_id')
title: str
description: str | None = None
completed: bool = False
model_config = ConfigDict(
arbitrary_types_allowed=True
)
Layout e rota inicial
O componente de layout é uma função de ordem superior reutilizável que envolve nossas views. Assim, dependências essenciais, como Tailwind CSS para estilo e HTMX para interatividade, ficam consistentes em toda a aplicação.
Ao usar os componentes em caixa-alta do FastHTML (como Main e Div), mantemos uma estrutura limpa e pythônica que espelha a árvore HTML.
# A responsive layout using Tailwind CSS and the HTMX CDN
def layout(*comps):
"""Wraps the application content in a consistent container."""
return Main(
Div(
H1('📝 Real-Time FastHTML Task Manager',
cls='text-3xl font-bold mb-8 text-center text-gray-800'),
*comps,
cls='container mx-auto p-6 max-w-2xl'
)
)
@app.get('/')
async def home():
"""Initial route rendering the core application layout."""
return layout(
await TaskList(), # Fetches and displays existing tasks (Read)
TaskForm() # Renders the entry form (Create)
)
Agora, ao executar o app, sua interface ficará assim:

Imagem: captura de tela após a configuração
Ainda não há tarefas. Vamos adicionar e atualizar tarefas na próxima seção.
Implementação CRUD completa
Read: exibindo tarefas (GET /)
Para exibir as tarefas, buscamos todos os documentos e renderizamos dentro de um componente. Porém, em um cenário real, o banco pode estar offline. Nossa versão atualizada de TaskList lida com isso de forma elegante, oferecendo passos de troubleshooting direto na interface.
Busca de dados resiliente
Usamos collection.find().to_list(length=None) para recuperar documentos de forma assíncrona. Ao envolver isso em um bloco try/except, conseguimos detectar se o MongoDB está desconectado e dar feedback imediato ao usuário.
async def TaskList():
"""Fetches documents from MongoDB and hydrates the Task list view with error handling."""
try:
tasks_data = await collection.find().to_list(length=None)
tasks = [Task(**doc) for doc in tasks_data]
except (ServerSelectionTimeoutError, ConnectionFailure, Exception) as e:
# Catch MongoDB connection errors and provide troubleshooting tips
return Div(
H2('Current Tasks', cls='text-xl font-semibold mb-4'),
Div(
P('⚠️ MongoDB is not running.', cls='text-red-600 font-semibold mb-2'),
P('To start MongoDB:', cls='text-gray-600 mb-1'),
P('1. brew install mongodb-community', cls='text-gray-500 text-sm ml-4'),
P('2. brew services start mongodb-community', cls='text-gray-500 text-sm ml-4'),
cls='p-4 bg-yellow-50 border border-yellow-200 rounded'
),
id='task-list'
)
return Div(
H2('Current Tasks', cls='text-xl font-semibold mb-4'),
*[TaskItem(task) for task in tasks] if tasks else P('No tasks yet. Add one below!', cls='text-gray-500 italic p-4'),
id='task-list',
cls='bg-white rounded-lg shadow-sm border border-gray-200'
)
Create: adicionando uma nova tarefa
O fluxo de criação usa um formulário HTML com atributos HTMX. Na versão atualizada, extraímos explicitamente os dados do formulário do objeto Request e usamos model_dump para preparar o documento para inserção no MongoDB.
@app.post('/add-task')
async def add_task(req: Request):
"""Create: Inserts a task and returns the refreshed list."""
try:
form_data = await req.form()
task = Task(
title=form_data.get('title', ''),
description=form_data.get('description') or None
)
# model_dump(by_alias=True) ensures 'id' is converted back to '_id' for Mongo
task_dict = task.model_dump(exclude_none=True, by_alias=True)
# Remove _id if it's None so MongoDB can generate its own unique ID
if '_id' in task_dict and task_dict['_id'] is None:
del task_dict['_id']
await collection.insert_one(task_dict)
return await TaskList()
except Exception as e:
return Div(P(f'Error: {str(e)}', cls='text-red-500 p-4'), id='task-list')
Se executarmos o código abaixo no terminal:
curl -X POST http://localhost:8000/add-task -d "title=Complete FastHTML MongoDB integration" -d "description=Verify that tasks can be created, updated, and deleted successfully" -H "Content-Type: application/x-www-form-urlencoded"
Você verá que a nova tarefa foi adicionada:
Imagem: captura de tela após adicionar uma tarefa
Update: alternando a conclusão
Para lidar com atualizações, usamos uma requisição PATCH. Isso demonstra um "micro-refresh", em que apenas a linha da tarefa é atualizada no banco e re-renderizada na UI usando seu ID específico.
@app.patch('/toggle-task/{task_id}')
async def toggle_task(task_id: str):
"""Update: Toggles completion status and returns the single row fragment."""
task_doc = await collection.find_one({"_id": ObjectId(task_id)})
if not task_doc: raise HTTPException(404, "Task not found")
await collection.update_one(
{"_id": ObjectId(task_id)},
{"$set": {"completed": not task_doc['completed']}}
)
# Return only the updated TaskItem for a surgical DOM update
updated_doc = await collection.find_one({"_id": ObjectId(task_id)})
return TaskItem(Task(**updated_doc))
Delete: removendo uma tarefa
A exclusão é iniciada pelo HTMX. Assim que o MongoDB confirma a remoção, o servidor retorna uma resposta Empty(). O HTMX interpreta essa resposta vazia como um sinal para remover o elemento alvo (closest div) do DOM.
@app.delete('/delete-task/{task_id}')
async def delete_task(task_id: str):
"""Delete: Removes a task from MongoDB."""
await collection.delete_one({"_id": ObjectId(task_id)})
# Signal HTMX to remove the element
return Empty()
Se fizermos…
curl -X DELETE http://localhost:8000/delete-task/695968244236010c04f313fa
…a tarefa é excluída.

Imagem: captura de tela após a exclusão
Você encontra o script completo no GitHub.
Conclusão
Ao adotar um stack moderno e centrado em Python, você construiu um app reativo que evita a complexidade tradicional dos frameworks JavaScript no cliente. Essa arquitetura oferece diversas vantagens para o desenvolvimento web atual. A sinergia entre a UI baseada em componentes do FastHTML e o modelo flexível de documentos do MongoDB permite manter lógica de negócio, integridade dos dados e apresentação em um único ecossistema coeso. Essa abordagem "tudo em Python" reduz significativamente o esforço de desenvolvimento e a complexidade de deploy.
Próximos passos para você
- Autenticação de usuários: use dependências do FastAPI para restringir listas de tarefas a usuários específicos, armazenando user_id no documento da tarefa.
- Consultas avançadas: use o framework de agregação do MongoDB ou filtros simples para adicionar as visões "Ativas" e "Concluídas" na sua UI.
- Deploy: publique seu app.py usando Uvicorn atrás de um proxy reverso NGINX para performance de produção.
FAQs
É possível usar MongoDB Change Streams com FastHTML para atualizações "push"?
Sim! Como tanto o Motor quanto o FastHTML são assíncronos, você pode usar um loop async for em Python para escutar um Change Stream do MongoDB. Depois, é só combinar com o EventStream do FastHTML (Server-Sent Events) para enviar atualizações em tempo real a todos os usuários conectados sempre que um documento mudar no banco.
Por que usar modelos Pydantic em vez de dicionários Python brutos com MongoDB?
Embora o MongoDB aceite dicionários brutos, o Pydantic funciona como o "esquema da aplicação". Ele oferece validação de dados, type hints e valores padrão (como definir completed como False automaticamente). Isso evita que "dados sujos" entrem na coleção e facilita muito o debug conforme o projeto cresce.
Como lidar com migrações de banco de dados nesse stack?
Uma das grandes forças do MongoDB é o esquema flexível. Você não precisa de "migrações" no sentido tradicional do SQL. Se adicionar um novo campo ao seu modelo Task, basta fornecer um valor padrão no Pydantic. Documentos existentes no MongoDB que não tenham esse campo serão "hidratados" com o valor padrão quando forem carregados na aplicação.
Posso adicionar busca avançada a este gerenciador de tarefas?
Com certeza. O MongoDB tem um índice $text poderoso e um Atlas Search ainda mais avançado (baseado em Lucene). Você pode criar facilmente uma barra de busca no FastHTML usando hx-get que dispara um pipeline de agregação no MongoDB para filtrar tarefas por palavras-chave conforme o usuário digita.
Como esse stack lida com alta concorrência em comparação a Django ou Flask?
FastHTML é um framework separado inspirado no FastAPI. Ele usa o padrão ASGI e consegue lidar com milhares de conexões simultâneas em um único processo. Em conjunto com o pool de conexões não bloqueante do Motor, seu app não fica "preso" aguardando respostas do banco, tornando-se muito mais eficiente para apps em tempo real e com alto tráfego.
Karen é uma engenheira de dados apaixonada por criar plataformas de dados escalonáveis. Ela tem experiência em automação de infraestrutura com o Terraform e está animada para compartilhar seus conhecimentos em postagens de blog e tutoriais. Karen é uma construtora de comunidades e é apaixonada por promover conexões entre profissionais de dados.

