Ga naar hoofdinhoud

Bouw een realtime takenmanager met FastHTML en MongoDB

Een complete tutorial over het gebruik van Python-native tools voor asynchrone CRUD-operaties en HTMX-interactiviteit.
Bijgewerkt 17 jun 2026  · 5 min lezen

FastHTML en MongoDB bieden een snelle, Python-native aanpak voor moderne webontwikkeling. In deze tutorial bouwen we een reactieve, realtime takenmanager en laten we een complete CRUD-cyclus (Create, Read, Update, Delete) zien binnen één onderhoudbaar Python-bestand. Wil je praktisch aan de slag met MongoDB in Python? Dan raad ik de cursus Introduction to MongoDB in Python aan.

Wat is FastHTML?

FastHTML is een minimalistisch, high-performance framework gebouwd op de fundamenten van FastAPI. Het introduceert een Pythonische HTML-paradigma, waardoor je volledige frontends kunt bouwen met herbruikbare Python-functies in plaats van traditionele templates.

De kernkracht zit in de native integratie met HTMX. Door eenvoudige HTML-attributen te gebruiken voor server-side updates, kun je met HTMX dynamische single-page-ervaringen creëren zonder de complexiteit van een JavaScript-zware buildstack.

Wat is MongoDB?

MongoDB is de toonaangevende, general-purpose, documentgebaseerde NoSQL-database. De flexibele schema’s en het gebruik van JSON-achtige BSON-documenten maken het ideaal voor moderne, iteratieve ontwikkeling.

Voor Python gebruiken we de officiële asynchrone driver Motor, die een niet-blokkerende interface biedt die perfect past bij de performancegerichte architectuur van FastAPI en FastHTML.

Waarom deze stack uitblinkt

Door deze technologieën te combineren, ontstaat een uitzonderlijk productieve ontwikkelomgeving:

  • Typeveiligheid met Pydantic en MongoDB: FastHTML gebruikt Pydantic voor datamodellering en validatie. Deze modellen sluiten naadloos aan op de documentstructuur van MongoDB en bieden een “code-first”-ervaring die de noodzaak van zware ORM-boilerplate wegneemt.
  • End-to-end async performance: Door Motor te koppelen aan de asynchrone kern van FastHTML blokkeren database-operaties nooit de eventloop. Dit zorgt voor hoge gelijktijdigheid en lage latency, cruciaal voor realtime, reactieve applicaties.
  • Minder contextswitching: Je kunt het databaseschema, de backendlogica en de frontendcomponenten beheren binnen één Python-ecosysteem, wat de opleversnelheid aanzienlijk verhoogt.

Setup en verbinding

Er zijn een paar vereisten om deze tutorial te volgen: 

  • Python 3.8+
  • MongoDB-instantie: een lokale installatie of een MongoDB Atlas-cluster (aanbevolen voor realtime features)
  • Basiskennis: bekendheid met Python-decorators en de basis HTML-structuur.

Projectinitialisatie

Om de efficiëntie van deze stack te tonen, implementeren we de volledige applicatie, inclusief databaseconfiguratie, datamodellen en reactieve routes, in één app.py-bestand.

Installatie 

We hebben het FastHTML-framework, Motor (de officiële asynchrone MongoDB-driver) en Uvicorn voor de ASGI-server nodig.

MongoDB-verbinding instellen

We gebruiken AsyncIOMotorClient om een niet-blokkerende verbinding op te zetten. Zo kan je applicatie andere gelijktijdige verzoeken blijven verwerken terwijl er op database-I/O wordt gewacht.

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()

Het datamodel definiëren 

In een documentgeoriënteerde workflow leeft het schema in je applicatiecode. We gebruiken Pydantic v2 om de kloof te overbruggen tussen MongoDB’s BSON-ObjectId en standaard Python-strings. Zo wordt elk document dat onze database in- of uitgaat gevalideerd tegen onze eisen.

We definiëren een aangepaste PyObjectId-klasse. Dit is nodig omdat Pydantic het MongoDB-ObjectId-type niet standaard begrijpt. Met __get_pydantic_core_schema__ vertellen we Pydantic dit type als string in JSON te behandelen maar het als BSON-object te valideren voor de database.

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
    )

Lay-out en initiële route 

De lay-outcomponent is een herbruikbare higher-orderfunctie die onze views omwikkelt. Zo blijven essentiële afhankelijkheden, zoals Tailwind CSS voor styling en HTMX voor interactiviteit, consistent in de hele applicatie.

Door de title-case-componenten van FastHTML te gebruiken (zoals Main en Div) behouden we een schone, Pythonische structuur die de HTML-boom weerspiegelt.

# 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)
    )

Als je de app nu draait, ziet je UI er zo uit:

Afbeelding: screenshot na setup

Er zijn nog geen taken. In de volgende sectie gaan we taken toevoegen en bijwerken.

Volledige CRUD-implementatie

Read: Taken weergeven (GET /)

Om taken weer te geven, halen we alle documenten op en renderen die in een component. In een echte omgeving kan de database echter offline zijn. Onze bijgewerkte TaskList gaat hier netjes mee om door in de UI direct stappen voor probleemoplossing te tonen.

Robuust data ophalen 

We gebruiken collection.find().to_list(length=None) om documenten asynchroon op te halen. Door dit in een try/except te wikkelen, kunnen we detecteren of MongoDB is losgekoppeld en de gebruiker meteen feedback geven.

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: Een nieuwe taak toevoegen

De creatiestroom gebruikt een HTML-formulier met HTMX-attributen. In de bijgewerkte versie halen we formulierdata expliciet uit het Request-object en gebruiken we model_dump om het document voor te bereiden voor invoeging in 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')

Als we onderstaande code in de terminal draaien:

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"

Dan zie je dat deze nieuwe taak is toegevoegd:

Afbeelding: screenshot na het toevoegen van een taak

Update: Voltooiing toggelen

Voor updates gebruiken we een PATCH-verzoek. Dit demonstreert een “micro-refresh”, waarbij alleen de enkele taakrij in de database wordt bijgewerkt en met zijn specifieke ID opnieuw in de UI wordt gerenderd.

@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: Een taak verwijderen

Verwijderen wordt gestart door HTMX. Zodra MongoDB de verwijdering bevestigt, retourneert de server een Empty()-response. HTMX interpreteert deze lege response als een signaal om het doel-element (dichtstbijzijnde div) uit de DOM te verwijderen.

@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()

Als we…

curl -X DELETE http://localhost:8000/delete-task/695968244236010c04f313fa

…doen, wordt de taak verwijderd.

Afbeelding: screenshot na verwijderen 

Je vindt het volledige script op GitHub.

Conclusie

Door een moderne, Python-centrische stack te benutten, heb je een reactieve applicatie gebouwd die de traditionele complexiteit van client-side JavaScript-frameworks vermijdt. Deze specifieke architectuur biedt verschillende belangrijke voordelen voor moderne webontwikkeling. De synergie tussen de componentgebaseerde UI van FastHTML en het flexibele documentmodel van MongoDB stelt je in staat businesslogica, dataintegriteit en presentatie te behouden binnen één samenhangend ecosysteem. Deze “alles-in-Python”-aanpak vermindert de ontwikkeloverhead en uitrolcomplexiteit aanzienlijk.

Volgende stappen voor lezers

  • Gebruikersauthenticatie: Gebruik FastAPI-dependencies om takenlijsten te beperken tot specifieke gebruikers, en sla user_id op in het taakdocument.
  • Geavanceerde queries: Gebruik MongoDB’s aggregatiekader of eenvoudige filters om “Actief”- en “Voltooid”-weergaven aan je UI toe te voegen.
  • Deployen: Zet je app.py uit met Uvicorn achter een NGINX reverse proxy voor performance op productieniveau.

FAQs

Is het mogelijk om MongoDB Change Streams met FastHTML te gebruiken voor “push”-updates?

Ja! Omdat zowel Motor als FastHTML asynchroon zijn, kun je een Python-async for-lus gebruiken om naar een MongoDB Change Stream te luisteren. Dit kun je vervolgens koppelen aan FastHTML’s EventStream (Server-Sent Events) om realtime updates te pushen naar elke verbonden gebruiker zodra een document in de database verandert.

Waarom Pydantic-modellen gebruiken in plaats van ruwe Python-dictionaries met MongoDB?

Hoewel MongoDB ruwe dictionaries accepteert, fungeert Pydantic als je “applicatieschema”. Het biedt datavalidatie, typehints en standaardwaarden (zoals completed automatisch op False zetten). Dit voorkomt dat “vuile data” je collectie binnendringt en maakt je code veel eenvoudiger te debuggen naarmate deze groeit.

Hoe ga ik om met databasemigraties met deze stack?

Een van de grootste krachten van MongoDB is het flexibele schema. Je hebt geen “migraties” in de traditionele SQL-betekenis nodig. Als je een nieuw veld toevoegt aan je Task-model, kun je eenvoudig een standaardwaarde in Pydantic opgeven. Bestaande documenten in MongoDB die dat veld missen, worden bij het laden in je applicatie “gehydrateerd” met de standaardwaarde.

Kan ik complexe zoekfuncties toevoegen aan deze takenmanager?

Zeker. MongoDB heeft een krachtige $text-index en een nog geavanceerdere Atlas Search (gebaseerd op Lucene). Je kunt eenvoudig een zoekbalk in FastHTML maken met hx-get die een MongoDB-aggregatiepijplijn triggert om taken te filteren op trefwoorden terwijl de gebruiker typt.

Hoe gaat deze stack om met hoge gelijktijdigheid vergeleken met Django of Flask?

FastHTML is een losstaand framework geïnspireerd op FastAPI. Het gebruikt de ASGI-standaard en kan duizenden gelijktijdige verbindingen op één proces afhandelen. In combinatie met Motor’s niet-blokkerende connection pooling zal je app niet “vastlopen” terwijl er op database-antwoorden wordt gewacht, waardoor deze veel efficiënter is voor high-traffic, realtime apps.


Karen Zhang's photo
Author
Karen Zhang
LinkedIn

Karen is een Data Engineer met een passie voor het bouwen van schaalbare dataplatforms. Ze heeft ervaring met infrastructuurautomatisering met Terraform en deelt haar inzichten graag in blogposts en tutorials. Karen is een community builder en ze zet zich in om verbindingen tussen dataprofessionals te stimuleren.

Onderwerpen

Topcursussen op DataCamp

Leerpad

Data-engineer in Python

40 Hr
Leer de vaardigheden die je nodig hebt om data efficiënt te verzamelen, op te schonen en te beheren, en om pijplijnen te plannen en te monitoren, zodat je je kunt onderscheiden op het gebied van data-engineering.
Bekijk detailsRight Arrow
Begin met de cursus
Meer zienRight Arrow