Chuyển đến nội dung chính

Xây dựng Trình quản lý Công việc Thời gian Thực với FastHTML và MongoDB

Hướng dẫn đầy đủ về sử dụng công cụ thuần Python cho các thao tác CRUD async và tính tương tác HTMX.
Đã cập nhật 17 thg 6, 2026  · 5 phút đọc

FastHTML và MongoDB mang đến một cách tiếp cận hiện đại, tốc độ cao và thuần Python cho phát triển web. Trong hướng dẫn này, chúng ta sẽ xây dựng một ứng dụng trình quản lý công việc phản ứng, thời gian thực, minh họa trọn vẹn vòng đời CRUD (Tạo, Đọc, Cập nhật, Xóa) trong một tệp Python duy nhất, dễ bảo trì. Để thực hành sử dụng MongoDB trong Python, tôi gợi ý khóa học Giới thiệu về MongoDB trong Python.

FastHTML là gì?

FastHTML là một framework tối giản, hiệu năng cao xây dựng trên nền tảng FastAPI. Nó giới thiệu một mô hình HTML theo phong cách Pythonic, cho phép nhà phát triển xây dựng toàn bộ frontend bằng các hàm Python có thể tái sử dụng thay vì các template truyền thống.

Điểm mạnh cốt lõi của nó là tích hợp nguyên bản với HTMX. Bằng cách dùng các thuộc tính HTML đơn giản để điều khiển cập nhật phía máy chủ, HTMX cho phép bạn tạo trải nghiệm ứng dụng một trang động mà không cần đến một bộ công cụ nặng JavaScript.

MongoDB là gì?

MongoDB là cơ sở dữ liệu NoSQL dạng tài liệu, mục đích chung hàng đầu. Lược đồ linh hoạt và việc sử dụng các tài liệu BSON giống JSON khiến nó lý tưởng cho phát triển hiện đại, lặp tiến.

Với Python, chúng ta dùng driver bất đồng bộ chính thức, Motor, cung cấp giao diện non-blocking rất phù hợp với kiến trúc định hướng hiệu năng của FastAPI và FastHTML.

Vì sao bộ công nghệ này vượt trội

Kết hợp các công nghệ này tạo ra một môi trường phát triển đặc biệt hiệu quả:

  • Tính an toàn kiểu với Pydantic và MongoDB: FastHTML tận dụng Pydantic cho mô hình hóa và kiểm chứng dữ liệu. Các mô hình này khớp tự nhiên với cấu trúc tài liệu của MongoDB, mang lại trải nghiệm "code-first" và loại bỏ nhu cầu mã khuôn (boilerplate) ORM nặng nề.
  • Hiệu năng async đầu-cuối: Kết hợp Motor với lõi bất đồng bộ của FastHTML giúp các thao tác cơ sở dữ liệu không bao giờ chặn vòng lặp sự kiện. Điều này đảm bảo mức đồng thời cao và độ trễ thấp, rất quan trọng cho các ứng dụng thời gian thực, phản ứng.
  • Giảm chuyển ngữ cảnh: Nhà phát triển có thể quản lý lược đồ cơ sở dữ liệu, logic backend, và thành phần frontend trong một hệ sinh thái Python thống nhất, giúp tăng tốc độ bàn giao đáng kể.

Thiết lập và Kết nối

Có một vài điều kiện tiên quyết bạn cần có để theo kịp hướng dẫn này: 

  • Python 3.8+
  • Phiên bản MongoDB: cài đặt local hoặc cụm MongoDB Atlas (khuyến nghị cho tính năng thời gian thực)
  • Kiến thức nền tảng: quen thuộc với decorator của Python và cấu trúc HTML cơ bản.

Khởi tạo dự án

Để minh họa tính hiệu quả của bộ công nghệ này, chúng ta sẽ triển khai toàn bộ ứng dụng, gồm cấu hình cơ sở dữ liệu, mô hình dữ liệu và các route phản ứng, trong một tệp app.py duy nhất.

Cài đặt 

Chúng ta cần framework FastHTML, Motor (driver MongoDB bất đồng bộ chính thức) và Uvicorn cho máy chủ ASGI.

Thiết lập kết nối MongoDB

Chúng ta dùng AsyncIOMotorClient để thiết lập kết nối non-blocking. Điều này đảm bảo khi ứng dụng chờ I/O từ cơ sở dữ liệu, nó vẫn có thể xử lý các yêu cầu đồng thời khác.

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

Định nghĩa mô hình dữ liệu 

Trong quy trình hướng tài liệu, lược đồ nằm trong mã ứng dụng của bạn. Chúng ta dùng Pydantic v2 để bắc cầu giữa ObjectId BSON của MongoDB và chuỗi Python chuẩn. Điều này đảm bảo mọi tài liệu đi vào hoặc rời khỏi cơ sở dữ liệu đều được kiểm chứng theo yêu cầu của chúng ta.

Chúng ta định nghĩa một lớp PyObjectId tùy chỉnh. Điều này là cần thiết vì Pydantic không tự biết cách xử lý kiểu ObjectId của MongoDB. Bằng cách dùng __get_pydantic_core_schema__, chúng ta chỉ định để Pydantic coi kiểu này là chuỗi trong JSON nhưng kiểm chứng nó như một đối tượng BSON cho cơ sở dữ liệu.

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
    )

Bố cục và route ban đầu 

Thành phần bố cục là một hàm bậc cao có thể tái sử dụng để bao bọc các view của chúng ta. Điều này đảm bảo các phụ thuộc thiết yếu, như Tailwind CSS cho phần giao diện và HTMX cho tính tương tác, nhất quán trên toàn bộ ứng dụng.

Bằng cách dùng các thành phần viết hoa của FastHTML (như MainDiv), chúng ta duy trì cấu trúc Pythonic gọn gàng phản chiếu cây 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)
    )

Giờ, nếu bạn chạy ứng dụng, giao diện sẽ trông như sau:

Hình: ảnh chụp màn hình sau khi thiết lập

Chưa có công việc nào. Chúng ta sẽ bổ sung và cập nhật công việc ở phần tiếp theo.

Triển khai đầy đủ CRUD

Đọc: Hiển thị công việc (GET /)

Để hiển thị công việc, chúng ta truy xuất tất cả tài liệu và render chúng trong một thành phần. Tuy nhiên, trong thực tế, cơ sở dữ liệu có thể ngoại tuyến. Phiên bản TaskList đã cập nhật xử lý điều này một cách mượt mà bằng cách cung cấp hướng dẫn khắc phục sự cố trực tiếp trong UI.

Truy xuất dữ liệu bền bỉ 

Chúng ta dùng collection.find().to_list(length=None) để lấy tài liệu một cách bất đồng bộ. Bằng cách bao bọc trong khối try/except, chúng ta có thể phát hiện nếu MongoDB bị ngắt kết nối và cung cấp phản hồi ngay lập tức cho người dùng.

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

Tạo: Thêm công việc mới

Luồng tạo sử dụng một biểu mẫu HTML được tăng cường với các thuộc tính HTMX. Ở phiên bản cập nhật, chúng ta trích xuất dữ liệu biểu mẫu trực tiếp từ đối tượng Request và sử dụng model_dump để chuẩn bị tài liệu cho thao tác chèn vào 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')

Nếu chúng ta chạy đoạn mã dưới đây trong 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"

Bạn sẽ thấy công việc mới này đã được thêm:

Hình: ảnh chụp màn hình sau khi thêm công việc

Cập nhật: Bật/tắt trạng thái hoàn thành

Để xử lý cập nhật, chúng ta dùng yêu cầu PATCH. Điều này minh họa "làm mới vi mô", khi chỉ riêng dòng công việc đó được cập nhật trong cơ sở dữ liệu và render lại trong UI bằng ID cụ thể của nó.

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

Xóa: Gỡ một công việc

Thao tác xóa được khởi phát bởi HTMX. Khi MongoDB xác nhận việc xóa, máy chủ trả về phản hồi Empty(). HTMX hiểu phản hồi trống này như một tín hiệu để loại bỏ phần tử mục tiêu (div gần nhất) khỏi 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()

Nếu chúng ta…

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

…công việc sẽ bị xóa.

Hình: ảnh chụp màn hình sau khi xóa 

Bạn có thể tìm toàn bộ script trên GitHub.

Kết luận

Bằng cách tận dụng một bộ công nghệ hiện đại, tập trung vào Python, bạn đã xây dựng một ứng dụng phản ứng mà không cần đến sự phức tạp truyền thống của các framework JavaScript phía client. Kiến trúc này mang lại nhiều lợi thế then chốt cho phát triển web hiện đại. Sự cộng hưởng giữa UI dựa trên thành phần của FastHTML và mô hình tài liệu linh hoạt của MongoDB cho phép bạn duy trì logic nghiệp vụ, tính toàn vẹn dữ liệu và phần trình bày trong một hệ sinh thái thống nhất. Cách tiếp cận "tất cả trong Python" này giúp giảm đáng kể chi phí phát triển và độ phức tạp khi triển khai.

Các bước tiếp theo cho người đọc

  • Xác thực người dùng: Dùng các dependency của FastAPI để giới hạn danh sách công việc theo từng người dùng, lưu user_id trong tài liệu công việc.
  • Truy vấn nâng cao: Dùng khung tổng hợp của MongoDB hoặc các bộ lọc đơn giản để thêm các chế độ xem "Đang hoạt động" và "Đã hoàn thành" vào UI của bạn.
  • Triển khai: Triển khai app.py của bạn bằng Uvicorn phía sau NGINX reverse proxy để đạt hiệu năng cấp độ sản xuất.

FAQs

Có thể dùng MongoDB Change Streams với FastHTML để cập nhật "đẩy" không?

Có! Vì cả Motor và FastHTML đều bất đồng bộ, bạn có thể dùng vòng lặp async for trong Python để lắng nghe Change Stream của MongoDB. Sau đó bạn có thể kết hợp với EventStream (Server-Sent Events) của FastHTML để đẩy cập nhật thời gian thực tới mọi người dùng kết nối bất cứ khi nào có tài liệu thay đổi trong cơ sở dữ liệu.

Tại sao dùng mô hình Pydantic thay vì dict Python thuần với MongoDB?

Mặc dù MongoDB chấp nhận dict thuần, Pydantic đóng vai trò như "lược đồ ứng dụng". Nó cung cấp kiểm chứng dữ liệu, gợi ý kiểu và giá trị mặc định (như tự động đặt completed thành False). Điều này ngăn "dữ liệu bẩn" đi vào bộ sưu tập của bạn và giúp mã của bạn dễ debug hơn khi mở rộng.

Xử lý migration cơ sở dữ liệu ra sao với bộ công nghệ này?

Một trong những thế mạnh lớn nhất của MongoDB là lược đồ linh hoạt. Bạn không cần "migration" theo nghĩa SQL truyền thống. Nếu bạn thêm một trường mới vào mô hình Task, bạn chỉ cần cung cấp giá trị mặc định trong Pydantic. Các tài liệu hiện có trong MongoDB thiếu trường đó sẽ được "bổ sung" giá trị mặc định khi chúng được nạp vào ứng dụng của bạn.

Tôi có thể thêm tính năng tìm kiếm phức tạp vào trình quản lý công việc này không?

Chắc chắn rồi. MongoDB có chỉ mục $text mạnh mẽ và Atlas Search còn tiên tiến hơn (dựa trên Lucene). Bạn có thể dễ dàng tạo một ô tìm kiếm trong FastHTML bằng hx-get kích hoạt pipeline tổng hợp của MongoDB để lọc công việc theo từ khóa khi người dùng nhập.

Bộ công nghệ này xử lý mức đồng thời cao như thế nào so với Django hay Flask?

FastHTML là một framework riêng lấy cảm hứng từ FastAPI. Nó dùng tiêu chuẩn ASGI và có thể xử lý hàng nghìn kết nối đồng thời trên một tiến trình. Khi kết hợp với cơ chế kết nối pooling non-blocking của Motor, ứng dụng của bạn sẽ không bị "kẹt" khi chờ phản hồi cơ sở dữ liệu, khiến nó hiệu quả hơn nhiều cho các ứng dụng thời gian thực, lưu lượng cao.


Karen Zhang's photo
Author
Karen Zhang
LinkedIn

Karen là một Kỹ sư Dữ liệu đam mê xây dựng các nền tảng dữ liệu có khả năng mở rộng. Cô có kinh nghiệm tự động hóa hạ tầng với Terraform và háo hức chia sẻ những gì mình học được qua các bài blog và hướng dẫn. Karen là một người xây dựng cộng đồng và cô tâm huyết với việc kết nối những chuyên gia dữ liệu.

Chủ đề

Các khóa học hàng đầu trên DataCamp

Tracks

Kỹ sư dữ liệu trong Python

40 giờ
Nắm vững các kỹ năng được săn đón để thu thập, làm sạch, quản lý dữ liệu một cách hiệu quả, cũng như lên lịch và giám sát các quy trình xử lý dữ liệu, giúp bạn nổi bật trong lĩnh vực kỹ thuật dữ liệu.
Xem chi tiếtRight Arrow
Bắt đầu khóa học
Xem thêmRight Arrow