This mini project pulls these tutorials together: a notes CRUD app with registration, login, and authorisation. One user sees their notes; an admin can manage users. Plain PHP, no framework.
What you are building
- Register, login, logout
- List, create, edit, delete notes (owner only, admin override)
- Flash messages and validated forms
- Prepared statements throughout
- Router + controllers + templates
Suggested schema
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(190) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'user',
created_at DATETIME NOT NULL
);
CREATE TABLE notes (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
title VARCHAR(120) NOT NULL,
body TEXT NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
Routes
GET / home
GET /register register form
POST /register create account
GET /login login form
POST /login authenticate
POST /logout logout
GET /notes list notes (auth)
GET /notes/create create form (auth)
POST /notes store (auth)
GET /notes/{id}/edit edit form (auth, owner)
POST /notes/{id} update (auth, owner)
POST /notes/{id}/delete delete (auth, owner or admin)
Build order
- 1. Project layout, Composer, PDO connection from config
- 2. Router and a hello-world home page
- 3. Auth (register, login, session helpers)
- 4. NoteRepository with prepared statements
- 5. CRUD controllers with validation and flash
- 6. Templates with layout and escaped output
- 7. Admin-only user list (optional stretch)
Checklist before you call it done
- CSRF tokens on all POST forms
- Passwords hashed, sessions regenerated on login
- Users cannot edit another user’s notes by guessing IDs
- Errors logged, not dumped to the browser in production mode
- Upload directory not used unless you added attachments
This is a portfolio-sized app. It is small on purpose – but it uses the same patterns as larger PHP codebases. Finish it, then read your own code critically: what would you extract next if the app grew?

