Welcome, aspiring web developers! Have you ever wanted to create your own corner on the internet, perhaps to share your thoughts, photos, or recipes? Building a blog is a fantastic way to start your journey into web development. It introduces you to many core concepts in a practical and fun way.
In this guide, we’re going to build a very simple blog application using two powerful yet beginner-friendly tools: Flask and SQLite. Don’t worry if those names sound intimidating; we’ll explain everything in simple terms. By the end, you’ll have a basic blog where you can create and view posts, and you’ll have a solid foundation to build upon!
What is Flask?
Flask is a “micro” web framework for Python. Think of it as a helpful toolkit that makes it easier to build web applications without getting bogged down in too many rules or complex setups. It’s called “micro” because it provides the essentials but lets you decide how to add extra features. This makes it perfect for beginners and small-to-medium projects.
What is a Web Framework?
A web framework is a collection of libraries and tools that provide a structure for building web applications quickly and efficiently. It handles many common tasks, so you don’t have to start from scratch.
What is SQLite?
SQLite is a super lightweight, file-based database. Unlike big database systems that need a separate server, SQLite stores all your data in a single file on your computer. This makes it incredibly easy to set up and use, especially for small projects or when you’re just learning. Your blog posts, for example, will be stored inside this SQLite file.
What is a Database?
A database is an organized collection of information (data) that can be easily accessed, managed, and updated. Imagine it like a super-organized digital filing cabinet for your application’s data.
Setting Up Your Environment
Before we write any code, let’s prepare your workspace.
-
Create a Project Directory:
Make a new folder for your project. You can name itmy_simple_blog.bash
mkdir my_simple_blog
cd my_simple_blog -
Create a Virtual Environment:
A virtual environment is an isolated space for your Python projects. It means that the packages (like Flask) you install for one project won’t interfere with other projects on your computer. It’s a best practice!bash
python3 -m venv venvThis command creates a folder named
venvinside your project directory. -
Activate Your Virtual Environment:
You need to “activate” this environment so that any packages you install go into it.-
On macOS/Linux:
bash
source venv/bin/activate -
On Windows (Command Prompt):
bash
venv\Scripts\activate -
On Windows (PowerShell):
bash
.\venv\Scripts\Activate.ps1
You’ll notice
(venv)appearing at the beginning of your command prompt, indicating that the virtual environment is active. -
-
Install Flask:
Now that your virtual environment is active, let’s install Flask!bash
pip install Flask
Project Structure
It’s good practice to organize your files. Here’s how our project will look:
my_simple_blog/
├── venv/ # Virtual environment files
├── app.py # Our main Flask application
├── schema.sql # Database schema for SQLite
├── init_db.py # Script to initialize the database
└── templates/ # Folder for HTML templates
├── base.html
├── index.html
└── create.html
Database Setup with SQLite
First, we need to tell SQLite what kind of data our blog posts will have. We’ll define a table named posts with columns for the id, title, and content of each post, along with a created timestamp.
-
Create
schema.sql:
Inside yourmy_simple_blogdirectory, create a file namedschema.sqland add the following SQL code:“`sql
DROP TABLE IF EXISTS posts;CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
title TEXT NOT NULL,
content TEXT NOT NULL
);
“`SQL (Structured Query Language):
SQL is a special programming language used to communicate with databases. It allows you to create, retrieve, update, and delete data.
–DROP TABLE IF EXISTS posts;: This line removes thepoststable if it already exists, ensuring we start fresh.
–CREATE TABLE posts (...);: This creates a new table namedposts.
–id INTEGER PRIMARY KEY AUTOINCREMENT: A unique number for each post, which automatically increases for new posts.
–created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP: The exact time the post was created.
–title TEXT NOT NULL: The title of the blog post, which cannot be empty.
–content TEXT NOT NULL: The actual text content of the blog post, which also cannot be empty. -
Create
init_db.py:
Next, create a Python script to actually create the database file and set up our table using theschema.sqlfile. Name this fileinit_db.py.“`python
import sqlite3Connect to the database file (it will be created if it doesn’t exist)
connection = sqlite3.connect(‘database.db’)
Open and read the schema.sql file
with open(‘schema.sql’) as f:
connection.executescript(f.read())Create a cursor object to execute SQL commands
cur = connection.cursor()
Insert some initial data (optional)
cur.execute(“INSERT INTO posts (title, content) VALUES (?, ?)”,
(‘First Post’, ‘Content for the first post’))cur.execute(“INSERT INTO posts (title, content) VALUES (?, ?)”,
(‘Second Post’, ‘Content for the second post’))Save the changes
connection.commit()
Close the connection
connection.close()
print(“Database initialized successfully!”)
“`This script connects to a file named
database.db(which will be our SQLite database). It then reads and executes the SQL commands fromschema.sqlto create thepoststable. Finally, it inserts two example blog posts so we have some data to display right away.
Creating Your Flask Application (app.py)
Now for the heart of our application! Create a file named app.py in your my_simple_blog directory and start by adding these lines:
import sqlite3
from flask import Flask, render_template, request, url_for, flash, redirect, g
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your secret key' # Replace with a strong, unique key for production!
Brief explanations:
– sqlite3: Python’s built-in module for working with SQLite databases.
– Flask: The main Flask class.
– render_template: A Flask function to display HTML files.
– request: A Flask object that holds all incoming request data (like form submissions).
– url_for: A Flask function to build URLs, which is useful for linking between pages.
– flash: A Flask function for displaying one-time messages to the user (e.g., “Post created successfully!”).
– redirect: A Flask function to send the user to a different URL.
– g: A special Flask object to store data that is specific to the current request.
– app = Flask(__name__): This creates your Flask application instance.
– app.config['SECRET_KEY']: A secret key used for security purposes like sessions and flashing messages. Choose a complex, unique string for real applications!
Database Connection Functions
We’ll define helper functions to connect to and close our database. We’ll use Flask’s g object to store the database connection so it can be reused during a single request.
def get_db_connection():
# Check if a connection already exists in the 'g' object
if 'db' not in g:
# If not, establish a new connection
conn = sqlite3.connect('database.db')
# This line ensures that you can access columns by name instead of by index
conn.row_factory = sqlite3.Row
g.db = conn # Store the connection in 'g' for this request
return g.db
def close_db(e=None):
# Retrieve the connection from the 'g' object if it exists
db = g.pop('db', None)
# If a connection exists, close it
if db is not None:
db.close()
app.teardown_appcontext(close_db)
g object (Flask):
A special object provided by Flask that allows you to store data that is specific to the current request. It’s a great place to put things like database connections so they can be accessed throughout the handling of a single request.
app.teardown_appcontext:
A feature in Flask that allows you to register functions that will run automatically when the application context is torn down (e.g., after a request has been processed). It’s perfect for cleaning up resources, such as closing database connections.
Fetching a Single Post (Helper Function)
We’ll need a way to get a single post by its ID, especially if we decide to add an individual post view later.
def get_post(post_id):
conn = get_db_connection()
post = conn.execute('SELECT * FROM posts WHERE id = ?',
(post_id,)).fetchone()
conn.close() # Close connection explicitly if not using g context
if post is None:
abort(404) # Flask's way to show a "Page Not Found" error
return post
We’ll need abort from Flask. Let’s add it to the import line:
from flask import Flask, render_template, request, url_for, flash, redirect, g, abort
Routes for Our Blog
Now let’s define the different pages (or “routes”) of our blog.
1. The Index Page (/)
This will be the homepage, displaying all blog posts.
@app.route('/')
def index():
conn = get_db_connection()
posts = conn.execute('SELECT * FROM posts ORDER BY created DESC').fetchall()
# The connection will be closed automatically by close_db registered with teardown_appcontext
return render_template('index.html', posts=posts)
@app.route('/'):
This is a decorator that tells Flask to run the index() function whenever someone visits the root URL (/) of your application.
– SELECT * FROM posts ORDER BY created DESC: This SQL query selects all columns from the posts table and orders them by the created timestamp in descending order (newest first).
– .fetchall(): Retrieves all rows from the query result.
– render_template('index.html', posts=posts): This tells Flask to take the index.html template and pass our posts data to it, which the template will then use to display the posts.
2. Create New Post Page (/create)
This page will have a form to allow users to add new blog posts.
@app.route('/create', methods=('GET', 'POST'))
def create():
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
if not title:
flash('Title is required!')
elif not content:
flash('Content is required!')
else:
conn = get_db_connection()
conn.execute('INSERT INTO posts (title, content) VALUES (?, ?)',
(title, content))
conn.commit()
# The connection will be closed automatically by close_db
flash('Post created successfully!')
return redirect(url_for('index'))
return render_template('create.html')
HTTP Methods (GET/POST):
– GET requests are for retrieving information (like viewing a webpage).
– POST requests are for sending data to the server (like submitting a form, creating a new post).
– methods=('GET', 'POST'): This tells Flask that this route can handle both GET (to display the form) and POST (to process the form submission) requests.
– request.method == 'POST': Checks if the incoming request is a POST request (meaning the user submitted the form).
– request.form['title']: Accesses the data submitted through the HTML form field named title.
– flash('Title is required!'): Displays a temporary message to the user if the title is missing.
– conn.execute('INSERT INTO posts ...'): Inserts the new post’s title and content into the posts table.
– conn.commit(): Saves the changes to the database.
– redirect(url_for('index')): After successfully creating a post, we redirect the user back to the homepage (index route). url_for('index') dynamically generates the URL for the index function.
Running the Flask Application
Finally, add this at the very bottom of your app.py file:
if __name__ == '__main__':
app.run(debug=True)
if __name__ == '__main__': ensures that the app.run() command only executes when app.py is run directly (not when imported as a module). debug=True makes Flask reload automatically on code changes and provides a debugger in the browser for errors. Never use debug=True in a production environment!
Designing Your Templates
Now, let’s create the HTML files that Flask will use to display our blog. These will go in a new folder named templates inside your my_simple_blog directory.
1. base.html
This will be our base template, containing common elements like the HTML structure, head, body, and navigation links. Other templates will “inherit” from this one.
<!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 Simple Blog{% endblock %}</title>
<style>
body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
nav { background-color: #333; padding: 10px 20px; border-radius: 5px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; }
nav a { color: white; text-decoration: none; margin-right: 15px; font-weight: bold; }
nav .nav-right a { background-color: #007bff; padding: 8px 15px; border-radius: 4px; }
nav .nav-right a:hover { background-color: #0056b3; }
h1 { color: #007bff; }
.post { background-color: white; border: 1px solid #ddd; border-radius: 8px; padding: 15px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
.post h2 { margin-top: 0; color: #333; }
.post .content { margin-top: 10px; line-height: 1.6; }
.flash { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; padding: 10px; margin-bottom: 15px; border-radius: 4px; }
form { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
form label { display: block; margin-bottom: 8px; font-weight: bold; }
form input[type="text"], form textarea { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
form textarea { resize: vertical; min-height: 100px; }
form button { background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
form button:hover { background-color: #0056b3; }
</style>
</head>
<body>
<nav>
<a href="{{ url_for('index') }}">Flask Blog</a>
<div class="nav-right">
<a href="{{ url_for('create') }}">New Post</a>
</div>
</nav>
<hr>
<div class="content">
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</div>
</body>
</html>
Jinja2 Templating (Flask’s default):
– {% block title %}{% endblock %}: This is a placeholder. Child templates can override this block to set their own titles.
– {{ url_for('index') }}: This dynamically generates the URL for the index route defined in app.py.
– {% for message in get_flashed_messages() %}: This loop checks if there are any flash messages (like “Title is required!”) and displays them.
– {% block content %}{% endblock %}: This is where the specific content of each child template will be inserted.
– (Simple inline CSS is added for basic styling, you would typically put this in a separate .css file in a static folder for larger projects).
2. index.html
This template extends base.html and displays all our blog posts.
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} All Posts {% endblock %}</h1>
{% for post in posts %}
<div class="post">
<h2>{{ post['title'] }}</h2>
<p class="content">{{ post['content'] }}</p>
<span class="timestamp">Created: {{ post['created'] }}</span>
</div>
{% endfor %}
{% endblock %}
{% extends 'base.html' %}: This line tells Jinja2 that this template inherits frombase.html.{% block content %}: This defines the content for thecontentblock inbase.html.{% for post in posts %}: This loop iterates through thepostslist that we passed fromapp.py.{{ post['title'] }}: Displays thetitleof each post.
3. create.html
This template will display the form to create new posts.
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} Create a New Post {% endblock %}</h1>
<form method="post">
<label for="title">Title</label>
<input type="text" name="title" id="title" required
value="{{ request.form['title'] or '' }}">
<br>
<label for="content">Content</label>
<textarea name="content" id="content" required>{{ request.form['content'] or '' }}</textarea>
<br>
<button type="submit">Submit</button>
</form>
{% endblock %}
value="{{ request.form['title'] or '' }}": If the form was submitted but there was an error (e.g., missing content), this keeps the entered title in the input field so the user doesn’t have to re-type it.
Bringing It All Together (Running the Application)
You’re almost there! Let’s get your blog up and running.
-
Initialize the Database:
Open your terminal, make sure your virtual environment is active, navigate to yourmy_simple_blogdirectory, and run theinit_db.pyscript:bash
python init_db.pyYou should see “Database initialized successfully!” and a new file
database.dbwill appear in your project folder. -
Run the Flask Application:
Now, run your main Flask application:bash
python app.pyFlask will tell you it’s running, usually at
http://127.0.0.1:5000/. -
Open in Browser:
Open your web browser and go tohttp://127.0.0.1:5000/. You should see your blog homepage with the two initial posts!Try navigating to
/createto add a new post. If you leave the title or content empty, you’ll see a flash message!
Next Steps
Congratulations! You’ve built a functional, albeit simple, blog with Flask and SQLite. This is a great starting point, but there’s always more to learn and add:
- Edit and Delete Posts: Add routes and templates to modify or remove existing posts.
- User Authentication: Implement user logins and registrations so only authorized users can create/edit posts.
- Styling (CSS): Make your blog look much nicer by moving the inline styles into a separate CSS file in a
staticfolder. - Pagination: If you have many posts, show only a few per page.
- Markdown Support: Allow users to write posts using Markdown syntax and render it as HTML.
- Deployment: Learn how to host your blog online so others can see it!
Keep experimenting, keep learning, and happy coding!
Leave a Reply
You must be logged in to post a comment.