Creating a dynamic blog using Flask and HTMX can be both fun and rewarding. This guide will take you through the entire process, focusing on making your blog interactive without the need for a complex single-page application (SPA) framework. By the end, you'll have a fully functional blog where users can create, read, update, and delete posts seamlessly.
What You'll Need
- Basic knowledge of HTML, CSS, and JavaScript
- Basic understanding of Python and Flask (or your preferred backend framework)
- Python and pip installed on your machine
Step 1: Setting Up Your Environment
1.1 Install Flask
First things first, let's set up our Flask environment. Open your terminal and create a virtual environment, then install Flask:
python -m venv venv
source venv/bin/activate # On Windows, use `venv\Scripts\activate`
pip install Flask Flask-SQLAlchemy
1.2 Create the Project Structure
Organize your project directory as follows:
blog_app/
βββ static/
β βββ css/
β β βββ styles.css
β βββ js/
β βββ scripts.js
βββ templates/
β βββ base.html
β βββ index.html
β βββ post.html
β βββ edit_post.html
β βββ post_snippet.html
βββ app.py
βββ models.py
Step 2: Create the Flask Backend
2.1 Define Models
In models.py, define a simple data model for blog posts using SQLAlchemy:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
2.2 Set Up Flask Application
Next, set up your Flask application in app.py:
---Note---: SQLite is included with Python as a built-in library, which means you don't need to install it separately. SQLite is a lightweight, disk-based database that doesnβt require a separate server process and allows access to the database using a nonstandard variant of the SQL query language.
from flask import Flask, render_template, request, redirect, url_for
from models import db, Post
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
with app.app_context():
db.create_all() # Create database tables
@app.before_request
def method_override():
if request.method == 'POST' and '_method' in request.form:
method = request.form['_method'].upper()
if method in ['PUT', 'DELETE', 'PATCH']:
request.environ['REQUEST_METHOD'] = method
@app.route('/')
def index():
posts = Post.query.all()
return render_template('index.html', posts=posts)
@app.route('/post/<int:post_id>')
def post(post_id):
post = Post.query.get_or_404(post_id)
return render_template('post.html', post=post)
@app.route('/create', methods=['POST'])
def create():
try:
title = request.form['title']
content = request.form['content']
if not title or not content:
raise ValueError("Title and content cannot be empty")
new_post = Post(title=title, content=content)
db.session.add(new_post)
db.session.commit()
# Render the new post as HTML
return render_template('post_snippet.html', post=new_post)
except Exception as e:
print(f"Error occurred: {e}")
db.session.rollback()
return '', 500 # Return an error response
@app.route('/edit/<int:post_id>', methods=['GET', 'POST'])
def edit(post_id):
post = Post.query.get_or_404(post_id)
if request.method == 'POST':
post.title = request.form['title']
post.content = request.form['content']
db.session.commit()
return redirect(url_for('post', post_id=post.id))
return render_template('edit_post.html', post=post)
@app.route('/delete/<int:post_id>', methods=['POST', 'DELETE'])
def delete(post_id):
post = Post.query.get_or_404(post_id)
db.session.delete(post)
db.session.commit()
return '<div id="post-{}"></div>'.format(post_id) # Return an empty div to swap the deleted post
if __name__ == '__main__':
app.run(debug=True)
Step 3: Create HTML Templates
3.1 Base Template
In templates/base.html, define the base HTML structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blog App</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
<script src="{{ url_for('static', filename='js/scripts.js') }}" defer></script>
</head>
<body>
<nav class="navbar">
<a href="{{ url_for('index') }}">Home</a>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
</body>
</html>
3.2 Index Template
In templates/index.html, create the index page to list all posts:
{% extends "base.html" %}
{% block content %}
<h1>Blog Posts</h1>
<form hx-post="{{ url_for('create') }}" hx-target="#posts" hx-swap="beforeend" method="post">
<input type="text" name="title" placeholder="Title" required>
<textarea name="content" placeholder="Content" required></textarea>
<button type="submit" class="btn btn-primary">Create</button>
</form>
<div id="posts">
{% for post in posts %}
{% include 'post_snippet.html' %}
{% endfor %}
</div>
{% endblock %}
3.3 Post Template
In templates/post.html, create the template for displaying a single post:
{% extends "base.html" %}
{% block content %}
<div class="post">
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<div class="post-buttons">
<a href="{{ url_for('edit', post_id=post.id) }}" class="btn btn-primary">Edit</a>
</div>
</div>
{% endblock %}
3.4 Post Snippet Template
In templates/post_snippet.html, create a snippet for individual posts to be used for dynamic updates:
<div class="post" id="post-{{ post.id }}">
<h2><a href="{{ url_for('post', post_id=post.id) }}">{{ post.title }}</a></h2>
<p>{{ post.content }}</p>
<div class="post-buttons">
<form action="{{ url_for('delete', post_id=post.id) }}" hx-delete="{{ url_for('delete', post_id=post.id) }}" hx-target="#post-{{ post.id }}" hx-swap="outerHTML" method="post" class="delete-form">
<a href="{{ url_for('edit', post_id=post.id) }}" class="btn btn-primary">Edit</a>
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</div>
</div>
3.5 Edit Post Template
In templates/edit_post.html, create the template for editing a post:
{% extends "base.html" %}
{% block content %}
<h1>Edit Post</h1>
<form method="post">
<input type="text" name="title" value="{{ post.title }}" required>
<textarea name="content" required>{{ post.content }}</textarea>
<button type="submit" class="btn btn-primary">Save</button>
</form>
{% endblock %}
π₯ Fired up to learn HTMX in more depth? This is a MUST read for leveling up. π
Hypermedia SystemsΒ Kindle Edition
Step 4: Styling the Application
Create a simple CSS file (styles.css) to style your blog:
/* General Styles */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.container {
width: 80%;
margin: 0 auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.navbar {
background-color: #343a40;
color: #fff;
padding: 10px;
text-align: center;
}
.navbar a {
color: #fff;
margin-right: 10px;
text-decoration: none;
}
h1 {
color: #343a40;
text-align: center;
margin-bottom: 20px;
}
/* Form Styles */
form {
margin-bottom: 20px;
padding: 20px;
background: #f9f9f9;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}
form input, form textarea {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 5px;
}
form button {
font-size: 0.8rem;
background-color: #007bff;
color: #fff;
border: none;
padding: 10px 20px;
cursor: pointer;
border-radius: 5px;
}
form button:hover {
background-color: #0056b3;
}
/* Post Styles */
.post {
padding: 20px;
background: #fff;
margin-bottom: 20px;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
}
.post:hover {
transform: scale(1.02);
}
.post h2 {
margin-top: 0;
color: #6c757d;
}
.post p {
margin: 10px 0;
color: #6c757d;
}
/* Post Buttons Styles */
.post-buttons {
display: flex;
gap: 10px;
margin-top: 10px;
}
.post-buttons .btn {
padding: 8px 16px;
border-radius: 5px;
font-size: 0.8rem;
border: none;
cursor: pointer;
text-align: center;
transition: background-color 0.3s, color 0.3s;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none; /* Remove underline for anchor tags */
}
.post-buttons .edit-btn, .post-buttons .delete-btn {
display: inline-flex;
align-items: center;
justify-content: center;
}
.post-buttons .btn-primary {
background-color: #007bff;
color: #fff;
}
.post-buttons .btn-primary:hover {
background-color: #0056b3;
}
.post-buttons .btn-danger {
background-color: #dc3545;
color: #fff;
}
.post-buttons .btn-danger:hover {
background-color: #c82333;
}
.delete-form {
display: flex;
align-items: center;
gap: 10px; /* Ensure space between the buttons within the form */
}
Step 5: Add Enhanced Debugging for HTMX
Create a simple JavaScript file (scripts.js) to handle HTMX events for better debugging:
/* static/js/scripts.js */
document.addEventListener('htmx:afterRequest', (event) => {
console.log('HTMX request completed:', event.detail);
});
document.addEventListener('htmx:error', (event) => {
console.error('HTMX request error:', event.detail);
});
Step 6: Testing Your Application
Now that you have set up the backend, created the HTML templates, and added HTMX for interactivity, itβs time to test your application. Make sure your Flask server is running by using the command:
flask --debug run
Open your web browser and navigate to http://127.0.0.1:5000/. You should see your blog's home page, where you can create, view, edit, and delete blog posts.
Create a Post
Enter a title and content in the form at the top of the page.
Click the "Create" button. The new post should appear instantly on the page without a full page reload.
View a Post
Click on the title of a post to view its full content on a separate page.
Edit a Post
Click the "Edit" link next to a post.
Modify the title or content and click "Save". You should be redirected to the updated post's page.
Click home on top to go back to home page.
Delete a Post
Click the "Delete" button next to a post. The post should be removed instantly without a full page reload.
Conclusion
In this comprehensive tutorial, you have learned how to create a dynamic blog application using Flask and HTMX. Here's a quick recap of what we've covered:
- Setting up a Flask environment and project structure
- Creating and configuring a Flask application
- Defining models with SQLAlchemy
- Creating HTML templates for your blog
- Adding HTMX attributes for dynamic form submission and deletion
- Styling your application with CSS
By following these steps, you can build modern web applications with enhanced interactivity without the need for complex single-page application frameworks. HTMX allows you to keep your workflow simple and productive while providing a smooth user experience.
Further Reading and Resources
To deepen your understanding and keep up with the latest trends and best practices in web development, here are somπ½e resources you might find helpful:
By leveraging these resources, you can continue to enhance your skills and stay updated with the latest trends and best practices in web development. Happy coding!
This guide provides a complete walkthrough for creating a dynamic blog using Flask and HTMX, focusing on interactivity and simplicity. By following these steps, you'll have a modern, interactive blog application that can easily be expanded and customized to meet your needs.
Top comments (0)