A full-stack blog application built with Flask, SQLAlchemy, and Bootstrap 5. Supports user authentication, role-based admin access, comments, contact form via Mailtrap, and rate limiting.
- User registration and login with hashed passwords (pbkdf2:sha256)
- Role-based admin access (create, edit, delete posts)
- Rich text post editor via CKEditor 4.25.1
- Comment system for authenticated users with XSS sanitization
- Gravatar profile images on comments (SHA256)
- Contact form with Mailtrap SMTP and error handling
- Rate limiting on login and register routes (Flask-Limiter)
- XSS protection via Bleach on all user input
- Token-protected admin setup route
- PostgreSQL support for production (SQLite for local dev)
- Timezone-aware datetime (UTC)
- DB connection pool with pre-ping and timeout
- Dev server binds to
127.0.0.1, production binds to0.0.0.0
- Backend: Flask, SQLAlchemy, Flask-Login, Flask-WTF, Flask-Limiter, Bleach
- Frontend: Bootstrap 5, CKEditor 4.25.1 (CDN)
- Database: SQLite (dev) / PostgreSQL (production)
- Deployment: Render (gunicorn)
- Passwords hashed with
pbkdf2:sha256via Werkzeug - All user input sanitized with
bleach.clean() - Gravatar hashed with SHA256 (not MD5)
- Role-based admin access (
role='admin'in DB) - Rate limiting on
/loginand/register(10 req/min) - CSRF protection on all forms via Flask-WTF
- Admin setup route is token-protected and commented out by default
.envexcluded from version control via.gitignore- Debug mode controlled via
FLASK_DEBUGenv var
- Clone the repo:
git clone <your_repo_url>
cd shubham-blog
- Create and activate a virtual environment:
python -m venv .venv
.venv\Scripts\activate # Windows
source .venv/bin/activate # macOS/Linux
- Install dependencies:
pip install -r requirements.txt
- Create your
.envfile from the example:
copy .env.example .env # Windows
cp .env.example .env # macOS/Linux
- Fill in your
.envvalues:
FLASK_KEY=<generate: python -c "import secrets; print(secrets.token_hex(32))">
FLASK_DEBUG=true
DB_URI=sqlite:///posts.db
MAILTRAP_USER=<your_mailtrap_username>
MAILTRAP_PASS=<your_mailtrap_password>
MAILTRAP_TO=<recipient_email>
- Run the app:
python main.py
- Open
http://localhost:5001in your browser.
After registering your first user, open DB Browser for SQLite:
- Open
instance/posts.db - Go to Execute SQL tab
- Run:
UPDATE users SET role='admin' WHERE id=1;- Click Write Changes
- Log out and log back in
- Push the project to GitHub:
git init
git add .
git commit -m "initial commit"
git remote add origin <your_repo_url>
git push -u origin main
-
Create a PostgreSQL database on Render:
- Render dashboard → New → PostgreSQL
- Copy the External Database URL
-
Create a new Web Service on Render:
- Connect your GitHub repo
- Build command:
pip install -r requirements.txt - Start command:
gunicorn main:app --bind 0.0.0.0:$PORT --timeout 180 --workers 1 - Region: must match your PostgreSQL region
-
Add environment variables in Render dashboard:
FLASK_KEY=<generate with: python -c "import secrets; print(secrets.token_hex(32))">
FLASK_DEBUG=false
DB_URI=<External Database URL from Render PostgreSQL>
MAILTRAP_USER=<your_mailtrap_username>
MAILTRAP_PASS=<your_mailtrap_password>
MAILTRAP_TO=<recipient_email>
- Deploy and wait for
Your service is live 🎉
The /make-admin/<token> route is available in the code (commented out). To use it:
- Uncomment the route in
main.py - Generate a token:
python -c "import secrets; print(secrets.token_hex(16))"
- Add
ADMIN_TOKEN=<your_token>to Render environment variables - Push and deploy
- Register your first user on the live site
- Visit:
https://<your-render-url>/make-admin/<your_token>
- You'll see "Done - delete ADMIN_TOKEN from environment variables now!"
- Delete
ADMIN_TOKENfrom Render env vars and comment the route back out - Push again
| Variable | Description |
|---|---|
FLASK_KEY |
Flask secret key for sessions |
FLASK_DEBUG |
Set to false in production |
PRODUCTION |
Set to true on any hosting platform to bind to 0.0.0.0 |
DB_URI |
Database connection string (use External URL on Render) |
MAILTRAP_USER |
Mailtrap SMTP username |
MAILTRAP_PASS |
Mailtrap SMTP password |
MAILTRAP_TO |
Email address to receive contact messages |
ADMIN_TOKEN |
Token for admin setup route (delete after use) |
pip install Flask-Limiter==3.9.0
Make sure requirements.txt has psycopg2-binary not psycopg-binary:
psycopg2-binary==2.9.10
pip install email-validator==2.2.0
- Make sure
DB_URIis set to the External Database URL from Render PostgreSQL - The URL must include the full hostname ending in
.oregon-postgres.render.com:5432 - Web Service and PostgreSQL must be in the same region
- Use
?sslmode=disableat the end of the internal URL if external URL has DNS issues:
postgresql://user:password@dpg-xxxx-a/dbname?sslmode=disable
- Switch from Internal to External Database URL in
DB_URIenv var on Render
SQLAlchemy requires postgresql:// — the code auto-fixes this, but make sure your URL starts with postgresql:// in the env var.
When adding a post, click the Source button (< >) in CKEditor toolbar, paste your HTML content directly, then click Source again to preview before saving.
The app loads CKEditor 4.25.1 directly from CDN — no action needed.
Your user's role is not set to admin. Follow the admin setup steps above.