16. Live Project: Building a Reels App with EC2, S3 and RDS
16.4 Database Schema
Teen tables: users, posts (video kiwa text), likes. Part 5 madhle keys aani constraints ithe kharya project madhe vaparle aahet – PRIMARY KEY, UNIQUE, FOREIGN KEY, composite key, CHECK, DEFAULT, AUTO_INCREMENT.
schema.sql
-- schema.sql : run once as the admin user
-- mysql -h <rds-endpoint> -u admin -p < schema.sql
CREATE DATABASE IF NOT EXISTS reelsdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE reelsdb;
CREATE TABLE IF NOT EXISTS users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(30) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_users_username UNIQUE (username)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS posts (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
post_type ENUM('video','text') NOT NULL,
caption VARCHAR(500) NOT NULL DEFAULT '',
body_text VARCHAR(1000) NULL, -- text posts
bg_color CHAR(7) NOT NULL DEFAULT '#0E7C86',
s3_key VARCHAR(255) NULL, -- video posts: key inside the bucket
mime_type VARCHAR(50) NULL,
size_bytes BIGINT UNSIGNED NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_posts_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT chk_post_content CHECK (
(post_type = 'video' AND s3_key IS NOT NULL) OR
(post_type = 'text' AND body_text IS NOT NULL)
),
INDEX idx_posts_user (user_id)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS likes (
user_id INT UNSIGNED NOT NULL,
post_id BIGINT UNSIGNED NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, post_id), -- composite key: one like per user per post
CONSTRAINT fk_likes_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT fk_likes_post FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE
) ENGINE=InnoDB;
Run it as the admin user, then create the app user (Chapter 15.4) with only four privileges:
mysql -h reels-db.abcdefgh1234.ap-south-1.rds.amazonaws.com -u admin -p < schema.sql
mysql -h reels-db.abcdefgh1234.ap-south-1.rds.amazonaws.com -u admin -p -e "
CREATE USER IF NOT EXISTS 'reels_app'@'%' IDENTIFIED BY 'Use-A-Long-Random-Password-Here!';
GRANT SELECT, INSERT, UPDATE, DELETE ON reelsdb.* TO 'reels_app'@'%';"
mysql -h reels-db.abcdefgh1234.ap-south-1.rds.amazonaws.com -u reels_app -p reelsdb -e "SHOW TABLES;"
| Design choice | Reason |
|---|---|
Video file in S3, only s3_key in MySQL |
Databases are bad at storing large files; S3 is cheap and scales |
password_hash column, VARCHAR(255) |
Stores bcrypt/argon2 hashes, never plain passwords |
likes composite primary key (user_id, post_id) |
The database itself prevents double likes |
ON DELETE CASCADE |
Deleting a user removes their posts and likes – no orphan rows |
CHECK on post_type |
A video post without a key, or a text post without text, is impossible |
Ravindra Bagale's Tip
Many students save the video's full S3 URL (even the presigned one!) in the database – and after 20 minutes every video gives "Access Denied". Store only the key (videos/2026/09/abc.mp4) in the database, and generate a fresh URL on every request. This is the most important design point of the project – remember it.
Ravindra Bagale's Tip – मराठी
बरेच students video चा पूर्ण S3 URL (presigned सुद्धा!) database मध्ये save करतात – आणि 20 मिनिटांनी सगळे videos "Access Denied" देतात. Database मध्ये फक्त key (videos/2026/09/abc.mp4) ठेवा; URL प्रत्येक request ला नवीन बनवा. हा project चा सगळ्यात important design point आहे, लक्षात ठेवा.
Ravindra Bagale's Tip – हिंदी
बहुत से students video का पूरा S3 URL (presigned वाला भी!) database में save कर देते हैं – और 20 मिनट बाद सारे videos "Access Denied" देते हैं. Database में सिर्फ़ key (videos/2026/09/abc.mp4) रखो; URL हर request पर नया बनाओ. यह project का सबसे important design point है, याद रखो.
Practice task
Load the schema, then try to insert a video post with s3_key NULL and a second like for the same user and post. Write down both error messages and which constraint caused each.