Première base SQL¶
Objectif : lire une donnée depuis MariaDB avec du SQL visible.
Ce que vous allez apprendre : créer une table via une migration SQL
versionnée et lisible, puis lire une donnée depuis MariaDB avec
fetch_one(...) et une requête SELECT écrite à la main — sans ORM ni
génération cachée.
Palier 10 de la progression officielle des starters, après Validation serveur.
Ce que ce starter montre¶
- une route
GET /first-sql - une table SQL minimale (
first_sql_messages) - une migration SQL visible
- une requête
SELECTbrute - une réponse texte avec
Response.text(...)
Aucun CRUD. Aucune entité JSON. Aucun formulaire. Aucune validation avancée. Aucune jointure.
Classes Forge utilisées¶
| Classe | Rôle dans ce starter | Référence |
|---|---|---|
Request |
Reçue par la méthode du contrôleur. | Request |
Response |
Construire la réponse texte avec Response.text(...). |
Response |
BaseController |
Classe parente du contrôleur. | BaseController |
Tester¶
Depuis le projet Forge déjà créé avec ce starter, appliquer d'abord la migration livrée puis démarrer le serveur :
Ouvrez :
Résultat attendu :
Les routes¶
# mvc/routes.py
from mvc.controllers.first_sql_controller import FirstSqlController
with router.group("", public=True) as pub:
pub.add("GET", "/first-sql", FirstSqlController.index, name="first_sql_index")
Le contrôleur¶
# mvc/controllers/first_sql_controller.py
from core.database.db import fetch_one
from core.http.request import Request
from core.http.response import Response
from core.mvc.controller.base_controller import BaseController
SELECT_FIRST_MESSAGE = "SELECT content FROM first_sql_messages ORDER BY id LIMIT 1"
class FirstSqlController(BaseController):
@staticmethod
def index(request: Request) -> Response:
row = fetch_one(SELECT_FIRST_MESSAGE)
message = row["content"] if row else "(aucun message)"
return Response.text(f"Message depuis la base : {message}")
Comprendre ce code¶
- La requête SQL est déclarée comme une chaîne Python lisible — Forge garde le SQL visible, sans génération cachée par un ORM.
fetch_one(...)exécute la requête et retourne la première ligne sous forme dedict, ouNonesi la table est vide.- Le contrôleur extrait la colonne
contentpuis la réinjecte dans une réponse texte. - Le cycle reste minimal : route → contrôleur → SQL → réponse. Dans une vraie application, on isolerait la requête dans un module modèle.
La migration¶
-- mvc/migrations/20260527120000_create_first_sql_messages.sql
CREATE TABLE IF NOT EXISTS first_sql_messages (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
content VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO first_sql_messages (content)
SELECT 'Bonjour SQL'
WHERE NOT EXISTS (
SELECT 1 FROM first_sql_messages WHERE content = 'Bonjour SQL'
);
Comprendre ce code¶
CREATE TABLE IF NOT EXISTS ...rend la migration idempotente : rejouée, elle ne lève pas d'erreur si la table existe déjà.INSERT ... WHERE NOT EXISTS (...)insère la donnée de démo une seule fois — utile pour réappliquer la migration sans dupliquer.- Les migrations Forge sont des fichiers SQL versionnés dans
mvc/migrations/;forge migration:applyles exécute dans l'ordre.
À retenir¶
- Forge garde le SQL visible : la requête
SELECTest une chaîne Python lisible, pas une méthode magique d'ORM. - La migration crée la table avec
CREATE TABLE IF NOT EXISTSet insère la donnée de démo de manière idempotente. - Le contrôleur lit la donnée avec
fetch_one(...)decore.database.db— c'est l'API officielle Forge (avec aussifetch_all,execute,insert). - Le CRUD complet vient seulement au palier suivant, qui utilise les mêmes briques en plus organisé.
Après ce starter¶
Vous savez lire une donnée en base. Passez au palier suivant : Écrire en base — insérer une ligne depuis un formulaire :