This project is the culmination of everything you've learned. You'll build a database that mirrors real-world platforms like Medium, Ghost, or WordPress. By the end, you'll have a high-quality portfolio piece that demonstrates your ability to:
Model complex business relationships
Write clean, production-grade
Optimize queries for performance and scale
Project Overview
Our goal is to build the backend database for a professional blogging platform.
Requirements:
Users: Secure storage for authors and admins.
Posts: Articles with SEO-friendly slugs and publishing workflows.
Comments: Reader engagement with moderation states.
Categories & Tags: Two ways to organize content (One-to-Many and Many-to-Many).
Blog Architecture Flow
How a Blog Post is Createdflowchart
Rendering diagram…
Step 1: Design the Schema (ER Diagram)
A professional design starts with a clear map of how tables connect.
Blog Database Schema
Entity Relationship (Mermaid)
Blog Database ER DiagramerDiagram
Rendering diagram…
Step 2: Create the Database & Tables
Run these commands in your psql shell.
sql
-- 1. Create the Database-- CREATE DATABASE blog_db;-- \c blog_db-- 2. Users TableCREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password_hash
In a real app, these queries would power the Homepage and Admin Dashboard.
4.1 Latest Published Posts (Homepage Feed)
sql
SELECT posts.title, users.username AS author, categories.name AS category, posts.published_atFROM postsJOIN users ON posts.user_id = users.idJOIN categories ON posts.category_id = categories.idWHERE
4.2 Post Engagement (Comment Count)
sql
SELECT posts.title, COUNT(comments.id) AS total_commentsFROM postsLEFT JOIN comments ON posts.id = comments.post_idGROUP BY posts.id, posts.titleORDER BY total_comments DESC;
Step 5: Production Optimization (Indexes)
Queries get slow as the database grows. We add Indexes to speed them up.
sql
-- Speed up looking up posts by their URL slugCREATE INDEX idx_posts_slug ON posts(slug);-- Speed up filtering by published dateCREATE INDEX idx_posts_published ON posts(published_at) WHERE published = true;
🏆 Final Challenge
To truly test your skills, try to write these queries on your own:
Tag Search: Find all posts that have the tag 'sql'.
Draft List: Find all posts by 'Alice' that are NOT yet published.
Admin Audit: Show a list of all comments along with the username of the person who wrote it and the title of the post it belongs to.
Content Strategy: Which category has the most published posts?
🎉 What You've Accomplished
Final Note
You have completed the SQL Fundamentals course! You now have the foundational skills to build the backend of almost any modern application. Keep practicing, keep building, and stay curious.
VARCHAR
(
255
)
NOT NULL
,
role VARCHAR(20) DEFAULT 'author' CHECK (role IN ('author', 'admin')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 3. Categories Table
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL
);
-- 4. Posts Table
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR(300) NOT NULL,
slug VARCHAR(300) UNIQUE NOT NULL,
content TEXT NOT NULL,
user_id INTEGER NOT NULL REFERENCES users(id),
category_id INTEGER REFERENCES categories(id),
published BOOLEAN DEFAULT false,
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 5. Comments Table
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id),
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 6. Tags & Junction Table (Many-to-Many)
CREATE TABLE tags (
id SERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL
);
CREATE TABLE post_tags (
post_id INTEGER REFERENCES posts(id) ON DELETE CASCADE,
tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE,