May 3, 2026 5 min read

Jinsi ya Kutengeneza Blog System kwa PHP na MySQL Step by Step + Full Working Code

Jifunze kutengeneza blog system kamili kwa PHP na MySQL yenye admin kuongeza posts, ku-edit, ku-delete, ku-upload picha, SEO title, meta description, slug, categories na public blog page.

1. Utangulizi
https://faulink.com
Blog ni website au sehemu ya website inayotumika kuchapisha makala, habari, tutorials, matangazo, masomo, downloads au taarifa mbalimbali. Kwa developer wa PHP, kutengeneza blog system ni project nzuri sana kwa sababu inakufundisha mambo muhimu kama:

database connection
CRUD system
file upload
admin dashboard
SEO
slug URLs
categories
image handling
public pages
security
mysqli prepared statements

Katika tutorial hii tutajenga blog system inayofanya kazi kwa kutumia:

PHP + MySQL + Bootstrap

System hii itakuwa na sehemu kuu mbili:

Admin side – kuongeza, ku-edit na kufuta blog posts.
Public side – visitors kusoma blog posts.
2. Features za Blog System

Blog system hii itakuwa na features hizi:

✅ Admin anaweza kuongeza blog post
✅ Admin anaweza ku-upload featured image
✅ Admin anaweza kuandika title
✅ Admin anaweza kuandika content
✅ Admin anaweza kuweka category
✅ Admin anaweza kuweka SEO title
✅ Admin anaweza kuweka meta description
✅ Admin anaweza ku-publish au ku-save draft
✅ Admin anaweza ku-edit post
✅ Admin anaweza ku-delete post
✅ Visitors wanaweza kuona posts zote
✅ Visitors wanaweza kusoma single post
✅ Blog ina Bootstrap design
✅ Blog ina slug URLs
✅ Blog ina prepared statements
✅ Blog ina security ya msingi
3. Folder Structure

Tengeneza folder ndani ya htdocs.

Mfano kama unatumia XAMPP:

C:\xampp\htdocs\my_blog

Folder structure iwe hivi:

my_blog/

├── config.php
├── index.php
├── post.php
├── admin.php
├── add_post.php
├── edit_post.php
├── delete_post.php
├── uploads/
│ └── blog/
└── assets/
└── style.css

Muhimu sana: folder uploads/blog/ lazima liwepo kwa ajili ya kuhifadhi picha za blog posts.

4. Kuandaa Database
https://faulink.com
Fungua phpMyAdmin:

http://localhost/phpmyadmin

Tengeneza database:

CREATE DATABASE my_blog_db;

Kisha tumia database hiyo:

USE my_blog_db;

Tengeneza table la posts:

CREATE TABLE posts (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL UNIQUE,
category VARCHAR(100) DEFAULT NULL,
content LONGTEXT NOT NULL,
image VARCHAR(255) DEFAULT NULL,
seo_title VARCHAR(255) DEFAULT NULL,
meta_description VARCHAR(300) DEFAULT NULL,
status ENUM('draft','published') DEFAULT 'published',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
5. Maelezo ya Columns

id ni unique number ya kila post.

title ni kichwa cha habari cha post.

slug ni URL friendly name. Mfano:

jinsi-ya-kutengeneza-blog

category ni kundi la post. Mfano:

PHP Tutorials

content ni body ya blog post.

image ni jina la picha iliyopakiwa.

seo_title ni title maalum kwa Google.

meta_description ni maelezo mafupi ya post kwa SEO.

status inaonyesha kama post ni draft au published.

created_at ni tarehe post ilipotengenezwa.

updated_at ni tarehe post ilipohaririwa.

6. Database Connection – config.php

Tengeneza file:

config.php

Weka code hii:

<?php
date_default_timezone_set("Africa/Dar_es_Salaam");

$host = "localhost";
$user = "root";
$password = "";
$database = "my_blog_db";

$conn = new mysqli($host, $user, $password, $database);

if ($conn->connect_error) {
die("Database connection failed.");
}

$conn->set_charset("utf8mb4");

function clean($data) {
return htmlspecialchars(trim($data), ENT_QUOTES, 'UTF-8');
}

function createSlug($string) {
$string = strtolower(trim($string));
$string = preg_replace('/[^a-z0-9\s-]/', '', $string);
$string = preg_replace('/[\s-]+/', '-', $string);
return trim($string, '-');
}

function uniqueSlug($conn, $title, $post_id = null) {
$slug = createSlug($title);
$originalSlug = $slug;
$count = 1;

while (true) {
if ($post_id) {
$stmt = $conn->prepare("SELECT id FROM posts WHERE slug = ? AND id != ?");
$stmt->bind_param("si", $slug, $post_id);
} else {
$stmt = $conn->prepare("SELECT id FROM posts WHERE slug = ?");
$stmt->bind_param("s", $slug);
}

$stmt->execute();
$result = $stmt->get_result();

if ($result->num_rows == 0) {
return $slug;
}

$slug = $originalSlug . "-" . $count;
$count++;
}
}
?>
7. Maelezo ya config.php

Line hii inaweka muda wa Tanzania:

date_default_timezone_set("Africa/Dar_es_Salaam");

Line hizi zina-connect PHP na MySQL:

$conn = new mysqli($host, $user, $password, $database);

Function hii inalinda output dhidi ya XSS:

function clean($data) {
return htmlspecialchars(trim($data), ENT_QUOTES, 'UTF-8');
}

Function hii inatengeneza slug:

function createSlug($string) {

Mfano title ikiwa:

Jinsi ya Kutengeneza Blog kwa PHP

slug itakuwa:

jinsi-ya-kutengeneza-blog-kwa-php
8. Public Blog Homepage – index.php

Hii ndiyo page ambayo visitors wataona posts zote zilizopublished.

Tengeneza file:

index.php

Weka code hii:

<?php
require_once "config.php";

$search = isset($_GET['search']) ? trim($_GET['search']) : "";
$category = isset($_GET['category']) ? trim($_GET['category']) : "";

$sql = "SELECT * FROM posts WHERE status = 'published'";
$params = [];
$types = "";

if ($search !== "") {
$sql .= " AND (title LIKE ? OR content LIKE ? OR category LIKE ?)";
$keyword = "%$search%";
$params[] = $keyword;
$params[] = $keyword;
$params[] = $keyword;
$types .= "sss";
}

if ($category !== "") {
$sql .= " AND category = ?";
$params[] = $category;
$types .= "s";
}

$sql .= " ORDER BY created_at DESC";

$stmt = $conn->prepare($sql);

if (!empty($params)) {
$stmt->bind_param($types, ...$params);
}

$stmt->execute();
$posts = $stmt->get_result();

$catResult = $conn->query("SELECT DISTINCT category FROM posts WHERE status='published' AND category IS NOT NULL AND category != '' ORDER BY category ASC");
?>
<!DOCTYPE html>
<html lang="sw">
<head>
<meta charset="UTF-8">
<title>My Blog - PHP Tutorials, News and Articles</title>
<meta name="description" content="Soma blog posts kuhusu PHP, MySQL, web development, tutorials na technology.">
<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="canonical" href="http://localhost/my_blog/&quot;&gt;
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css&quot; rel="stylesheet">
</head>

<body class="bg-light">

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a href="index.php" class="navbar-brand fw-bold">My Blog</a>
<a href="admin.php" class="btn btn-success btn-sm">Admin</a>
</div>
</nav>

<header class="bg-primary text-white py-5">
<div class="container text-center">
<h1 class="fw-bold">Karibu Kwenye Blog Yetu</h1>
<p class="lead">Soma tutorials, makala na habari mbalimbali za teknolojia.</p>
</div>
</header>

<div class="container my-4">

<form method="GET" class="row g-2 mb-4">
<div class="col-md-7">
<input type="text" name="search" class="form-control" placeholder="Search blog posts..." value="<?php echo clean($search); ?>">
</div>

<div class="col-md-3">
<select name="category" class="form-control">
<option value="">All Categories</option>
<?php while ($cat = $catResult->fetch_assoc()): ?>
<option value="<?php echo clean($cat['category']); ?>" <?php echo ($category == $cat['category']) ? 'selected' : ''; ?>>
<?php echo clean($cat['category']); ?>
</option>
<?php endwhile; ?>
</select>
</div>

<div class="col-md-2 d-grid">
<button class="btn btn-primary">Search</button>
</div>
</form>

<div class="row">
<?php if ($posts->num_rows > 0): ?>
<?php while ($post = $posts->fetch_assoc()): ?>
<div class="col-md-4 mb-4">
<div class="card h-100 shadow-sm">

<?php if (!empty($post['image'])): ?>
<img src="uploads/blog/<?php echo clean($post['image']); ?>" class="card-img-top" style="height:220px; object-fit:cover;" alt="<?php echo clean($post['title']); ?>">
<?php else: ?>
<div class="bg-secondary text-white d-flex align-items-center justify-content-center" style="height:220px;">
No Image
</div>
<?php endif; ?>

<div class="card-body">
<span class="badge bg-info text-dark mb-2">
<?php echo clean($post['category']); ?>
</span>

<h5 class="card-title">
<?php echo clean($post['title']); ?>
</h5>

<p class="text-muted small">
Posted on <?php echo date("d M Y", strtotime($post['created_at'])); ?>
</p>

<p class="card-text">
<?php echo clean(substr(strip_tags($post['content']), 0, 130)); ?>...
</p>
</div>

<div class="card-footer bg-white">
<a href="post.php?slug=<?php echo clean($post['slug']); ?>" class="btn btn-primary btn-sm">
Read More
</a>
</div>

</div>
</div>
<?php endwhile; ?>
<?php else: ?>
<div class="col-12">
<div class="alert alert-warning text-center">
No blog posts found.
</div>
</div>
<?php endif; ?>
</div>

</div>

<footer class="bg-dark text-white text-center py-3">
<p class="mb-0">&copy; <?php echo date("Y"); ?> My Blog. All Rights Reserved.</p>
</footer>

</body>
</html>
9. Single Blog Post Page – post.php

Hii ni page ya kusoma post moja kwa full details.

Tengeneza file:

post.php

Weka code hii:

<?php
require_once "config.php";

if (!isset($_GET['slug']) || trim($_GET['slug']) == "") {
http_response_code(404);
die("Post not found.");
}

$slug = trim($_GET['slug']);

$stmt = $conn->prepare("SELECT * FROM posts WHERE slug = ? AND status = 'published'");
$stmt->bind_param("s", $slug);
$stmt->execute();
$post = $stmt->get_result()->fetch_assoc();

if (!$post) {
http_response_code(404);
die("Post not found.");
}

$pageTitle = !empty($post['seo_title']) ? $post['seo_title'] : $post['title'];
$metaDescription = !empty($post['meta_description']) ? $post['meta_description'] : substr(strip_tags($post['content']), 0, 160);
?>
<!DOCTYPE html>
<html lang="sw">
<head>
<meta charset="UTF-8">
<title><?php echo clean($pageTitle); ?></title>
<meta name="description" content="<?php echo clean($metaDescription); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="canonical" href="http://localhost/my_blog/post.php?slug=&lt;?php echo clean($post['slug']); ?>">

<meta property="og:title" content="<?php echo clean($post['title']); ?>">
<meta property="og:description" content="<?php echo clean($metaDescription); ?>">
<meta property="og:type" content="article">

<?php if (!empty($post['image'])): ?>
<meta property="og:image" content="http://localhost/my_blog/uploads/blog/&lt;?php echo clean($post['image']); ?>">
<?php endif; ?>

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css&quot; rel="stylesheet">
</head>

<body class="bg-light">

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a href="index.php" class="navbar-brand fw-bold">My Blog</a>
</div>
</nav>

<div class="container my-5">
<div class="row justify-content-center">
<div class="col-md-9">

<article class="card shadow-sm">
<?php if (!empty($post['image'])): ?>
<img src="uploads/blog/<?php echo clean($post['image']); ?>" class="card-img-top" style="max-height:450px; object-fit:cover;" alt="<?php echo clean($post['title']); ?>">
<?php endif; ?>

<div class="card-body">
<span class="badge bg-primary mb-2"><?php echo clean($post['category']); ?></span>

<h1 class="fw-bold"><?php echo clean($post['title']); ?></h1>

<p class="text-muted">
Published on <?php echo date("d M Y H:i", strtotime($post['created_at'])); ?>
</p>

<hr>

<div class="blog-content">
<?php echo nl2br($post['content']); ?>
</div>
</div>
</article>

<div class="mt-3">
<a href="index.php" class="btn btn-secondary">← Back to Blog</a>
</div>

</div>
</div>
</div>

<footer class="bg-dark text-white text-center py-3">
<p class="mb-0">&copy; <?php echo date("Y"); ?> My Blog.</p>
</footer>

</body>
</html>
10. Admin Dashboard – admin.php

Hii ni page ya admin kuona posts zote, ku-edit na ku-delete.

Tengeneza file:

admin.php

Weka code hii:

<?php
require_once "config.php";

$result = $conn->query("SELECT * FROM posts ORDER BY created_at DESC");
$message = isset($_GET['message']) ? clean($_GET['message']) : "";
?>
<!DOCTYPE html>
<html lang="sw">
<head>
<meta charset="UTF-8">
<title>Admin Dashboard - Blog Posts</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css&quot; rel="stylesheet">
</head>

<body class="bg-light">

<nav class="navbar navbar-dark bg-dark">
<div class="container">
<a href="admin.php" class="navbar-brand fw-bold">Blog Admin</a>
<div>
<a href="index.php" class="btn btn-outline-light btn-sm">View Blog</a>
<a href="add_post.php" class="btn btn-success btn-sm">+ Add Post</a>
</div>
</div>
</nav>

<div class="container my-4">

<?php if ($message): ?>
<div class="alert alert-success">
<?php echo $message; ?>
</div>
<?php endif; ?>

<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">All Blog Posts</h4>
</div>

<div class="card-body table-responsive">

<table class="table table-bordered table-striped align-middle">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Image</th>
<th>Title</th>
<th>Category</th>
<th>Status</th>
<th>Created</th>
<th width="180">Action</th>
</tr>
</thead>

<tbody>
<?php if ($result->num_rows > 0): ?>
<?php while ($row = $result->fetch_assoc()): ?>
<tr>
<td><?php echo clean($row['id']); ?></td>

<td>
<?php if (!empty($row['image'])): ?>
<img src="uploads/blog/<?php echo clean($row['image']); ?>" width="70" height="50" style="object-fit:cover;">
<?php else: ?>
No Image
<?php endif; ?>
</td>

<td><?php echo clean($row['title']); ?></td>
<td><?php echo clean($row['category']); ?></td>

<td>
<?php if ($row['status'] == 'published'): ?>
<span class="badge bg-success">Published</span>
<?php else: ?>
<span class="badge bg-warning text-dark">Draft</span>
<?php endif; ?>
</td>

<td><?php echo date("d M Y", strtotime($row['created_at'])); ?></td>

<td>
<a href="edit_post.php?id=<?php echo $row['id']; ?>" class="btn btn-warning btn-sm">Edit</a>

<a href="delete_post.php?id=<?php echo $row['id']; ?>"
onclick="return confirm('Are you sure you want to delete this post?')"
class="btn btn-danger btn-sm">
Delete
</a>
</td>
</tr>
<?php endwhile; ?>
<?php else: ?>
<tr>
<td colspan="7" class="text-center text-muted">No posts available.</td>
</tr>
<?php endif; ?>
</tbody>
</table>

</div>
</div>

</div>

</body>
</html>
11. Add Blog Post – add_post.php

Hii ndiyo page ya kuongeza blog post mpya.

Tengeneza file:

add_post.php

Weka code hii:

<?php
require_once "config.php";

$errors = [];

if (isset($_POST['save'])) {
$title = trim($_POST['title']);
$category = trim($_POST['category']);
$content = trim($_POST['content']);
$seo_title = trim($_POST['seo_title']);
$meta_description = trim($_POST['meta_description']);
$status = trim($_POST['status']);

if ($title == "") {
$errors[] = "Title is required.";
}

if ($content == "") {
$errors[] = "Content is required.";
}

if (!in_array($status, ['draft', 'published'])) {
$errors[] = "Invalid status selected.";
}

$imageName = null;

if (!empty($_FILES['image']['name'])) {
$allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
$fileType = $_FILES['image']['type'];
$fileSize = $_FILES['image']['size'];
$tmpName = $_FILES['image']['tmp_name'];

if (!in_array($fileType, $allowedTypes)) {
$errors[] = "Only JPG, PNG, WEBP and GIF images are allowed.";
}

if ($fileSize > 2 * 1024 * 1024) {
$errors[] = "Image size must not exceed 2MB.";
}

if (empty($errors)) {
$extension = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$imageName = time() . "_" . uniqid() . "." . strtolower($extension);
$uploadPath = "uploads/blog/" . $imageName;

if (!move_uploaded_file($tmpName, $uploadPath)) {
$errors[] = "Failed to upload image.";
}
}
}

if (empty($errors)) {
$slug = uniqueSlug($conn, $title);

$stmt = $conn->prepare("INSERT INTO posts (title, slug, category, content, image, seo_title, meta_description, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->bind_param("ssssssss", $title, $slug, $category, $content, $imageName, $seo_title, $meta_description, $status);

if ($stmt->execute()) {
header("Location: admin.php?message=Post added successfully");
exit;
} else {
$errors[] = "Failed to save post.";
}
}
}
?>
<!DOCTYPE html>
<html lang="sw">
<head>
<meta charset="UTF-8">
<title>Add Blog Post</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css&quot; rel="stylesheet">
</head>

<body class="bg-light">

<nav class="navbar navbar-dark bg-dark">
<div class="container">
<a href="admin.php" class="navbar-brand fw-bold">Blog Admin</a>
<a href="admin.php" class="btn btn-outline-light btn-sm">Back</a>
</div>
</nav>

<div class="container my-4">
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h4 class="mb-0">Add New Blog Post</h4>
</div>

<div class="card-body">

<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach ($errors as $error): ?>
<li><?php echo clean($error); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>

<form method="POST" enctype="multipart/form-data">

<div class="mb-3">
<label class="form-label">Post Title</label>
<input type="text" name="title" class="form-control" required value="<?php echo isset($title) ? clean($title) : ''; ?>">
</div>

<div class="mb-3">
<label class="form-label">Category</label>
<input type="text" name="category" class="form-control" placeholder="Example: PHP Tutorials" value="<?php echo isset($category) ? clean($category) : ''; ?>">
</div>

<div class="mb-3">
<label class="form-label">Featured Image</label>
<input type="file" name="image" class="form-control" accept="image/*">
</div>

<div class="mb-3">
<label class="form-label">Content</label>
<textarea name="content" rows="12" class="form-control" required><?php echo isset($content) ? clean($content) : ''; ?></textarea>
</div>

<div class="mb-3">
<label class="form-label">SEO Title</label>
<input type="text" name="seo_title" class="form-control" maxlength="255" value="<?php echo isset($seo_title) ? clean($seo_title) : ''; ?>">
</div>

<div class="mb-3">
<label class="form-label">Meta Description</label>
<textarea name="meta_description" rows="3" class="form-control" maxlength="300"><?php echo isset($meta_description) ? clean($meta_description) : ''; ?></textarea>
</div>

<div class="mb-3">
<label class="form-label">Status</label>
<select name="status" class="form-control">
<option value="published">Published</option>
<option value="draft">Draft</option>
</select>
</div>

<button type="submit" name="save" class="btn btn-success">Save Post</button>
<a href="admin.php" class="btn btn-secondary">Cancel</a>

</form>
</div>
</div>
</div>

</body>
</html>
12. Edit Blog Post – edit_post.php

Tengeneza file:

edit_post.php

Weka code hii:

<?php
require_once "config.php";

$errors = [];

if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: admin.php?message=Invalid post ID");
exit;
}

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

$stmt = $conn->prepare("SELECT * FROM posts WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$post = $stmt->get_result()->fetch_assoc();

if (!$post) {
header("Location: admin.php?message=Post not found");
exit;
}

if (isset($_POST['update'])) {
$title = trim($_POST['title']);
$category = trim($_POST['category']);
$content = trim($_POST['content']);
$seo_title = trim($_POST['seo_title']);
$meta_description = trim($_POST['meta_description']);
$status = trim($_POST['status']);
$imageName = $post['image'];

if ($title == "") {
$errors[] = "Title is required.";
}

if ($content == "") {
$errors[] = "Content is required.";
}

if (!in_array($status, ['draft', 'published'])) {
$errors[] = "Invalid status selected.";
}

if (!empty($_FILES['image']['name'])) {
$allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
$fileType = $_FILES['image']['type'];
$fileSize = $_FILES['image']['size'];
$tmpName = $_FILES['image']['tmp_name'];

if (!in_array($fileType, $allowedTypes)) {
$errors[] = "Only JPG, PNG, WEBP and GIF images are allowed.";
}

if ($fileSize > 2 * 1024 * 1024) {
$errors[] = "Image size must not exceed 2MB.";
}

if (empty($errors)) {
$extension = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$newImage = time() . "_" . uniqid() . "." . strtolower($extension);
$uploadPath = "uploads/blog/" . $newImage;

if (move_uploaded_file($tmpName, $uploadPath)) {
if (!empty($post['image']) && file_exists("uploads/blog/" . $post['image'])) {
unlink("uploads/blog/" . $post['image']);
}
$imageName = $newImage;
} else {
$errors[] = "Failed to upload new image.";
}
}
}

if (empty($errors)) {
$slug = uniqueSlug($conn, $title, $id);

$stmt = $conn->prepare("UPDATE posts SET title=?, slug=?, category=?, content=?, image=?, seo_title=?, meta_description=?, status=? WHERE id=?");
$stmt->bind_param("ssssssssi", $title, $slug, $category, $content, $imageName, $seo_title, $meta_description, $status, $id);

if ($stmt->execute()) {
header("Location: admin.php?message=Post updated successfully");
exit;
} else {
$errors[] = "Failed to update post.";
}
}
} else {
$title = $post['title'];
$category = $post['category'];
$content = $post['content'];
$seo_title = $post['seo_title'];
$meta_description = $post['meta_description'];
$status = $post['status'];
}
?>
<!DOCTYPE html>
<html lang="sw">
<head>
<meta charset="UTF-8">
<title>Edit Blog Post</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css&quot; rel="stylesheet">
</head>

<body class="bg-light">

<nav class="navbar navbar-dark bg-dark">
<div class="container">
<a href="admin.php" class="navbar-brand fw-bold">Blog Admin</a>
<a href="admin.php" class="btn btn-outline-light btn-sm">Back</a>
</div>
</nav>

<div class="container my-4">
<div class="card shadow-sm">
<div class="card-header bg-warning">
<h4 class="mb-0">Edit Blog Post</h4>
</div>

<div class="card-body">

<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach ($errors as $error): ?>
<li><?php echo clean($error); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>

<form method="POST" enctype="multipart/form-data">

<div class="mb-3">
<label class="form-label">Post Title</label>
<input type="text" name="title" class="form-control" required value="<?php echo clean($title); ?>">
</div>

<div class="mb-3">
<label class="form-label">Category</label>
<input type="text" name="category" class="form-control" value="<?php echo clean($category); ?>">
</div>

<div class="mb-3">
<label class="form-label">Current Image</label><br>
<?php if (!empty($post['image'])): ?>
<img src="uploads/blog/<?php echo clean($post['image']); ?>" width="180" style="object-fit:cover;">
<?php else: ?>
<p>No image uploaded.</p>
<?php endif; ?>
</div>

<div class="mb-3">
<label class="form-label">Change Featured Image</label>
<input type="file" name="image" class="form-control" accept="image/*">
</div>

<div class="mb-3">
<label class="form-label">Content</label>
<textarea name="content" rows="12" class="form-control" required><?php echo clean($content); ?></textarea>
</div>

<div class="mb-3">
<label class="form-label">SEO Title</label>
<input type="text" name="seo_title" class="form-control" maxlength="255" value="<?php echo clean($seo_title); ?>">
</div>

<div class="mb-3">
<label class="form-label">Meta Description</label>
<textarea name="meta_description" rows="3" class="form-control" maxlength="300"><?php echo clean($meta_description); ?></textarea>
</div>

<div class="mb-3">
<label class="form-label">Status</label>
<select name="status" class="form-control">
<option value="published" <?php echo ($status == 'published') ? 'selected' : ''; ?>>Published</option>
<option value="draft" <?php echo ($status == 'draft') ? 'selected' : ''; ?>>Draft</option>
</select>
</div>

<button type="submit" name="update" class="btn btn-warning">Update Post</button>
<a href="admin.php" class="btn btn-secondary">Cancel</a>

</form>
</div>
</div>
</div>

</body>
</html>
13. Delete Blog Post – delete_post.php

Tengeneza file:

delete_post.php

Weka code hii:

<?php
require_once "config.php";

if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: admin.php?message=Invalid post ID");
exit;
}

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

$stmt = $conn->prepare("SELECT image FROM posts WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$post = $stmt->get_result()->fetch_assoc();

if (!$post) {
header("Location: admin.php?message=Post not found");
exit;
}

$stmt = $conn->prepare("DELETE FROM posts WHERE id = ?");
$stmt->bind_param("i", $id);

if ($stmt->execute()) {
if (!empty($post['image']) && file_exists("uploads/blog/" . $post['image'])) {
unlink("uploads/blog/" . $post['image']);
}

header("Location: admin.php?message=Post deleted successfully");
exit;
} else {
header("Location: admin.php?message=Failed to delete post");
exit;
}
?>
14. CSS ya Kuongeza Muonekano – assets/style.css

Tengeneza file:

assets/style.css

Weka:

body {
font-family: Arial, sans-serif;
}

.blog-content {
font-size: 18px;
line-height: 1.8;
}

.blog-content p {
margin-bottom: 18px;
}

.card-title {
font-weight: bold;
}

Kwenye pages zako unaweza kui-include hivi:

<link rel="stylesheet" href="assets/style.css">
15. Jinsi ya Ku-run Project

Baada ya kuandika files zote:

Hakikisha Apache na MySQL zimewaka kwenye XAMPP.
Hakikisha database my_blog_db ipo.
Hakikisha table posts ipo.
Hakikisha folder uploads/blog/ ipo.
Fungua browser:
http://localhost/my_blog/admin.php

Ongeza blog post.

Kisha fungua:

http://localhost/my_blog/

Utaona posts zako.

16. Jinsi Flow ya Blog Inavyofanya Kazi

Admin akiandika post:

add_post.php → posts table → index.php → post.php

Admin akipublish post:

post inaonekana public side
visitors wanaweza kuisoma
Google inaweza kuindex kama website iko online

Admin akiweka draft:

post inabaki admin side
visitors hawaioni
17. Kwa Nini Tunatumia Prepared Statements?

Prepared statements zinasaidia kuzuia SQL Injection.

Mfano hatari:

$sql = "SELECT * FROM posts WHERE slug = '$slug'";

Mtu anaweza kutumia malicious input kuvuruga query.

Njia salama:

$stmt = $conn->prepare("SELECT * FROM posts WHERE slug = ?");
$stmt->bind_param("s", $slug);
$stmt->execute();

Hii ni professional zaidi.

18. Kwa Nini Tunatumia clean()?

Data kutoka database inaweza kuwa na HTML au script hatari.

Kwa hiyo tunapoonyesha title, category, au meta description, tunatumia:

echo clean($post['title']);

Hii inalinda against XSS attack.

19. Kuhusu Blog Content na HTML

Kwenye post.php tumeandika:

<?php echo nl2br($post['content']); ?>

Hii inaonyesha content kwa mistari.

Lakini kama unataka admin aweze kuweka HTML kama:

<h2>Heading</h2>
<p>Paragraph</p>
<ul>
<li>Point</li>
</ul>

basi unaweza kuacha:

echo $post['content'];

Lakini hii ni hatari kama admin si trusted. Kwa system salama zaidi, tumia HTML purifier au rich text editor yenye sanitization.

20. Kuongeza Login kwa Admin

System hii haina login bado, kwa sababu tutorial imelenga blog CRUD. Lakini kwenye production, usiache admin.php wazi.

Unapaswa kuongeza:

login.php
logout.php
sessions
password hashing
role ya admin

Mfano password hashing:

$passwordHash = password_hash($password, PASSWORD_DEFAULT);

Kuthibitisha password:

password_verify($password, $user['password']);
21. Kuongeza Pagination

Kama posts zitakuwa nyingi, index.php inaweza kuwa slow. Unaweza kuongeza pagination.

Concept:

$limit = 6;
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$offset = ($page - 1) * $limit;

Query:

SELECT * FROM posts WHERE status='published' ORDER BY created_at DESC LIMIT ? OFFSET ?

Pagination ni muhimu kwa blog kubwa.

22. Kuongeza Sitemap kwa Blog Posts

Kama website yako iko online, tengeneza sitemap.php.

<?php
require_once "config.php";

header("Content-Type: application/xml; charset=utf-8");

echo '<?xml version="1.0" encoding="UTF-8"?>';
?>

<urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9&quot;&gt;
<url>
<loc>https://example.com/&lt;/loc&gt;
<lastmod><?php echo date("Y-m-d"); ?></lastmod>
<priority>1.0</priority>
</url>

<?php
$stmt = $conn->prepare("SELECT slug, updated_at FROM posts WHERE status='published' ORDER BY updated_at DESC");
$stmt->execute();
$result = $stmt->get_result();

while ($row = $result->fetch_assoc()):
?>
<url>
<loc>https://example.com/post.php?slug=&lt;?php echo htmlspecialchars($row['slug'], ENT_XML1, 'UTF-8'); ?></loc>
<lastmod><?php echo date("Y-m-d", strtotime($row['updated_at'])); ?></lastmod>
<priority>0.8</priority>
</url>
<?php endwhile; ?>

</urlset>

Kisha unaweza kutumia .htaccess kuifanya iwe:

sitemap.xml
23. Robots.txt

Tengeneza file:

robots.txt

Weka:

User-agent: *
Allow: /

Disallow: /admin.php
Disallow: /add_post.php
Disallow: /edit_post.php
Disallow: /delete_post.php
Disallow: /config.php

Sitemap: https://example.com/sitemap.xml

Kwa localhost haihitajiki sana, lakini kwa hosting online ni muhimu.

24. SEO Tips kwa Blog Yako
https://faulink.com
Kila blog post iwe na:

✅ Title nzuri
✅ SEO title
✅ Meta description
✅ Featured image
✅ Category
✅ Content ndefu na yenye maana
✅ Internal links
✅ Alt text ya image
✅ Slug readable
✅ Canonical URL

Mfano mzuri wa title:

Jinsi ya Kutengeneza Login System kwa PHP na MySQL

Slug:

jinsi-ya-kutengeneza-login-system-php-mysql

Meta description:

Jifunze step by step jinsi ya kutengeneza login system salama kwa PHP na MySQL kwa kutumia sessions na password hashing.
25. Makosa ya Kuepuka

Epuka:

kuacha admin bila login
ku-upload files bila validation
kutumia SQL queries bila prepared statements
kutumia title moja kwenye posts zote
kutokuwa na meta description
kutotumia slug
kuacha image folder bila protection
kutotumia backup
ku-copy content kutoka websites nyingine
kuacha draft posts zionekane public
kutotumia HTTPS ukiwa online
26. Improvements za Baadaye
https://faulink.com
Unaweza kuboresha system hii kwa kuongeza:

✅ Admin login
✅ Multiple users
✅ Categories table
✅ Tags table
✅ Comments system
✅ Likes system
✅ Related posts
✅ Recent posts sidebar
✅ Popular posts
✅ View counter
✅ Social share buttons
✅ YouTube embed
✅ File attachments
✅ Rich text editor
✅ AdSense areas
✅ Newsletter subscription
✅ Contact form
✅ Dashboard statistics
27. Hitimisho

Sasa umejifunza kutengeneza blog system kamili kwa PHP na MySQL. System hii ina:

database connection
posts table
admin dashboard
add post
edit post
delete post
image upload
public blog homepage
single post page
SEO fields
slug generation
categories
search
status ya draft/published
security ya prepared statements

Kwa kifupi, blog system yoyote ina msingi huu:

Admin anaandika post → post inaingia database → visitor anaisoma public page

Ukijua hii, unaweza kutengeneza blog kubwa zaidi, news website, educational portal, school announcements system, tutorials website, au website ya downloadable files

🚀 Unahitaji mfumo au website ya biashara?

Chagua huduma hapa chini kisha mteja bofya moja kwa moja kwenda kwenye ukurasa wa huduma au kuwasiliana nasi kwa WhatsApp.

Share this post

Comments

0
No comments yet. Be the first to comment.

Continue Reading

Subscribe

Get new updates

Jiunge upokee posts mpya, tutorials, na updates za mifumo moja kwa moja kwenye email yako.

Faulink Support