Building a blog with Python and Flask is one of the most rewarding ways to learn web development. In this step‑by‑step tutorial you’ll create a fully functional blog application from scratch, covering everything from environment setup to deployment. Whether you’re a beginner looking for a practical project or an experienced developer wanting a lightweight CMS, this guide provides clear code examples, best‑practice tips, and SEO‑friendly content that will help your site rank higher in search results.
Why Choose Flask for a Blog?
Flask is a micro‑framework that gives you the flexibility to design your application exactly the way you want, without the overhead of large, opinionated frameworks. Its lightweight nature makes it perfect for a blog because you can start small and scale up as your audience grows. Key advantages include:
- Simple routing: Define URLs with just a decorator.
- Jinja2 templating: Powerful, Python‑like syntax for dynamic HTML.
- Extensible ecosystem: Add extensions for databases, authentication, and more.
- Great documentation: Plenty of tutorials and community support.
Prerequisites
Before diving into code, make sure you have the following tools installed on your development machine:
- Python 3.9 or newer
- pip (Python package manager)
- Git (optional, but useful for version control)
- A code editor such as VS Code, PyCharm, or Sublime Text
Familiarity with basic Python syntax and HTML will make the tutorial smoother, but each concept is explained in plain language.
Step 1: Setting Up the Project Environment
Create a virtual environment
Isolating your project’s dependencies prevents conflicts with other Python applications. Run the following commands in your terminal:
mkdir flask_blog
cd flask_blog
python -m venv venv
source venv/bin/activate # On Windows use: venv\Scripts\activate
Install Flask and required extensions
We’ll use Flask for the core framework, Flask‑SQLAlchemy for database handling, and Flask‑Migrate for migrations.
pip install Flask Flask-SQLAlchemy Flask-Migrate
Step 2: Creating the Basic Flask Application
Project structure
Organize your files as shown below. This layout keeps the code clean and makes future extensions easier.
flask_blog/
│
├─ app/
│ ├─ __init__.py
│ ├─ models.py
│ ├─ routes.py
│ └─ templates/
│ ├─ base.html
│ ├─ index.html
│ └─ post.html
│
├─ migrations/
│ └─ (auto‑generated)
│
├─ config.py
└─ run.py
Initialize the Flask app
Create app/__init__.py and configure the core components:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
def create_app():
app = Flask(__name__)
app.config.from_object('config.Config')
db.init_app(app)
migrate.init_app(app, db)
with app.app_context():
from . import routes, models # Import routes & models
return app
Configuration file
Store sensitive settings in config.py. For a simple blog, SQLite works well.
import os
class Config:
SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key')
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL', 'sqlite:///blog.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
Step 3: Defining the Database Model
In app/models.py we’ll create a Post model that stores the title, content, and timestamp of each blog entry.
from datetime import datetime
from . import db
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(150), nullable=False)
slug = db.Column(db.String(150), unique=True, nullable=False)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self):
return f'<Post {self.title}>'
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'slug': self.slug,
'content': self.content,
'created_at': self.created_at.isoformat()
}
Step 4: Building Routes and Views
Home page – list of posts
The home route fetches all posts from the database and renders them with Jinja2.
from flask import render_template, abort, request, redirect, url_for, flash
from . import db
from .models import Post
from . import create_app
app = create_app()
@app.route('/')
def index():
posts = Post.query.order_by(Post.created_at.desc()).all()
return render_template('index.html', posts=posts)
Single post page
@app.route('/post/<slug>')
def post_detail(slug):
post = Post.query.filter_by(slug=slug).first_or_404()
return render_template('post.html', post=post)
Creating a new post (CRUD – Create)
For simplicity, we’ll use a basic HTML form. In production you’d protect this route with authentication.
@app.route('/admin/new', methods=['GET', 'POST'])
def new_post():
if request.method == 'POST':
title = request.form['title']
slug = request.form['slug']
content = request.form['content']
post = Post(title=title, slug=slug, content=content)
db.session.add(post)
db.session.commit()
flash('Post created successfully!', 'success')
return redirect(url_for('index'))
return render_template('new_post.html')
Step 5: Designing Templates with Jinja2
Base layout
All pages inherit from base.html, which includes the navigation bar, SEO meta tags, and a content block.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Flask Blog{% endblock %}</title>
<meta name="description" content="A simple, SEO‑optimized blog built with Python Flask.">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<nav>
<a href="{{ url_for('index') }}">Home</a> |
<a href="{{ url_for('new_post') }}">New Post</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© {{ datetime.utcnow().year }} My Flask Blog</p>
</footer>
</body>
</html>
Index page
{% extends "base.html" %}
{% block title %}Home – My Flask Blog{% endblock %}
{% block content %}
<h2>Latest Posts</h2>
{% for post in posts %}
<article>
<h3><a href="{{ url_for('post_detail', slug=post.slug) }}">{{ post.title }}</a></h3>
<small>Published on {{ post.created_at.strftime('%B %d, %Y') }}</small>
<p>{{ post.content[:200] }}...</p>
</article>
<hr>
{% else %}
<p>No posts yet. Be the first to <a href="{{ url_for('new_post') }}">publish</a>!</p>
{% endfor %}
{% endblock %}
Single post page
{% extends "base.html" %}
{% block title %}{{ post.title }} – My Flask Blog{% endblock %}
{% block content %}
<article>
<h2>{{ post.title }}</h2>
<small>{{ post.created_at.strftime('%B %d, %Y') }}</small>
<div>{{ post.content|safe }}</div>
</article>
<a href="{{ url_for('index') }}">← Back to Home</a>
{% endblock %}
Leave a Reply