A CRUD app in PHP and MySQL lets you add, view, edit and delete records in a database from simple web pages. To build one, you create a MySQL table, connect PHP to it with PDO, and write four small operations: INSERT to add a record, SELECT to show records, UPDATE to change one, and DELETE to remove one. In this tutorial you will build a complete student records app, step by step, using PDO prepared statements so your code is safe from SQL injection from day one.
What Does CRUD Mean?
CRUD names the four basic jobs almost every app does with data: Create adds something new, Read shows it, Update changes it, and Delete removes it. Your contacts app does CRUD, and so does a shopping site. Even a big social media app is, deep down, mostly CRUD wearing a very fancy jacket.
Each action matches one SQL command:
| CRUD action | SQL command | What it does in our app |
|---|---|---|
| Create | INSERT | Add a new student record |
| Read | SELECT | Show the list of students |
| Update | UPDATE | Edit a student's name, email or course |
| Delete | DELETE | Remove a student record |
Our app will manage students, each with a name, email and course. It is small, but it teaches the pattern you will reuse in bigger projects like a blog admin panel.
What You Need Before You Start
You only need a local server such as XAMPP or WAMP, which gives you PHP and MySQL together, and a text editor. No framework, no library. Create a folder called crud-app inside your server's web folder, such as htdocs in XAMPP, and put all the tutorial files inside it.
Step 1: Create the Database and Table
Open phpMyAdmin, click the SQL tab, and run this code to create a database called school and a table called students:
CREATE DATABASE school;
USE school;
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
course VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);The id column gives every student a unique number automatically, and we will use it later to edit or delete exactly one record. The syntax follows the standard statement described in the MySQL CREATE TABLE documentation, which you can check for more column types.
Step 2: Connect PHP to MySQL With PDO
Every page needs a database connection, so we put it in one shared file called db.php. Each page includes this file instead of repeating the code, because typing the same ten lines everywhere is a great way to make one small typo in five places.
<?php
$host = "localhost";
$dbname = "school";
$username = "root";
$password = "";
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("Connection failed: " . $e->getMessage());
}If connections are new to you, read our guide on how to connect PHP with a MySQL database first; it explains PDO and MySQLi side by side.
Step 3: Create — Add a New Record
Create a file called create.php with a simple form. When it is submitted, PHP inserts the values into the table using a prepared statement. The question marks are placeholders: PDO fills them in safely, so user input can never change the meaning of your SQL.
<?php
require "db.php";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$name = trim($_POST["name"]);
$email = trim($_POST["email"]);
$course = trim($_POST["course"]);
$sql = "INSERT INTO students (name, email, course) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $email, $course]);
header("Location: index.php");
exit;
}
?>
<form method="post">
<label>Name: <input type="text" name="name" required></label><br>
<label>Email: <input type="email" name="email" required></label><br>
<label>Course: <input type="text" name="course" required></label><br>
<button type="submit">Add Student</button>
</form>This is exactly the pattern recommended in the official PHP manual on PDO prepared statements: prepare the query once, then send the values separately when you execute it. After the insert, we redirect to the list page, which also stops the same record being added twice if the user refreshes.
Step 4: Read — View All Records
The home page, index.php, reads every student and shows them in an HTML table. Each row gets an Edit link and a Delete link, which pass the student's id in the URL so the next page knows exactly which record you mean.
<?php
require "db.php";
$stmt = $pdo->query("SELECT * FROM students ORDER BY id DESC");
$students = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<table border="1" cellpadding="8">
<tr><th>ID</th><th>Name</th><th>Email</th><th>Course</th><th>Actions</th></tr>
<?php foreach ($students as $student): ?>
<tr>
<td><?php echo htmlspecialchars($student["id"]); ?></td>
<td><?php echo htmlspecialchars($student["name"]); ?></td>
<td><?php echo htmlspecialchars($student["email"]); ?></td>
<td><?php echo htmlspecialchars($student["course"]); ?></td>
<td>
<a href="edit.php?id=<?php echo $student["id"]; ?>">Edit</a>
<a href="delete.php?id=<?php echo $student["id"]; ?>" onclick="return confirm('Delete this record?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</table>Notice htmlspecialchars() around every value we print. It turns special characters into safe text, so a name containing HTML cannot break your page. Later, when a student belongs to a course stored in another table, you will combine the data using our guide to MySQL joins explained with simple examples.
Step 5: Update — Edit an Existing Record
The edit page does two jobs. First it reads the current record using the id from the URL and fills the form with those values. Then, when the form is submitted, it runs an UPDATE query. The WHERE id = ? part is the most important line in the file: forget it, and you will update every student at once, which is the sort of excitement nobody needs on a Monday morning.
<?php
require "db.php";
$id = (int) $_GET["id"];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$name = trim($_POST["name"]);
$email = trim($_POST["email"]);
$course = trim($_POST["course"]);
$sql = "UPDATE students SET name = ?, email = ?, course = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $email, $course, $id]);
header("Location: index.php");
exit;
}
$stmt = $pdo->prepare("SELECT * FROM students WHERE id = ?");
$stmt->execute([$id]);
$student = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$student) {
die("Student not found.");
}
?>
<form method="post">
<label>Name: <input type="text" name="name" value="<?php echo htmlspecialchars($student["name"]); ?>" required></label><br>
<label>Email: <input type="email" name="email" value="<?php echo htmlspecialchars($student["email"]); ?>" required></label><br>
<label>Course: <input type="text" name="course" value="<?php echo htmlspecialchars($student["course"]); ?>" required></label><br>
<button type="submit">Save Changes</button>
</form>Step 6: Delete — Remove a Record
Deleting is the shortest operation. delete.php takes the id from the URL, runs one prepared DELETE query, and sends the user back to the list. The JavaScript confirmation on the Delete link in Step 4 gives one last chance to change your mind, because the delete button has no undo and it knows it.
<?php
require "db.php";
$id = (int) $_GET["id"];
$stmt = $pdo->prepare("DELETE FROM students WHERE id = ?");
$stmt->execute([$id]);
header("Location: index.php");
exit;That is the whole app: five files — db.php, index.php, create.php, edit.php and delete.php. Open index.php, add a few students, edit one, delete one, and watch the table behave. You have built the pattern behind almost every database-driven website.
Common Mistakes to Avoid
Beginners usually meet the same three problems: putting form values directly inside an SQL string, which opens the door to SQL injection; forgetting the WHERE clause in UPDATE or DELETE, which changes or removes every row; and printing values without htmlspecialchars(). Avoid these three, and your app is already safer than many tutorials.
Frequently Asked Questions
What is a CRUD app in PHP?
A CRUD app can create, read, update and delete records in a database. In PHP, you build it with HTML forms for input, PDO for the connection, and the SQL commands INSERT, SELECT, UPDATE and DELETE.
Why use PDO instead of MySQLi for CRUD?
Both work well, but PDO supports prepared statements in a clean way and can work with other databases besides MySQL, so the pattern transfers to almost every PHP project.
How do prepared statements stop SQL injection?
A prepared statement sends the SQL structure and the user values to MySQL separately. MySQL treats the values only as data, never as part of the command, so harmful input cannot change what your query does.
Can I add login to this CRUD app?
Yes. Add a users table, store passwords with password_hash(), start a session after login, and check that session at the top of each page.
Conclusion
You have built a complete CRUD app in PHP and MySQL: a table for students, a shared PDO connection, and working pages to add, view, edit and delete records, all protected by prepared statements. It is one of the most useful beginner PHP projects, because the same four operations appear in almost every real project. Practise by changing it to manage books or products, and the pattern will soon feel like second nature.
Leave a Comment