Développer un mini-blog est un excellent exercice pour comprendre la logique d’une application web complète. Vous allez construire une partie publique pour afficher les articles, puis une interface d’administration sécurisée pour publier, modifier et supprimer du contenu, avec une base MySQL, des sessions PHP et des requêtes préparées.

Introduction

Quand on débute en développement web, on apprend souvent à afficher une page, puis à traiter un formulaire. Le vrai cap est franchi quand on transforme ces briques en une petite application capable de gérer un contenu de bout en bout, avec une partie publique et une interface d’administration privée.

Dans ce tutoriel, vous allez créer un mini-blog fonctionnel. Les visiteurs pourront lire les articles, tandis que l’administrateur pourra se connecter, publier un billet, le modifier plus tard ou le supprimer si nécessaire.

L’objectif est de construire une base claire, pédagogique et évolutive. À la fin, vous disposerez d’un mini CMS simple, mais formateur.

Ce que vous allez construire

Le projet contiendra les éléments suivants :

  • Une page d’accueil qui liste les articles
  • Une page qui affiche un article complet
  • Une page de connexion administrateur avec session PHP
  • Un tableau de bord simple
  • Un formulaire d’ajout d’article
  • Une page de modification d’article
  • Une action de suppression d’article avec identifiant transmis dans l’URL

Ce fonctionnement correspond à une logique classique : création, lecture, modification et suppression (CRUD en anglais).

Arborescence du projet

Voici l’arborescence finale du projet :

mini-blog/
├── config/
│ └── database.php
├── admin/
│ ├── login.php
│ ├── dashboard.php
│ ├── create-post.php
│ ├── posts.php
│ ├── edit-post.php
│ ├── delete-post.php
│ ├── logout.php
│ └── auth.php
├── includes/
│ ├── header.php
│ └── footer.php
├── index.php
├── article.php
└── style.css

Cette structure sépare la configuration, les pages publiques, les pages d’administration et les morceaux de mise en page réutilisables. Elle reste volontairement légère pour garder le projet simple à comprendre.

Prérequis

Avant de commencer, vous devez disposer de :

  • PHP installé en local.
  • MySQL ou MariaDB.
  • Un serveur local de type XAMPP, MAMP, WAMP ou Laragon.
  • Un éditeur de code comme VS Code.
  • Quelques bases en HTML, CSS et SQL.

L’ensemble du tutoriel repose sur une logique simple : un article = une ligne dans la base de données, et l’interface d’administration = une série de pages protégées par session. C’est précisément le socle que l’on retrouve dans de nombreux mini CMS en PHP.

Étape 1 : créer la base de données

La première étape consiste à créer la base MySQL. Nous allons utiliser deux tables : une pour l’administrateur, une pour les articles.

Créez une base nommée mini_blog, puis exécutez ce script SQL :

CREATE DATABASE mini_blog CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

USE mini_blog;

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(100) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    role ENUM('admin', 'user') DEFAULT 'admin'
);

CREATE TABLE posts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Cette structure suit une approche classique : la table users sert à l’authentification, tandis que la table posts stocke les contenus du blog. Ce principe est cohérent avec les exemples d’admin dashboard PHP qui s’appuient sur un système de connexion et des tables métiers distinctes.

Pour pouvoir vous connecter par la suite, il faut insérer un utilisateur administrateur. Le plus propre consiste à stocker un mot de passe haché avec password_hash(), car la vérification se fera ensuite avec password_verify(), qui est précisément la méthode montrée dans les exemples d’authentification PHP.

Créez temporairement un fichier generate-password.php (admin123 correspondant à votre mot de passe) :

<?php
echo password_hash('admin123', PASSWORD_DEFAULT); 
?>

Exécutez-le une fois, copiez le hash obtenu, puis insérez l’utilisateur :

INSERT INTO users (username, password, role)
VALUES ('admin', 'COLLEZ_ICI_LE_HASH', 'admin');

Votre identifiant sera tout simplement admin.

Étape 2 : configurer la connexion à la base de données

Créez un dossier config, puis à l’intérieur, un fichier database.php :

<?php

$host = 'localhost';
$dbname = 'mini_blog';
$username = 'root';
$password = '';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die("Erreur de connexion : " . $e->getMessage());
}

Ce fichier centralise la connexion PDO. C’est une bonne pratique, car toutes les pages auront besoin d’accéder à la base sans répéter le même code partout. En fonction de votre serveur, vous devez personnaliser l’host, le dbname (= nom de la base de donnée), l’username (user de la base de donnée) et password (mot de passe de l’user de la base de donnée). Sur un environnement wamp, généralement l’user est ROOT, sans password (comme dans notre exemple).

Étape 3 : créer la mise en page publique

Avant de développer l’administration, mettons en place l’interface visible par les visiteurs. Nous allons créer un en-tête et un pied de page réutilisables.

Créez un dossier includes, et à l’intérieur, le fichier header.php :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mini Blog</title>
    <link rel="stylesheet" href="/mini-blog/style.css">
</head>
<body>
    <header class="site-header">
        <div class="container">
            <h1>Mini Blog</h1>
            <nav>
                <a href="/mini-blog/index.php">Accueil</a>
                <a href="/mini-blog/admin/login.php">Administration</a>
            </nav>
        </div>
    </header>
    <main class="container">

Puis toujours dans le dossier includes, créez le fichier footer.php :

    </main>
    <footer class="site-footer">
        <div class="container">
            <p>&copy; 2026 - Mini Blog</p>
        </div>
    </footer>
</body>
</html>

Puis, à la racine, créez le fichier style.css :

body {
    margin: 0;
    font-family: Arial, sans-serif;
    background: #f4f4f4;
    color: #222;
}

.container {
    width: 90%;
    max-width: 1000px;
    margin: 0 auto;
}

.site-header,
.site-footer {
    background: #222;
    color: #fff;
    padding: 20px 0;
}

.site-header .container,
.site-footer .container {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.site-header a {
    color: #fff;
    text-decoration: none;
    margin-left: 15px;
}

.post-card {
    background: #fff;
    padding: 20px;
    margin: 20px 0;
    border-radius: 8px;
}

.post-card h2 {
    margin-top: 0;
}

.btn {
    display: inline-block;
    background: #0077cc;
    color: #fff;
    padding: 10px 14px;
    text-decoration: none;
    border-radius: 4px;
}

form {
    background: #fff;
    padding: 20px;
    border-radius: 8px;
    margin: 20px 0;
}

input, textarea {
    width: 100%;
    padding: 10px;
    margin-top: 6px;
    margin-bottom: 15px;
    box-sizing: border-box;
}

La mise en forme reste volontairement simple pour garder le focus sur la logique applicative. C’est un fichier que vous pourrez ensuite personnaliser à votre guise pour modifier l’apparence de votre application.

Étape 4 : afficher la liste des articles

A la racine, créez le fichier index.php :

<?php
require_once 'config/database.php';
include 'includes/header.php';

$stmt = $pdo->query("SELECT * FROM posts ORDER BY created_at DESC");
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>

<h2>Derniers articles</h2>

<?php if ($posts): ?>
    <?php foreach ($posts as $post): ?>
        <article class="post-card">
            <h2><?= htmlspecialchars($post['title']) ?></h2>
            <p>
                <?= nl2br(htmlspecialchars(substr($post['content'], 0, 200))) ?>...
            </p>
            <a class="btn" href="article.php?id=<?= $post['id'] ?>">Lire la suite</a>
        </article>
    <?php endforeach; ?>
<?php else: ?>
    <p>Aucun article pour le moment.</p>
<?php endif; ?>

<?php include 'includes/footer.php'; ?>

Ici, la logique est simple : on récupère tous les billets, du plus récent au plus ancien, puis on les parcourt dans une boucle. L’usage de htmlspecialchars() est indispensable pour éviter d’afficher du contenu non échappé dans la page.

Étape 5 : afficher un article complet

A la racine, créez le fichier article.php :

<?php
require_once 'config/database.php';
include 'includes/header.php';

if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
    die("Article introuvable.");
}

$id = (int) $_GET['id'];

$stmt = $pdo->prepare("SELECT * FROM posts WHERE id = ?");
$stmt->execute([$id]);
$post = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$post) {
    die("Article inexistant.");
}
?>

<article class="post-card">
    <h2><?= htmlspecialchars($post['title']) ?></h2>
    <p><small>Publié le <?= $post['created_at'] ?></small></p>
    <div>
        <?= nl2br(htmlspecialchars($post['content'])) ?>
    </div>
</article>

<?php include 'includes/footer.php'; ?>

Cette page récupère un identifiant dans l’URL, vérifie qu’il est valide, puis charge le bon article. L’utilisation d’une requête préparée permet de sécuriser l’accès aux données, ce qui est essentiel dès qu’un paramètre utilisateur intervient dans une requête SQL.

Étape 6 : protéger l’administration avec une session

Passons maintenant à la zone d’administration. Dans un tableau de bord PHP classique, l’accès se fait via une authentification, puis toutes les pages privées vérifient qu’une session valide existe avant de s’afficher.

Créez un dossier admin, et à l’intérieur le fichier auth.php :

<?php
session_start();

if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
    header('Location: login.php');
    exit();
}

Ce petit fichier sera inclus en haut de chaque page admin. Il joue le rôle de gardien : si l’utilisateur n’est pas connecté, il est immédiatement redirigé vers la page de connexion.

Étape 7 : créer la page de connexion

Dans le dossier admin, créer la page login.php

<?php
session_start();
require_once '../config/database.php';

$error = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username']);
    $password = trim($_POST['password']);

    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
    $stmt->execute([$username]);
    $user = $stmt->fetch(PDO::FETCH_ASSOC);

    if ($user && password_verify($password, $user['password'])) {
        $_SESSION['admin_logged_in'] = true;
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['username'] = $user['username'];

        header('Location: dashboard.php');
        exit();
    } else {
        $error = "Identifiants invalides.";
    }
}
?>

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Connexion admin</title>
    <link rel="stylesheet" href="../style.css">
</head>
<body>
    <main class="container">
        <h1>Connexion à l’administration</h1>

        <?php if ($error): ?>
            <p style="color:red;"><?= htmlspecialchars($error) ?></p>
        <?php endif; ?>

        <form method="POST">
            <label for="username">Nom d’utilisateur</label>
            <input type="text" name="username" id="username" required>

            <label for="password">Mot de passe</label>
            <input type="password" name="password" id="password" required>

            <button class="btn" type="submit">Se connecter</button>
        </form>
    </main>
</body>
</html>

Cette logique reprend le schéma classique d’un login PHP : récupération de l’utilisateur, vérification du mot de passe haché, création de variables de session, puis redirection vers le dashboard.

Étape 8 : créer le tableau de bord

Créez dans le dossier admin, la page dashboard.php :

<?php
require_once 'auth.php';
require_once '../config/database.php';

$stmt = $pdo->query("SELECT COUNT(*) FROM posts");
$postCount = $stmt->fetchColumn();
?>

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard</title>
    <link rel="stylesheet" href="../style.css">
    <style>
        .admin-layout {
            display: flex;
            min-height: 100vh;
        }

        .sidebar {
            width: 240px;
            background: #222;
            color: white;
            padding: 20px;
        }

        .sidebar a {
            display: block;
            color: white;
            text-decoration: none;
            margin: 10px 0;
        }

        .admin-content {
            flex: 1;
            padding: 30px;
            background: #f7f7f7;
        }

        .stat-box {
            background: white;
            padding: 20px;
            border-radius: 8px;
            margin-top: 20px;
        }

        .quick-links a {
            margin-right: 10px;
        }
    </style>
</head>
<body>
    <div class="admin-layout">
        <aside class="sidebar">
            <h2>Admin</h2>
            <p>Bonjour, <?= htmlspecialchars($_SESSION['username']) ?></p>
            <a href="dashboard.php">Tableau de bord</a>
            <a href="create-post.php">Nouvel article</a>
            <a href="posts.php">Gérer les articles</a>
            <a href="logout.php">Déconnexion</a>
        </aside>

        <section class="admin-content">
            <h1>Tableau de bord</h1>
            <p>Bienvenue dans votre interface d’administration.</p>

            <div class="stat-box">
                <h3>Statistiques</h3>
                <p>Nombre total d’articles : <?= $postCount ?></p>
            </div>

            <div class="stat-box quick-links">
                <h3>Actions rapides</h3>
                <a class="btn" href="create-post.php">Ajouter un article</a>
                <a class="btn" href="posts.php">Voir tous les articles</a>
            </div>
        </section>
    </div>
</body>
</html>

Un dashboard admin contient généralement une navigation latérale, une zone centrale et quelques indicateurs clés. Même sur un mini-projet, cette organisation rend la gestion plus claire et suit les codes habituels d’un panneau d’administration.

Étape 9 : ajouter un formulaire de publication

Toujours dans le dossier admin, créez la page create-post.php

<?php
require_once 'auth.php';
require_once '../config/database.php';

$message = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $title = trim($_POST['title']);
    $content = trim($_POST['content']);

    if (!empty($title) && !empty($content)) {
        $stmt = $pdo->prepare("INSERT INTO posts (title, content) VALUES (?, ?)");
        $stmt->execute([$title, $content]);
        $message = "Article publié avec succès.";
    } else {
        $message = "Tous les champs sont obligatoires.";
    }
}
?>

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Nouvel article</title>
    <link rel="stylesheet" href="../style.css">
</head>
<body>
    <main class="container">
        <h1>Publier un article</h1>

        <?php if ($message): ?>
            <p><?= htmlspecialchars($message) ?></p>
        <?php endif; ?>

        <form method="POST">
            <label for="title">Titre</label>
            <input type="text" name="title" id="title" required>

            <label for="content">Contenu</label>
            <textarea name="content" id="content" rows="10" required></textarea>

            <button class="btn" type="submit">Publier</button>
        </form>

        <p><a href="dashboard.php">Retour au tableau de bord</a></p>
    </main>
</body>
</html>

Ce formulaire envoie les données en POST, vérifie qu’elles ne sont pas vides, puis insère un nouveau billet dans la table posts. On tient ici le cœur du mini-blog : l’administrateur peut désormais publier du contenu qui apparaîtra automatiquement côté public.

Étape 10 : lister les articles dans l’admin

Créez admin/posts.php :

<?php
require_once 'auth.php';
require_once '../config/database.php';

$stmt = $pdo->query("SELECT * FROM posts ORDER BY created_at DESC");
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gérer les articles</title>
    <link rel="stylesheet" href="../style.css">
    <style>
        .actions a {
            margin-right: 10px;
        }

        .btn-delete {
            background: #c62828;
        }
    </style>
</head>
<body>
    <main class="container">
        <h1>Liste des articles</h1>

        <?php if ($posts): ?>
            <?php foreach ($posts as $post): ?>
                <article class="post-card">
                    <h2><?= htmlspecialchars($post['title']) ?></h2>
                    <p><small><?= htmlspecialchars($post['created_at']) ?></small></p>
                    <p><?= nl2br(htmlspecialchars(substr($post['content'], 0, 150))) ?>...</p>

                    <p class="actions">
                        <a class="btn" href="edit-post.php?id=<?= $post['id'] ?>">Modifier</a>
                        <a class="btn btn-delete"
                           href="delete-post.php?id=<?= $post['id'] ?>"
                           onclick="return confirm('Voulez-vous vraiment supprimer cet article ?');">
                           Supprimer
                        </a>
                    </p>
                </article>
            <?php endforeach; ?>
        <?php else: ?>
            <p>Aucun article enregistré.</p>
        <?php endif; ?>

        <p><a href="dashboard.php">Retour au tableau de bord</a></p>
    </main>
</body>
</html>

Cette page fait deux choses utiles. Elle affiche la liste des billets existants, puis ajoute deux liens d’action par article, ce qui correspond à la forme la plus simple d’un tableau CRUD administrable.

Le lien “Modifier” envoie vers une page dédiée, tandis que le lien “Supprimer” pointe vers un script de suppression et affiche une boîte de confirmation JavaScript. Cette confirmation ne remplace pas la sécurité côté serveur, mais elle évite les suppressions accidentelles.

Étape 11 : créer la page de modification d’un article

La modification repose sur une logique en deux temps. D’abord, on récupère l’article concerné grâce à son identifiant pour préremplir le formulaire ; ensuite, au moment de la soumission, on exécute une requête UPDATE avec des paramètres sécurisés.

Créez le fichier admin/edit-post.php :

<?php
require_once 'auth.php';
require_once '../config/database.php';

if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
    die("Article introuvable.");
}

$id = (int) $_GET['id'];

$stmt = $pdo->prepare("SELECT * FROM posts WHERE id = ?");
$stmt->execute([$id]);
$post = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$post) {
    die("Article inexistant.");
}

$message = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $title = trim($_POST['title']);
    $content = trim($_POST['content']);

    if (!empty($title) && !empty($content)) {
        $stmt = $pdo->prepare("UPDATE posts SET title = ?, content = ? WHERE id = ?");
        $stmt->execute([$title, $content, $id]);

        $message = "Article mis à jour avec succès.";

        $stmt = $pdo->prepare("SELECT * FROM posts WHERE id = ?");
        $stmt->execute([$id]);
        $post = $stmt->fetch(PDO::FETCH_ASSOC);
    } else {
        $message = "Tous les champs sont obligatoires.";
    }
}
?>

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Modifier un article</title>
    <link rel="stylesheet" href="../style.css">
</head>
<body>
    <main class="container">
        <h1>Modifier l’article</h1>

        <?php if ($message): ?>
            <p><?= htmlspecialchars($message) ?></p>
        <?php endif; ?>

        <form method="POST">
            <label for="title">Titre</label>
            <input
                type="text"
                name="title"
                id="title"
                value="<?= htmlspecialchars($post['title']) ?>"
                required
            >

            <label for="content">Contenu</label>
            <textarea
                name="content"
                id="content"
                rows="10"
                required
            ><?= htmlspecialchars($post['content']) ?></textarea>

            <button class="btn" type="submit">Enregistrer les modifications</button>
        </form>

        <p><a href="posts.php">Retour à la liste des articles</a></p>
    </main>
</body>
</html>

Le point clé ici est le préremplissage du formulaire. On charge d’abord le billet à partir de son id, puis on réinjecte ses valeurs dans les champs afin que l’administrateur puisse corriger un titre ou réécrire le contenu sans repartir de zéro.

Lors de l’envoi du formulaire, la requête UPDATE posts SET title = ?, content = ? WHERE id = ? applique les changements de manière ciblée.

Étape 12 : créer le script de suppression

Pour supprimer un article, le plus simple est de créer un fichier dédié qui reçoit un identifiant, vérifie qu’il est valide, exécute une requête DELETE, puis redirige vers la liste des articles.

Créez admin/delete-post.php :

<?php
require_once 'auth.php';
require_once '../config/database.php';

if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
    die("Identifiant invalide.");
}

$id = (int) $_GET['id'];

$stmt = $pdo->prepare("DELETE FROM posts WHERE id = ?");
$stmt->execute([$id]);

header('Location: posts.php');
exit();

Le fonctionnement est volontairement court. On valide l’identifiant, on prépare la requête de suppression, puis on redirige immédiatement vers la liste pour éviter qu’un rechargement du navigateur ne reproduise l’action.

Étape 13 : gérer la déconnexion

Créez admin/logout.php :

<?php
session_start();
session_unset();
session_destroy();

header('Location: login.php');
exit();

La déconnexion détruit la session active et renvoie l’utilisateur vers le formulaire de connexion. C’est la fin normale du cycle d’authentification dans une interface d’administration.

Bilan

Avec ce mini-blog, vous avez mis en place un projet complet de développement web : base de données, affichage public, authentification, tableau de bord et publication d’articles. Vous comprenez maintenant comment relier le front office et le back office dans une petite application PHP structurée.

Une fois cette base en place, vous pouvez faire évoluer le mini-blog de plusieurs façons : Ajouter la modification d’un article. Ajouter la suppression d’un article. Gérer une image à la une. Ajouter un champ excerpt pour les résumés. Introduire des catégories. Gérer le statut brouillon ou publié. Mettre en place un éditeur enrichi. Nous vous proposerons, au mois d’août, certaines de ces améliorations ;).