Erstellen Sie Ihr eigenes Forum mit FastAPI: Schritt 2 - Integration der Datenbank
Ethan Miller
Product Engineer · Leapcell

Im vorherigen Artikel haben wir von Grund auf neu begonnen und schnell einen Prototyp eines Forums mit FastAPI erstellt. Obwohl seine Funktionalität grundlegend war, hatte er bereits die Kernmerkmale eines Forums: das Erstellen und Anzeigen von Threads.
Dieser Prototyp hat ein signifikantes Problem: Wir haben eine Python-Liste als In-Memory-Datenbank verwendet. Das bedeutet, dass jedes Mal, wenn der Server neu gestartet wird, alle von Benutzern veröffentlichten Beiträge verschwinden.
Um dieses Problem zu lösen, werden wir in diesem Artikel eine echte Datenbank in unser Forum einführen: PostgreSQL, und diese über das SQLAlchemy ORM betreiben, um eine persistente Datenspeicherung zu erreichen.
Legen wir los!
Vorbereitung von PostgreSQL
Bevor Sie mit dem Tutorial beginnen, benötigen Sie eine einsatzbereite PostgreSQL-Datenbank. Sie können sie lokal installieren; Anleitungen finden Sie auf der offiziellen PostgreSQL-Website.
Eine einfachere Alternative ist die Verwendung von Leapcell, um mit einem Klick eine kostenlose Online-Datenbank zu erhalten.
Nachdem Sie ein Konto auf der Website registriert haben, klicken Sie auf "Datenbank erstellen".
Geben Sie einen Datenbanknamen ein, wählen Sie eine Bereitstellungsregion aus und Sie können die PostgreSQL-Datenbank erstellen.
Auf der neuen Seite, die erscheint, sehen Sie die Informationen, die zum Verbinden mit der Datenbank erforderlich sind. Unten befindet sich ein Bedienfeld, mit dem Sie die Datenbank direkt auf der Webseite lesen und ändern können.
Mit diesen Verbindungsinformationen können Sie direkt von verschiedenen Werkzeugen aus auf die Datenbank zugreifen, ohne weitere lokale Konfiguration.
Schritt 1: Neue Abhängigkeiten installieren
Damit Python mit PostgreSQL kommunizieren kann, benötigen wir einige neue Bibliotheken. Stellen Sie sicher, dass Ihre virtuelle Umgebung aktiviert ist, und führen Sie den folgenden Befehl aus:
pip install "sqlalchemy[asyncio]" "psycopg[binary]"
sqlalchemy
ist das beliebteste Objektrelationale Mapping (ORM) Tool im Python-Ökosystem. Es ermöglicht uns, die Datenbank mit Python-Code zu manipulieren, anstatt mühsame SQL-Anweisungen zu schreiben.
psycopg[binary]
wird verwendet, um PostgreSQL und Python zu verbinden. SQLAlchemy verwendet es, um mit der Datenbank zu kommunizieren.
Schritt 2: Datenbankverbindung herstellen
Erstellen Sie eine neue Datei namens database.py
, die speziell für die Handhabung aller Datenbankverbindungs-bezogenen Konfigurationen vorgesehen ist.
database.py
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker from sqlalchemy.orm import DeclarativeBase # 1. Datenbank-URL # Format: "postgresql+psycopg://<user>:<password>@<host>:<port>/<dbname>" DATABASE_URL = "postgresql+psycopg://your_user:your_password@localhost/fastapi_forum_db" # 2. Erstellen Sie die Datenbank-Engine engine = create_async_engine(DATABASE_URL) SessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=engine) # 3. Basisklasse erstellen # Unsere ORM-Modelle werden später von dieser Klasse erben class Base(DeclarativeBase): pass
create_async_engine
erstellt eine SQLAlchemy-Engine, das Kernstück für die Kommunikation mit der Datenbank.SessionLocal
wird verwendet, um Datenbankoperationen (Erstellen, Lesen, Aktualisieren, Löschen) durchzuführen.- Die
Base
-Klasse wird die Basisklasse für alle Datenbankmodelle (Datentabellen) in diesem Tutorial sein.
Schritt 3: Datenmodell definieren
Jetzt müssen wir den Speicher nicht mehr als unsere Datenbank verwenden. Erstellen wir ein SQLAlchemy-Modell, um die Struktur der posts
-Tabelle in der Datenbank wirklich zu definieren.
Erstellen Sie eine neue Datei namens models.py
:
models.py
from sqlalchemy import Column, Integer, String from .database import Base class Post(Base): __tablename__ = "posts" id = Column(Integer, primary_key=True, index=True) title = Column(String, index=True) content = Column(String)
Diese Post
-Klasse entspricht direkt der Struktur der posts
-Tabelle:
__tablename__ = "posts"
: Gibt den entsprechenden Tabellennamen in der Datenbank an.id
: Ein Integer-Primärschlüssel, für den ein Index erstellt wurde, um Abfragen zu beschleunigen.title
undcontent
: Felder vom Typ String.
Beachten Sie, dass die Definition des Modells nicht bedeutet, dass die Tabelle bereits in der Datenbank existiert. Sie müssen SQL oder ähnliche Befehle ausführen, um diese Tabelle manuell zu erstellen.
Das entsprechende SQL lautet:
CREATE TABLE posts ( id SERIAL PRIMARY KEY, title VARCHAR, content TEXT );
Wenn Sie Ihre Datenbank mit Leapcell erstellt haben, können Sie die SQL-Anweisungen direkt auf deren Webseite eingeben, um die Datenbank zu ändern.
Schritt 4: API für die Verwendung der Datenbank refaktorieren
Dies ist der wichtigste Schritt. Wir müssen die In-Memory-Liste db
vollständig entfernen und die API-Routenfunktionen so ändern, dass sie über eine SQLAlchemy-Sitzung mit PostgreSQL interagieren.
Fügen Sie zuerst eine Abhängigkeitsfunktion in database.py
hinzu:
database.py
(Funktion hinzufügen)
# ... vorheriger Code bleibt unverändert ... # Abhängigkeit: Eine Datenbanksitzung erhalten async def get_db(): async with SessionLocal() as session: yield session
Nun können wir Depends(get_db)
verwenden, um eine Datenbanksitzung in unseren API-Pfadoperation-Funktionen zu erhalten.
Nachfolgend finden Sie die endgültige, vollständige Version von main.py
, die vollständig auf die Verwendung der Datenbank umgestellt wurde.
main.py
(Endgültige vollständige Version)
from fastapi import FastAPI, Form, Depends from fastapi.responses import HTMLResponse, RedirectResponse from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, desc from typing import List from . import models from .database import engine, get_db app = FastAPI() # --- HTML-Vorlage --- def generate_html_response(posts: List[models.Post]): posts_html = "" for post in posts: # reversed() nicht mehr nötig, wir können in der Abfrage sortieren posts_html += f""" <div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 10px;"> <h3>{post.title} (ID: {post.id})</h3> <p>{post.content}</p> </div> """ html_content = f""" <html> <head> <title>Mein FastAPI Forum</title> <style> body {{ font-family: sans-serif; margin: 2em; }} input, textarea {{ width: 100%; padding: 8px; margin-bottom: 10px; box-sizing: border-box; }} button {{ padding: 10px 15px; background-color: #007BFF; color: white; border: none; cursor: pointer; }} button:hover {{ background-color: #0056b3; }} </style> </head> <body> <h1>Willkommen in meinem Forum</h1> <h2>Neuen Beitrag erstellen</h2> <form action="/api/posts" method="post"> <input type="text" name="title" placeholder="Titel des Beitrags" required><br> <textarea name="content" rows="4" placeholder="Inhalt des Beitrags" required></textarea><br> <button type="submit">Posten</button> </form> <hr> <h2>Beitragsliste</h2> {posts_html} </body> </html> """ return HTMLResponse(content=html_content, status_code=200) # --- Routen --- @app.get("/", response_class=RedirectResponse) def read_root(): return "/posts" # Route zum Anzeigen der Seite @app.get("/posts", response_class=HTMLResponse) async def view_posts(db: AsyncSession = Depends(get_db)): # 1. Alle Beiträge aus der Datenbank abfragen result = await db.execute(select(models.Post).order_by(desc(models.Post.id))) posts = result.scalars().all() # 2. Das HTML rendern return generate_html_response(posts) @app.post("/api/posts") async def create_post( title: str = Form(...), content: str = Form(...), db: AsyncSession = Depends(get_db) ): # 1. Ein neues Post-Objekt erstellen new_post = models.Post(title=title, content=content) # 2. Es zur Datenbanksitzung hinzufügen db.add(new_post) # 3. Committen und in der Datenbank speichern await db.commit() # 4. Das Objekt aktualisieren, um die neu generierte ID zu erhalten await db.refresh(new_post) return RedirectResponse(url="/posts", status_code=303)
Die obigen Schritte haben Folgendes erreicht:
- Entfernen der In-Memory-Liste
db
. - Alle Routenfunktionen, die mit der Datenbank interagieren, wurden in
async def
geändert, undawait
wird vor Datenbankoperationen verwendet. Dies liegt daran, dass wir einen asynchronen Datenbanktreiber und eine asynchrone Engine gewählt haben. GET /posts
undPOST /api/posts
wurden geändert, um aus der Datenbank zu lesen und in sie zu schreiben.
Ausführen und Überprüfen
Starten Sie nun Ihren uvicorn-Server neu:
uvicorn main:app --reload
Öffnen Sie Ihren Browser und besuchen Sie http://127.0.0.1:8000
. Eine leere Beitragsliste wird angezeigt (da die Datenbank brandneu ist).
Versuchen Sie, einige neue Beiträge zu veröffentlichen. Sie werden wie zuvor angezeigt.
Testen Sie als Nächstes die Datenpersistenz:
- Drücken Sie
Strg+C
im Terminal, um den uvicorn-Server herunterzufahren. - Starten Sie den Server neu.
- Besuchen Sie
http://127.0.0.1:8000
erneut.
Sie werden feststellen, dass die zuvor veröffentlichten Beiträge noch vorhanden sind! Ihre Forum-Daten befinden sich jetzt in PostgreSQL und sind persistent gespeichert.
Bereitstellung des Projekts online
Genau wie im ersten Tutorial können Sie die Ergebnisse dieses Schritts online bereitstellen, damit Ihre Freunde die Änderungen und den Fortschritt Ihres Projekts erleben können.
Eine einfache Bereitstellungslösung ist die Verwendung von Leapcell.
Wenn Sie zuvor bereitgestellt haben, pushen Sie einfach den Code in Ihr Git-Repository, und Leapcell stellt den neuesten Code automatisch für Sie bereit.
Wenn Sie den Bereitstellungsservice von Leapcell noch nie genutzt haben, können Sie das Tutorial in diesem Artikel nachschlagen.
Zusammenfassung
In diesem Tutorial haben wir erfolgreich den Backend-Speicher des Forums von einer unzuverlässigen In-Memory-Liste auf eine robuste PostgreSQL-Datenbank migriert.
Sie haben jedoch vielleicht bemerkt, dass unsere main.py
-Datei mit großen HTML-Strings überladen ist. Dies erschwert die Wartung des Codes.
Im nächsten Artikel führen wir das Konzept einer "Template-Engine" mit Jinja2 ein, um den HTML-Code in unabhängige Vorlagendateien auszulagern, was die Lesbarkeit und Wartbarkeit des Frontend-Codes vereinfachen wird.