Crée ta propre application de bibliothèque personnelle avec Flask + MySQL

Crée ta propre application de bibliothèque personnelle avec Flask + MySQL

Tu veux apprendre à coder une application web utile et concrète, pas juste un tuto théorique ?
Dans ce guide, tu vas créer une appli de gestion de bibliothèque avec Flask, MySQL et Bootstrap : fiches de livres, filtres, statistiques et dashboard responsive.


Pourquoi ce projet est parfait pour apprendre

Concret
Tu vas développer une application autour des livres, un sujet simple et accessible à tous. Pas besoin de connaissances techniques pointues pour comprendre la finalité : tu construis un outil clair, utile et motivant.

Complet
Ce projet couvre toutes les briques essentielles d’une application web moderne : gestion de contenu (CRUD), filtres dynamiques, affichage de statistiques, et création d’un mini-dashboard interactif. C’est un véritable terrain d’entraînement pour monter en compétence.

Moderne
Tu utiliseras des technologies actuelles et professionnelles comme Flask 3+, PyMySQL, Bootstrap 5.3 et Chart.js 4, te préparant ainsi aux standards du développement web Python.

Réutilisable
La structure du projet est suffisamment générique pour être adaptée à d’autres thématiques : gestion de films, de recettes, de jeux vidéo ou tout autre contenu basé sur des fiches.

Progressif
Le tutoriel est pensé étape par étape, avec une montée en complexité douce. Il est parfaitement adapté pour un rapport de stage, un projet étudiant, ou pour étoffer ton portfolio avec une application vraiment fonctionnelle.


Prérequis & installation

Avant de commencer, assure-toi de disposer des outils suivants pour suivre ce tutoriel dans de bonnes conditions :

Python 3.10 ou plus
Installe la dernière version de Python depuis le site officiel si ce n’est pas déjà fait. C’est la base de ton environnement de développement.

MySQL ou MariaDB
Tu auras besoin d’un serveur de base de données relationnelle pour stocker les livres. Une installation locale suffit, mais tu peux aussi utiliser une instance distante.

Visual Studio Code (VS Code)
C’est l’éditeur recommandé pour ce projet, car il offre une excellente intégration avec Python, Git, les environnements virtuels, et les extensions utiles au quotidien.

Mise en place de l’environnement

Une fois les outils installés, voici comment configurer ton projet proprement :

1. Crée un environnement virtuel
Cela permet d’isoler tes dépendances Python et d’éviter les conflits entre projets.

python -m venv venv

2. Active l’environnement virtuel

  • Sous Windows :
.\venv\Scripts\activate
  • Sous macOS/Linux :
source venv/bin/activate

3. Installe les dépendances nécessaires
Grâce au fichier requirements.txt, toutes les bibliothèques utiles sont installées en une commande :

pip install -r requirements.txt

Structure complète du projet

Avant de coder, voici un aperçu de l’arborescence de ton projet Flask. Chaque dossier et fichier a un rôle clair dans l’architecture MVC :

bibliotheque/
├── app.py                # Initialise Flask et PyMySQL
├── run.py                # Lance l’appli en mode debug
├── config.py             # Paramètres & secrets centralisés
├── .env                  # Variables sensibles (non commitées)
├── requirements.txt      # Dépendances Python

├── db/
│   └── init_db.sql       # Script SQL de création + données de test

├── controllers/
│   └── book_controller.py  # Routes & logique métier

├── models/
│   └── book_model.py       # Requêtes SQL vers la base

├── utils/
│   └── helpers.py          # Fonctions utilitaires (ex : dictfetchall)

├── templates/            # HTML + Jinja2 (structure des vues)
│   ├── layout.html
│   ├── index.html
│   ├── add_book.html
│   ├── edit_book.html
│   └── stats.html

└── static/               # Fichiers statiques (CSS, JS, images)
    ├── css/style.css
    └── js/chart.js

Pourquoi cette arborescence ?

ÉlémentRôlePourquoi c’est important
app.pyInitialise Flask, connecte MySQL, enregistre les routesPoint central de l’application
config.pyStocke les paramètres de connexion et les clés secrètesTout est centralisé, facile à modifier
models/Contient les classes qui interagissent avec la baseRespect du MVC : séparation claire
controllers/Logique métier et routes HTTPLisibilité, modularité, testabilité
templates/Gère les vues HTML via Jinja2Structure propre et réutilisable
static/CSS, JS, imagesMeilleures performances et organisation
db/Script SQL pour la baseFacilite l’installation ou la réinitialisation du projet
.envStocke les infos sensiblesNe doit jamais être versionné (.gitignore)

Configuration & secrets

Pour que ton application fonctionne correctement et reste sécurisée, il est essentiel de séparer les informations sensibles (comme les identifiants MySQL ou la clé secrète de Flask) du code principal. C’est là qu’interviennent les fichiers config.py et .env.

config.py – Centralise les paramètres et charge .env

import os
from dotenv import load_dotenv

load_dotenv()  # Charge automatiquement le fichier .env

class Config:
    SECRET_KEY        = os.getenv("SECRET_KEY", "devkey")
    MYSQL_HOST        = os.getenv("MYSQL_HOST", "localhost")
    MYSQL_USER        = os.getenv("MYSQL_USER", "root")
    MYSQL_PASSWORD    = os.getenv("MYSQL_PASSWORD", "")
    MYSQL_DB          = os.getenv("MYSQL_DB", "bibliotheque")
    MYSQL_CURSORCLASS = "DictCursor"  # Retourne les résultats en dictionnaires

L’avantage est que tu peux modifier les paramètres de connexion ou de sécurité sans toucher au code, simplement en changeant les valeurs dans .env.

.env – Ne jamais versionner ce fichier

Ajoute-le à ton .gitignore pour éviter de publier tes mots de passe ou clés secrètes. Le fichier .env contient toutes les informations que tu ne veux surtout pas publier : mots de passe, clés secrètes, identifiants, etc. Il n’est pas obligatoire de créer un environnement virtuel, mais fortement recommandé.

SECRET_KEY=une_vraie_clef_ultra_secrete
MYSQL_USER=root
MYSQL_PASSWORD=monMDP
MYSQL_DB=bibliotheque

Point d’entrée Flask

Ton application Flask est découpée en deux fichiers : app.py pour la configuration globale, et run.py pour le lancement en développement.

app.py — Le cœur de ton application Flask

Le fichier app.py sert à construire et configurer ton application Flask. C’est là que tout est rassemblé :

  • L’instanciation de l’objet Flask
  • Le chargement des paramètres depuis config.py
  • L’initialisation des extensions (comme MySQL)
  • L’injection de dépendances (comme le modèle dans le contrôleur)
  • L’enregistrement des blueprints (routes organisées)
# app.py
from flask import Flask
from flask_mysqldb import MySQL
from config import Config
from models.book_model import BookModel
from controllers.book_controller import book_bp

mysql = MySQL()  # instance unique

def create_app() -> Flask:
    app = Flask(__name__)
    app.config.from_object(Config)
    mysql.init_app(app)  # Connexion MySQL via flask_mysqldb

    # Injection du modèle dans le contrôleur
    model = BookModel(mysql)

    import controllers.book_controller as book_controller
    book_controller.model = model

    app.register_blueprint(book_bp)
    return app

app = create_app()

app.py ne lance pas l’application lui-même : il la prépare. Cela permet de réutiliser l’application ailleurs, par exemple en production avec un serveur comme Gunicorn ou uWSGI.

run.py — Lancement local en mode développement

Ce fichier est très court, mais essentiel pendant le développement. Il importe l’application Flask construite dans app.py et la lance en mode debug.

# run.py
from app import app

if __name__ == "__main__":
    app.run(debug=True)  # Auto‑reload et affichage des erreurs en clair

Ce que fait run.py :

  • Démarre le serveur de développement intégré de Flask
  • Active le rechargement automatique à chaque modification de code
  • Affiche les messages d’erreur détaillés en cas de plantage

🧪 À utiliser uniquement en local.
En production, on utilisera un serveur WSGI comme Gunicorn qui se base directement sur app.py.


Le modèle : book_model.py

Le fichier book_model.py contient toute la logique d’accès à la base de données. Il correspond à la lettre M dans l’architecture MVC (Modèle – Vue – Contrôleur). Son objectif est de centraliser les requêtes SQL, d’interagir avec la base via un curseur MySQL, et de retourner les données sous forme de dictionnaires pour les routes Flask.

from utils.helpers import dictfetchall
from datetime import date, timedelta

class BookModel:
    def __init__(self, mysql):
        self.db = mysql

    # --- CRUD --------------------------------------------------
    def get_all_books(self):
        cur = self.db.connection.cursor()
        cur.execute("SELECT * FROM books ORDER BY id DESC")
        res = dictfetchall(cur)
        cur.close()
        return res

    def get_filtered_books(self, status=None, author=None):
        sql, params = "SELECT * FROM books WHERE 1=1", []
        if status:
            sql += " AND status=%s"; params.append(status)
        if author:
            sql += " AND author LIKE %s"; params.append(f"%{author}%")
        sql += " ORDER BY id DESC"

        cur = self.db.connection.cursor()
        cur.execute(sql, params)
        res = dictfetchall(cur)
        cur.close()
        return res

    def insert_book(self, data):
        cur = self.db.connection.cursor()
        cur.execute("""
            INSERT INTO books (title, author, summary, read_date, note, status)
            VALUES (%s,%s,%s,%s,%s,%s)
        """, (
            data['title'],
            data['author'],
            data['summary'],
            data['read_date'] or None,
            data['note'] or None,
            data['status']
        ))
        self.db.connection.commit()
        cur.close()

    # ... méthodes update_book, delete_book à l’identique ...

    # --- Statistiques ------------------------------------------
    def stats_summary(self):
        cur = self.db.connection.cursor()
        cur.execute("""
            SELECT COUNT(*) total,
                   SUM(status='à lire')  to_read,
                   SUM(status='en cours') reading,
                   SUM(status='lu')      finished,
                   ROUND(AVG(note),2)    avg_note
            FROM books
        """)
        row = cur.fetchone()
        cur.close()
        return row

    def stats_status(self):
        cur = self.db.connection.cursor()
        cur.execute("SELECT status, COUNT(*) nb FROM books GROUP BY status")
        rows = dictfetchall(cur)
        cur.close()
        return {
            "labels": [r['status'] for r in rows],
            "values": [r['nb'] for r in rows]
        }

    def stats_monthly(self):
        cur = self.db.connection.cursor()
        cur.execute("""
            SELECT DATE_FORMAT(read_date,'%Y-%m') ym, COUNT(*) nb
            FROM books
            WHERE status='lu' AND read_date IS NOT NULL
              AND read_date >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
            GROUP BY ym ORDER BY ym
        """)
        rows = dictfetchall(cur)
        cur.close()

        labels, values = [], []
        today = date.today().replace(day=1)
        for i in range(11, -1, -1):
            label = (today - timedelta(days=i * 30)).strftime("%Y-%m")
            labels.append(label)
            values.append(next((r['nb'] for r in rows if r['ym'] == label), 0))

        return {"labels": labels, "values": values}

Importance : Centraliser les requêtes SQL dans ce fichier évite les duplications, rend le code testable et respecte le principe MVC.

Les routes : book_controller.py

Le contrôleur gère toutes les routes Flask : il récupère les données depuis le modèle, les transmet aux templates, et gère les actions de l’utilisateur.

from flask import Blueprint, render_template, request, redirect, url_for, flash

book_bp = Blueprint("book_bp", __name__)
model = None  # Injecté depuis app.py

@book_bp.route("/")
def index():
    status = request.args.get("status")
    author = request.args.get("author")
    books = model.get_filtered_books(status, author) if (status or author) else model.get_all_books()
    return render_template("index.html", books=books)

@book_bp.route("/add", methods=["GET", "POST"])
def add_book():
    if request.method == "POST":
        model.insert_book(request.form)
        flash("Livre ajouté ✅")
        return redirect(url_for("book_bp.index"))
    return render_template("add_book.html")

@book_bp.route("/edit/<int:book_id>", methods=["GET", "POST"])
def edit_book(book_id):
    book = model.get_by_id(book_id)
    if not book:
        flash("Livre introuvable", "warning")
        return redirect(url_for("book_bp.index"))

    if request.method == "POST":
        model.update_book(book_id, request.form)
        flash("Mise à jour réussie 💾")
        return redirect(url_for("book_bp.index"))

    return render_template("edit_book.html", book=book)

@book_bp.route("/delete/<int:book_id>")
def delete_book(book_id):
    model.delete_book(book_id)
    flash("Livre supprimé 🗑️")
    return redirect(url_for("book_bp.index"))

@book_bp.route("/stats")
def stats():
    return render_template("stats.html",
        summary_cards=[
            {"label": "Total",       "value": model.stats_summary()["total"]},
            {"label": "À lire",      "value": model.stats_summary()["to_read"]},
            {"label": "En cours",    "value": model.stats_summary()["reading"]},
            {"label": "Terminés",    "value": model.stats_summary()["finished"]},
            {"label": "Note moy.",   "value": model.stats_summary()["avg_note"] or "–"},
        ],
        stats_status=model.stats_status(),
        stats_monthly=model.stats_monthly()
    )

Ce fichier correspond à la lettre C de l’architecture MVC : le contrôleur.
Il fait le lien entre :

  • les modèles (accès aux données),
  • les vues (fichiers HTML),
  • et les actions utilisateur (ajout, édition, suppression…).

À quoi sert le contrôleur ?

Il utilise flash() pour afficher des messages de retour utilisateur

Il gère les routes HTTP accessibles via le navigateur (comme /, /add, /edit/5, etc.)

Il interagit avec le modèle pour récupérer ou modifier des données

Il transmet les données aux templates HTML


Helpers utilitaires

Ton fichier utils/helpers.py contient une simple fonction pour récupérer les résultats SQL sous forme de dictionnaires. Un petit outil très pratique qui t’évitera de répéter ce code partout.

# utils/helpers.py
def dictfetchall(cursor):
    """Transforme le résultat en liste de dictionnaires."""
    return [row for row in cursor.fetchall()]

Cette fonction dictfetchall transforme le résultat d’un curseur SQL en une liste de dictionnaires, ce qui rend les données bien plus faciles à manipuler dans Flask, notamment pour les afficher dans les templates HTML.

C’est un outil simple mais indispensable dans une application qui interagit souvent avec une base de données.

Templates HTML (Bootstrap 5)

Ton design repose sur un template principal layout.html que tous les autres fichiers héritent. Il gère la navigation, le style sombre et les blocs de contenu.

<!-- templates/layout.html -->
<!DOCTYPE html>
<html lang="fr" data-bs-theme="dark">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>{% block title %}Ma Bibliothèque{% endblock %}</title>
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
  <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
  {% block extra_head %}{% endblock %}
</head>
<body>
  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <a class="navbar-brand ms-3" href="{{ url_for('book_bp.index') }}">📚 Bibliothèque</a>
    <button class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#nav">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="nav">
      <ul class="navbar-nav ms-auto me-3">
        <li class="nav-item">
          <a class="nav-link {% if request.endpoint=='book_bp.index' %}active{% endif %}" href="{{ url_for('book_bp.index') }}">Livres</a>
        </li>
        <li class="nav-item">
          <a class="nav-link {% if request.endpoint=='book_bp.stats' %}active{% endif %}" href="{{ url_for('book_bp.stats') }}">Statistiques</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="{{ url_for('book_bp.add_book') }}">➕ Ajouter</a>
        </li>
      </ul>
    </div>
  </nav>

  <main class="container py-4">
    {% with messages = get_flashed_messages() %}
      {% if messages %}
        {% for m in messages %}
          <div class="alert alert-info">{{ m }}</div>
        {% endfor %}
      {% endif %}
    {% endwith %}
    {% block content %}{% endblock %}
  </main>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
  {% block extra_js %}{% endblock %}
</body>
</html>

CSS & Chart.js

Tu peux personnaliser rapidement l’apparence et les graphes avec deux petits fichiers statiques.

static/css/style.css (extrait)

:root {
  --accent: #6366f1;
}
body {
  background: #111827;
  color: #f1f5f9;
}
.btn-accent {
  background: var(--accent);
  border: none;
}
.btn-accent:hover {
  background: #4f46e5;
}
.card-dark {
  background: #1f2937;
  border: 1px solid #334155;
  border-radius: 1rem;
}

static/js/chart.js

document.addEventListener("DOMContentLoaded", () => {
  const parseJSON = id => JSON.parse(document.getElementById(id).textContent);

  // Doughnut chart : répartition par statut
  const st = parseJSON("jsonStatus");
  new Chart(document.getElementById("chartStatus"), {
    type: "doughnut",
    data: { labels: st.labels, datasets: [{ data: st.values }] },
    options: { plugins: { legend: { position: "bottom" } } }
  });

  // Line chart : progression mensuelle
  const mo = parseJSON("jsonMonthly");
  new Chart(document.getElementById("chartMonthly"), {
    type: "line",
    data: { labels: mo.labels, datasets: [{ data: mo.values, tension: .3, fill: true }] },
    options: {
      scales: { y: { beginAtZero: true } },
      plugins: { legend: { display: false } }
    }
  });
});

Le CSS et le JavaScript jouent un rôle essentiel dans l’expérience utilisateur de l’application.

Le CSS — Pour un design cohérent et moderne

Le fichier style.css permet de personnaliser l’apparence de ton application au-delà du style par défaut de Bootstrap. Il apporte une identité visuelle à ton interface : couleurs, boutons personnalisés, cartes sombres, etc.
Un design soigné rend l’application plus agréable à utiliser et plus crédible dans un contexte pro (stage, démonstration, portfolio…).

JavaScript — Pour l’interactivité et la visualisation

Le fichier chart.js utilise Chart.js 4 pour générer dynamiquement des graphiques interactifs dans la page Statistiques. Il transforme des données brutes en visuels clairs, digestes et responsives.

Grâce à JavaScript, l’utilisateur peut visualiser l’évolution de ses lectures, la répartition des statuts, ou encore la moyenne des notes, sans recharger la page.


Script SQL de base

Ton fichier db/init_db.sql crée la base, la table books et insère 20 enregistrements réalistes pour tes tests.

-- Création de la base
CREATE DATABASE IF NOT EXISTS bibliotheque CHARACTER SET utf8mb4;
USE bibliotheque;

DROP TABLE IF EXISTS books;

CREATE TABLE books (
  id INT AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  author VARCHAR(255),
  summary TEXT,
  read_date DATE,
  note TINYINT CHECK (note BETWEEN 0 AND 10),
  status ENUM('à lire','en cours','lu') DEFAULT 'à lire'
);

Le fichier init_db.sql, situé dans le dossier db/, contient toutes les instructions nécessaires pour créer la base de données, définir la table principale et ajouter des données de test.

Il te permet d’initialiser rapidement ton environnement de développement ou de réinstaller la base à l’identique en une seule commande.

Tu peux te rendre sur ton serveur si tu l’as installé en local, ici j’utilises Wampserver, et créer ta bdd soit graphiquement ou à partir du script

Pour exécuter le script si tu veux le lancer à partir du ficher init.sql:

mysql -u root -p < db/init_db.sql

Lancer l’application

Une fois tous les fichiers en place, la base de données créée, et les dépendances installées, il ne te reste plus qu’à démarrer ton application Flask pour la tester en local. Tu es prêt à voir ton projet en action ? Rien de plus simple :

python run.py

Ensuite, ouvre ton navigateur à l’adresse suivante :
http://127.0.0.1:5000

Teste les fonctionnalités :

  • ➕ Ajoute un livre
  • ✏️ Modifie-le
  • 📊 Consulte les statistiques
    🎯 Tu as entre les mains un vrai dashboard responsive !

Félicitations ! 🎓 Tu viens de créer une application web complète avec Flask, MySQL, Bootstrap et Chart.js, en suivant une approche claire, modulaire et moderne.

Tu maîtrises désormais :

  • Le CRUD complet avec filtres dynamiques
  • L’organisation MVC propre et scalable
  • L’utilisation de templates Bootstrap avec Jinja2
  • L’affichage de statistiques interactives via Chart.js
  • La séparation entre configuration, logique métier et vues

Et maintenant ?

Tu peux télécharger le projet complet sur GitHub pour l’adapter, l’améliorer ou simplement l’étudier :
👉 Voir le dépôt GitHub

Des questions, suggestions ou idées d’amélioration ?
N’hésite pas à laisser un commentaire, je répondrai avec plaisir.

N’hésites pas aussi à consulte les autres articles du blog et à t’abonner à la NewsLetter pour ne rien rater. A bientôt !

Comments

No comments yet. Why don’t you start the discussion?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *