How to Build a Simple Blog in Flask: A Complete Step-by-Step Guide
Flask is one of the most popular Python web frameworks, known for its simplicity and flexibility. If you're looking to build a blog application from scratch, Flask provides an excellent foundation that's both beginner-friendly and powerful enough for production use. In this comprehensive guide, we'll walk through creating a fully functional blog application with user authentication, post management, and a clean, responsive interface.
What You'll Learn
By the end of this tutorial, you'll have built a complete blog application featuring: - User registration and authentication - Create, read, update, and delete blog posts - User profiles and post management - Responsive web design - Database integration with SQLAlchemy - Form handling and validation - Security best practices
Prerequisites
Before we begin, make sure you have: - Python 3.6 or higher installed - Basic knowledge of Python programming - Understanding of HTML and CSS basics - Familiarity with command line operations
Setting Up Your Development Environment
Step 1: Create a Virtual Environment
First, let's create a isolated environment for our project:
`bash
mkdir flask-blog
cd flask-blog
python -m venv blog_env
`
Activate the virtual environment:
- On Windows: blog_env\Scripts\activate
- On macOS/Linux: source blog_env/bin/activate
Step 2: Install Required Packages
Install Flask and the necessary extensions:
`bash
pip install Flask
pip install Flask-SQLAlchemy
pip install Flask-WTF
pip install Flask-Login
pip install Flask-Bcrypt
pip install email-validator
`
Create a requirements.txt file to track dependencies:
`bash
pip freeze > requirements.txt
`
Project Structure
Let's organize our project with a clean structure:
`
flask-blog/
│
├── app.py
├── config.py
├── requirements.txt
├── models.py
├── forms.py
├── static/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── main.js
└── templates/
├── base.html
├── index.html
├── login.html
├── register.html
├── profile.html
├── create_post.html
└── post.html
`
Step 3: Configuration Setup
Create a config.py file to manage our application settings:
`python
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secret-key-here'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///blog.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
POSTS_PER_PAGE = 5
`
Step 4: Database Models
Create models.py to define our database structure:
`python
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from flask_bcrypt import Bcrypt
db = SQLAlchemy() bcrypt = Bcrypt()
class User(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) password_hash = db.Column(db.String(128)) posts = db.relationship('Post', backref='author', lazy=True) created_at = db.Column(db.DateTime, default=datetime.utcnow)
def set_password(self, password): self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
def check_password(self, password): return bcrypt.check_password_hash(self.password_hash, password)
def __repr__(self):
return f'
class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) content = db.Column(db.Text, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f'`
Step 5: Form Classes
Create forms.py for handling user input validation:
`python
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from models import User
class RegistrationForm(FlaskForm): username = StringField('Username', validators=[DataRequired(), Length(min=4, max=20)]) email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired(), Length(min=6)]) confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')]) submit = SubmitField('Sign Up')
def validate_username(self, username): user = User.query.filter_by(username=username.data).first() if user: raise ValidationError('Username already exists. Choose a different one.')
def validate_email(self, email): user = User.query.filter_by(email=email.data).first() if user: raise ValidationError('Email already registered. Choose a different one.')
class LoginForm(FlaskForm): email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired()]) submit = SubmitField('Sign In')
class PostForm(FlaskForm):
title = StringField('Title', validators=[DataRequired(), Length(max=200)])
content = TextAreaField('Content', validators=[DataRequired()])
submit = SubmitField('Publish')
`
Step 6: Main Application File
Now, let's create the main app.py file:
`python
from flask import Flask, render_template, url_for, flash, redirect, request, abort
from flask_login import LoginManager, login_user, current_user, logout_user, login_required
from config import Config
from models import db, bcrypt, User, Post
from forms import RegistrationForm, LoginForm, PostForm
def create_app(): app = Flask(__name__) app.config.from_object(Config) # Initialize extensions db.init_app(app) bcrypt.init_app(app) # Setup Flask-Login login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'login' login_manager.login_message_category = 'info' @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) return app
app = create_app()
@app.route('/') @app.route('/home') def home(): page = request.args.get('page', 1, type=int) posts = Post.query.order_by(Post.created_at.desc()).paginate( page=page, per_page=app.config['POSTS_PER_PAGE'], error_out=False) return render_template('index.html', posts=posts)
@app.route('/register', methods=['GET', 'POST']) def register(): if current_user.is_authenticated: return redirect(url_for('home')) form = RegistrationForm() if form.validate_on_submit(): user = User(username=form.username.data, email=form.email.data) user.set_password(form.password.data) db.session.add(user) db.session.commit() flash('Registration successful!', 'success') return redirect(url_for('login')) return render_template('register.html', form=form)
@app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('home')) form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user and user.check_password(form.password.data): login_user(user) next_page = request.args.get('next') return redirect(next_page) if next_page else redirect(url_for('home')) flash('Login failed. Check email and password.', 'danger') return render_template('login.html', form=form)
@app.route('/logout') def logout(): logout_user() return redirect(url_for('home'))
@app.route('/profile') @login_required def profile(): posts = Post.query.filter_by(author=current_user).order_by(Post.created_at.desc()).all() return render_template('profile.html', posts=posts)
@app.route('/create_post', methods=['GET', 'POST']) @login_required def create_post(): form = PostForm() if form.validate_on_submit(): post = Post(title=form.title.data, content=form.content.data, author=current_user) db.session.add(post) db.session.commit() flash('Your post has been created!', 'success') return redirect(url_for('home')) return render_template('create_post.html', form=form, legend='New Post')
@app.route('/post/
@app.route('/post/
@app.route('/post/
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
`
Step 7: HTML Templates
Base Template (templates/base.html)
`html
{% block content %}{% endblock %}