Building a Simple Blog with Flask and SQLite

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.

  1. Create a Project Directory:
    Make a new folder for your project. You can name it my_simple_blog.

    bash
    mkdir my_simple_blog
    cd my_simple_blog

  2. 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 venv

    This command creates a folder named venv inside your project directory.

  3. 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.

  4. 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.

  1. Create schema.sql:
    Inside your my_simple_blog directory, create a file named schema.sql and 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 the posts table if it already exists, ensuring we start fresh.
    CREATE TABLE posts (...);: This creates a new table named posts.
    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.

  2. Create init_db.py:
    Next, create a Python script to actually create the database file and set up our table using the schema.sql file. Name this file init_db.py.

    “`python
    import sqlite3

    Connect 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 from schema.sql to create the posts table. 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 from base.html.
  • {% block content %}: This defines the content for the content block in base.html.
  • {% for post in posts %}: This loop iterates through the posts list that we passed from app.py.
  • {{ post['title'] }}: Displays the title of 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.

  1. Initialize the Database:
    Open your terminal, make sure your virtual environment is active, navigate to your my_simple_blog directory, and run the init_db.py script:

    bash
    python init_db.py

    You should see “Database initialized successfully!” and a new file database.db will appear in your project folder.

  2. Run the Flask Application:
    Now, run your main Flask application:

    bash
    python app.py

    Flask will tell you it’s running, usually at http://127.0.0.1:5000/.

  3. Open in Browser:
    Open your web browser and go to http://127.0.0.1:5000/. You should see your blog homepage with the two initial posts!

    Try navigating to /create to 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 static folder.
  • 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!

Comments

Leave a Reply