Dynamic Portfolio CMS IN HTML CSS AND JAVASCRIPT WITH PHP AND MY SQL

Project Introduction: Dynamic Portfolio CMS

This project is a fully functional Dynamic Personal Portfolio Website built as a Content Management System (CMS). It allows a client (visitor) to view a professional portfolio, and it provides an Admin Panel for the portfolio owner to easily update their information without touching the code.

The Problem it Solves:
Traditionally, to change a project or your bio, you had to edit HTML files. This system allows you to log in to a secure admin panel and update your profile, skills, and projects through a simple form. The changes are stored in a database and instantly appear on the live website.

Key Features:

  • Client Side (Public View):
    • Responsive design (works on mobile, tablet, desktop).
    • Home Section: Intro and dynamic bio fetched from the database.
    • Skills Section: Displays technical skills with progress bars (data fetched from DB).
    • Projects Section: Showcases projects with images, titles, descriptions, and links (data fetched from DB).
    • Contact Section: A form that sends emails (static, as email requires server config).
  • Admin Side (Private Panel):
    • Secure Login: Password-protected access (default: admin / password).
    • Dashboard: Quick overview of total skills and projects.
    • Manage Skills: Add, Edit, and Delete skills (name and percentage).
    • Manage Projects: Add, Edit, and Delete projects (title, description, image, link).
    • Manage Profile: Edit the "About Me" text and name displayed on the main page.
    • Logout Functionality.

Technology Stack:

  • Frontend: HTML5, CSS3, JavaScript (Fetch API for AJAX calls).
  • Backend: PHP (Core PHP, no frameworks for simplicity).
  • Database: MySQL.
  • Server: Apache (XAMPP/WAMP/LAMP).

📁 Project File Structure

Here is the organized file structure you need to create in your server's root directory (e.g., htdocs for XAMPP).

portfolio-cms/
│
├── index.php                 # Public facing homepage (Client Side)
├── admin/                    # Admin Panel Directory (Password Protected)
│   ├── index.php             # Admin Login Page
│   ├── dashboard.php         # Admin Dashboard (after login)
│   ├── manage_skills.php     # Page to manage skills (CRUD)
│   ├── manage_projects.php   # Page to manage projects (CRUD)
│   ├── edit_profile.php      # Page to edit bio/name
│   ├── logout.php            # Logout script
│   └── css/
│       └── admin-style.css   # Specific styles for admin panel
│
├── includes/                  # Backend PHP logic and config
│   ├── config.php             # Database connection
│   ├── auth.php               # Authentication check functions
│   ├── header.php             # Shared header for public site (optional)
│   └── footer.php             # Shared footer for public site (optional)
│
├── api/                       # PHP endpoints for JavaScript (AJAX)
│   ├── get_profile.php        # Fetch profile data for public page
│   ├── get_skills.php         # Fetch skills for public page
│   └── get_projects.php       # Fetch projects for public page
│
├── assets/                    # Static assets
│   ├── css/
│   │   └── style.css          # Main CSS for the public website
│   ├── js/
│   │   └── script.js          # Main JS for public website (fetches data)
│   └── images/                # Store uploaded images here
│       ├── profile/           # Profile pictures
│       └── projects/          # Project screenshots
│
└── database/
└── portfolio.sql          # Database dump file (to import)

🗄️ Database Setup (database/portfolio.sql)

Create a database named portfolio_cms and run this SQL to create the tables.

-- phpMyAdmin SQL Dump
-- Database: `portfolio_cms`
CREATE DATABASE IF NOT EXISTS `portfolio_cms`;
USE `portfolio_cms`;
-- --------------------------------------------------------
-- Table structure for table `admin`
-- --------------------------------------------------------
CREATE TABLE `admin` (
`id` int(11) NOT NULL,
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL -- Hashed password
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Dumping data for table `admin` (password = "password" hashed)
INSERT INTO `admin` (`id`, `username`, `password`) VALUES
(1, 'admin', '$2y$10$YourHashedPasswordHere'); -- We will update this via PHP script later.
-- --------------------------------------------------------
-- Table structure for table `profile`
-- --------------------------------------------------------
CREATE TABLE `profile` (
`id` int(11) NOT NULL,
`name` varchar(100) NOT NULL,
`bio` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `profile` (`id`, `name`, `bio`) VALUES
(1, 'John Doe', 'I am a passionate web developer with 5+ years of experience creating dynamic and responsive websites. I love solving problems and building things for the web.');
-- --------------------------------------------------------
-- Table structure for table `skills`
-- --------------------------------------------------------
CREATE TABLE `skills` (
`id` int(11) NOT NULL,
`skill_name` varchar(100) NOT NULL,
`percentage` int(3) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `skills` (`id`, `skill_name`, `percentage`) VALUES
(1, 'HTML & CSS', 90),
(2, 'JavaScript', 85),
(3, 'PHP', 80);
-- --------------------------------------------------------
-- Table structure for table `projects`
-- --------------------------------------------------------
CREATE TABLE `projects` (
`id` int(11) NOT NULL,
`title` varchar(200) NOT NULL,
`description` text NOT NULL,
`image_path` varchar(255) DEFAULT NULL,
`project_link` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `projects` (`id`, `title`, `description`, `image_path`, `project_link`) VALUES
(1, 'E-commerce App', 'A modern e-commerce platform built with PHP and MySQL.', 'assets/images/projects/ecommerce.jpg', 'https://example.com/proj1'),
(2, 'Weather App', 'A simple weather app using a third-party API.', 'assets/images/projects/weather.jpg', 'https://example.com/proj2');
-- Indexes and AUTO_INCREMENT
ALTER TABLE `admin` ADD PRIMARY KEY (`id`);
ALTER TABLE `profile` ADD PRIMARY KEY (`id`);
ALTER TABLE `skills` ADD PRIMARY KEY (`id`);
ALTER TABLE `projects` ADD PRIMARY KEY (`id`);
ALTER TABLE `admin` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
ALTER TABLE `profile` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
ALTER TABLE `skills` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
ALTER TABLE `projects` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
COMMIT;

💻 The Code

Here are the core code snippets for the most important files. You will need to create all the files listed in the structure.

1. Database Connection (includes/config.php)

<?php
$host = 'localhost';
$dbname = 'portfolio_cms';
$username = 'root'; // Default XAMPP user
$password = '';     // Default XAMPP password is empty
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $username, $password);
// Set the PDO error mode to exception
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
die("Connection failed: " . $e->getMessage());
}
// Function to sanitize input data
function sanitize($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
?>

2. Admin Authentication Check (includes/auth.php)

<?php
session_start();
// Check if the user is logged in, if not then redirect to login page
if(!isset($_SESSION["admin_loggedin"]) || $_SESSION["admin_loggedin"] !== true){
header("location: index.php");
exit;
}
?>

3. The Public Frontend (index.php)

This file loads the main HTML structure and uses JavaScript to fill in the dynamic data.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Portfolio | Home</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<!-- Navigation -->
<nav>
<div class="container">
<h1 class="logo"><span id="nav-name">My</span>Portfolio</h1>
<ul class="nav-links">
<li><a href="#home">Home</a></li>
<li><a href="#skills">Skills</a></li>
<li><a href="#projects">Projects</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div>
</nav>
<!-- Home Section -->
<section id="home">
<div class="container">
<div class="home-content">
<h2>Hi, I'm <span id="profile-name">Loading...</span></h2>
<p id="profile-bio">Loading profile...</p>
</div>
</div>
</section>
<!-- Skills Section -->
<section id="skills">
<div class="container">
<h2>My Skills</h2>
<div class="skills-grid" id="skills-container">
<!-- Skills will be loaded here by JavaScript -->
<p>Loading skills...</p>
</div>
</div>
</section>
<!-- Projects Section -->
<section id="projects">
<div class="container">
<h2>My Projects</h2>
<div class="projects-grid" id="projects-container">
<!-- Projects will be loaded here by JavaScript -->
<p>Loading projects...</p>
</div>
</div>
</section>
<!-- Contact Section (Static) -->
<section id="contact">
<div class="container">
<h2>Contact Me</h2>
<form id="contact-form" method="POST">
<input type="text" name="name" placeholder="Your Name" required>
<input type="email" name="email" placeholder="Your Email" required>
<textarea name="message" placeholder="Your Message" required></textarea>
<button type="submit">Send Message</button>
</form>
</div>
</section>
<footer>
<p>&copy; 2023 <span id="footer-name">My Portfolio</span>. All rights reserved.</p>
</footer>
<!-- Link JavaScript -->
<script src="assets/js/script.js"></script>
</body>
</html>

4. Public JavaScript (Fetch Data) - assets/js/script.js

This file fetches data from the API endpoints and updates the HTML.

document.addEventListener('DOMContentLoaded', function() {
// --- Fetch Profile Data ---
fetch('api/get_profile.php')
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('profile-name').textContent = data.data.name;
document.getElementById('profile-bio').textContent = data.data.bio;
// Update other name instances
document.getElementById('nav-name').textContent = data.data.name.split(' ')[0]; // First name
document.getElementById('footer-name').textContent = data.data.name;
} else {
console.error('Failed to load profile');
}
})
.catch(error => console.error('Error fetching profile:', error));
// --- Fetch Skills Data ---
fetch('api/get_skills.php')
.then(response => response.json())
.then(data => {
const container = document.getElementById('skills-container');
if (data.success && data.data.length > 0) {
container.innerHTML = ''; // Clear loading text
data.data.forEach(skill => {
const skillHTML = `
<div class="skill-item">
<h4>${skill.skill_name}</h4>
<div class="progress-bar">
<div class="progress" style="width: ${skill.percentage}%;">${skill.percentage}%</div>
</div>
</div>
`;
container.innerHTML += skillHTML;
});
} else {
container.innerHTML = '<p>No skills found.</p>';
}
})
.catch(error => console.error('Error fetching skills:', error));
// --- Fetch Projects Data ---
fetch('api/get_projects.php')
.then(response => response.json())
.then(data => {
const container = document.getElementById('projects-container');
if (data.success && data.data.length > 0) {
container.innerHTML = ''; // Clear loading text
data.data.forEach(project => {
const imagePath = project.image_path ? project.image_path : 'assets/images/placeholder.jpg';
const projectHTML = `
<div class="project-card">
<img src="${imagePath}" alt="${project.title}">
<h3>${project.title}</h3>
<p>${project.description.substring(0, 100)}...</p>
<a href="${project.project_link}" target="_blank" class="btn">View Project</a>
</div>
`;
container.innerHTML += projectHTML;
});
} else {
container.innerHTML = '<p>No projects found.</p>';
}
})
.catch(error => console.error('Error fetching projects:', error));
});

5. API Endpoint Example (api/get_skills.php)

<?php
header('Content-Type: application/json');
require_once '../includes/config.php';
try {
$stmt = $pdo->query("SELECT skill_name, percentage FROM skills ORDER BY id ASC");
$skills = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'data' => $skills]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

6. Admin Login Page (admin/index.php)

<?php
session_start();
// If already logged in, redirect to dashboard
if(isset($_SESSION["admin_loggedin"]) && $_SESSION["admin_loggedin"] === true){
header("location: dashboard.php");
exit;
}
require_once '../includes/config.php';
$username = $password = '';
$username_err = $password_err = $login_err = '';
if($_SERVER["REQUEST_METHOD"] == "POST"){
// Check if username is empty
if(empty(trim($_POST["username"]))){
$username_err = "Please enter username.";
} else{
$username = trim($_POST["username"]);
}
// Check if password is empty
if(empty(trim($_POST["password"]))){
$password_err = "Please enter your password.";
} else{
$password = trim($_POST["password"]);
}
// Validate credentials
if(empty($username_err) && empty($password_err)){
$sql = "SELECT id, username, password FROM admin WHERE username = :username";
if($stmt = $pdo->prepare($sql)){
$stmt->bindParam(":username", $param_username, PDO::PARAM_STR);
$param_username = $username;
if($stmt->execute()){
if($stmt->rowCount() == 1){
$row = $stmt->fetch();
$hashed_password = $row["password"];
if(password_verify($password, $hashed_password)){
session_start();
$_SESSION["admin_loggedin"] = true;
$_SESSION["admin_id"] = $row["id"];
$_SESSION["admin_username"] = $row["username"];                            
header("location: dashboard.php");
} else{
$login_err = "Invalid username or password.";
}
} else{
$login_err = "Invalid username or password.";
}
} else{
echo "Oops! Something went wrong. Please try again later.";
}
unset($stmt);
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Admin Login</title>
<link rel="stylesheet" href="css/admin-style.css">
</head>
<body class="login-body">
<div class="login-container">
<h2>Admin Login</h2>
<p>Please fill in your credentials to login.</p>
<?php 
if(!empty($login_err)){
echo '<div class="alert alert-danger">' . $login_err . '</div>';
}        
?>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
<div class="form-group">
<label>Username</label>
<input type="text" name="username" class="form-control <?php echo (!empty($username_err)) ? 'is-invalid' : ''; ?>" value="<?php echo $username; ?>">
<span class="invalid-feedback"><?php echo $username_err; ?></span>
</div>    
<div class="form-group">
<label>Password</label>
<input type="password" name="password" class="form-control <?php echo (!empty($password_err)) ? 'is-invalid' : ''; ?>">
<span class="invalid-feedback"><?php echo $password_err; ?></span>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Login">
</div>
</form>
</div>
</body>
</html>

📝 How to Use This Project (Step-by-Step Guide)

Follow these steps exactly to get your portfolio live.

Step 1: Install a Local Server

  • Download and install XAMPP (or WAMP).
  • Start the Apache and MySQL services from the XAMPP control panel.

Step 2: Create the Project Folder

  • Navigate to the htdocs folder inside your XAMPP installation directory (e.g., C:\xampp\htdocs\).
  • Create a new folder named portfolio-cms.

Step 3: Set up the Database

  • Open your browser and go to http://localhost/phpmyadmin.
  • Click on the "SQL" tab.
  • Copy the entire SQL code from the database/portfolio.sql section above and paste it into the text area.
  • Click "Go". This will create the database portfolio_cms and the tables.

Step 4: Create the Password Hash

  • We need to set the admin password. You can't just write "password" in the database; it must be hashed for security.
  • Create a temporary PHP file called hash.php in your portfolio-cms folder.
  • Put this code inside it: <?php echo password_hash('password', PASSWORD_DEFAULT); ?>
  • Run it via your browser: http://localhost/portfolio-cms/hash.php. It will output a long string. Copy this string.
  • Go back to phpMyAdmin, open the admin table, and paste that long string into the password field for the user 'admin'.
  • Delete the hash.php file immediately after!

Step 5: Place the Code Files

  • Now, create all the folders and files as shown in the File Structure section.
  • Copy and paste the corresponding code from this guide into each file.
  • Make sure you update the includes/config.php file with your database details (if you used a password for MySQL other than blank, update it here).

Step 6: Set Folder Permissions (Important)

  • The assets/images/ folder needs to be writable by the server so you can upload images from the admin panel.
  • On Windows, right-click the images folder -> Properties -> Security -> Give "Modify" permissions to the "Users" group.

Step 7: Run the Project

  • Client Side: Open your browser and go to http://localhost/portfolio-cms/. You should see your portfolio homepage with the default data loaded from the database.
  • Admin Side: Go to http://localhost/portfolio-cms/admin/.
    • Username: admin
    • Password: password

Step 8: Use the Admin Panel

  • Log in to the admin panel.
  • Go to Manage Skills and add your own skills (e.g., "React", 80).
  • Go to Manage Projects and add your projects. Upload an image for each.
  • Go to Edit Profile and change the name and bio to your own.
  • Refresh the main site (http://localhost/portfolio-cms/) to see your changes instantly!

🎨 Customization Tips

  • CSS Styling: The project comes with basic functionality. To make it look stunning, you need to write CSS in assets/css/style.css and admin/css/admin-style.css. You can add gradients, animations, and modern fonts (like Google Fonts).
  • File Uploads: The code for handling file uploads in the admin panel (for project images) is not included in this snippet for brevity, but the structure supports it. You would add standard PHP file upload handling in manage_projects.php.
  • Deployment to Live Server:
    1. Upload all files to your web host (e.g., via cPanel File Manager or FTP).
    2. Create a MySQL database on your host using cPanel.
    3. Import the portfolio.sql file into that new database.
    4. Update the includes/config.php file with your live server's database credentials (host, dbname, username, password).
    5. Update the $2y$10$... password hash in the admin table with a new one generated from your live server (create a temporary hash.php there too, and delete it).

This project provides a solid, secure foundation for a dynamic portfolio that you can easily manage and expand upon.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper