Building a Basic Blog with Flask and Markdown

Hello there, aspiring web developers and coding enthusiasts! Have you ever wanted to create your own corner on the internet, a simple blog where you can share your thoughts, ideas, or even your coding journey? You’re in luck! Today, we’re going to build a basic blog using two fantastic tools: Flask for our web application and Markdown for writing our blog posts.

This guide is designed for beginners, so don’t worry if some terms sound new. We’ll break down everything into easy-to-understand steps. By the end, you’ll have a functional, albeit simple, blog that you can expand upon!

Why Flask and Markdown?

Before we dive into the code, let’s quickly understand why these tools are a great choice for a basic blog:

  • Flask: This is what we call a “micro web framework” for Python.
    • What is a web framework? Imagine you’re building a house. Instead of crafting every single brick and nail from scratch, you’d use pre-made tools, blueprints, and processes. A web framework is similar: it provides a structure and common tools to help you build web applications faster and more efficiently, handling things like requests from your browser, routing URLs, and generating web pages.
    • Why “micro”? Flask is considered “micro” because it doesn’t make many decisions for you. It provides the essentials and lets you choose how to add other components, making it lightweight and flexible – perfect for learning and building small projects like our blog.
  • Markdown: This is a “lightweight markup language.”
    • What is a markup language? It’s a system for annotating a document in a way that is syntactically distinguishable from the text itself. Think of it like adding special instructions (marks) to your text that tell a program how to display it (e.g., make this bold, make this a heading).
    • Why “lightweight”? Markdown is incredibly simple to write and read. Instead of complex HTML tags (like <b> for bold or <h1> for a heading), you use intuitive symbols (like **text** for bold or # Heading for a heading). It allows you to write your blog posts in plain text files, which are easy to manage and version control.

Getting Started: Setting Up Your Environment

Before we write any Python code, we need to set up our development environment.

1. Install Python

If you don’t have Python installed, head over to the official Python website and download the latest stable version. Make sure to check the box that says “Add Python to PATH” during installation.

2. Create a Virtual Environment

A virtual environment is a self-contained directory that holds a specific version of Python and any libraries (packages) you install for a particular project. It’s like having a separate toolbox for each project, preventing conflicts between different project’s dependencies.

Let’s create one:

  1. Open your terminal or command prompt.
  2. Navigate to the directory where you want to create your blog project. For example:
    bash
    mkdir my-flask-blog
    cd my-flask-blog
  3. Create the virtual environment:
    bash
    python -m venv venv

    This creates a folder named venv (you can name it anything, but venv is common).

3. Activate the Virtual Environment

Now, we need to “enter” our isolated environment:

  • On Windows:
    bash
    .\venv\Scripts\activate
  • On macOS/Linux:
    bash
    source venv/bin/activate

    You’ll notice (venv) appearing at the beginning of your terminal prompt, indicating that the virtual environment is active.

4. Install Flask and Python-Markdown

With our virtual environment active, let’s install the necessary Python packages using pip.
* What is pip? pip is the standard package installer for Python. It allows you to easily install and manage additional libraries that aren’t part of the Python standard library.

pip install Flask markdown

This command installs both the Flask web framework and the markdown library, which we’ll use to convert our Markdown blog posts into HTML.

Our Blog’s Structure

To keep things organized, let’s define a simple folder structure for our blog:

my-flask-blog/
├── venv/                   # Our virtual environment
├── posts/                  # Where our Markdown blog posts will live
│   ├── first-post.md
│   └── another-great-read.md
├── templates/              # Our HTML templates
│   ├── index.html
│   └── post.html
└── app.py                  # Our Flask application code

Create the posts and templates folders inside your my-flask-blog directory.

Building the Flask Application (app.py)

Now, let’s write the core of our application in app.py.

1. Basic Flask Application

Create a file named app.py in your my-flask-blog directory and add the following code:

from flask import Flask, render_template, abort
import os
import markdown

app = Flask(__name__)

@app.route('/')
def index():
    # In a real blog, you'd list all your posts here.
    # For now, let's just say "Welcome!"
    return "<h1>Welcome to My Flask Blog!</h1><p>Check back soon for posts!</p>"

if __name__ == '__main__':
    app.run(debug=True)

Explanation:
* from flask import Flask, render_template, abort: We import necessary components from the Flask library.
* Flask: The main class for our web application.
* render_template: A function to render HTML files (templates).
* abort: A function to stop a request early with an error code (like a “404 Not Found”).
* import os: This module provides a way of using operating system-dependent functionality, like listing files in a directory.
* import markdown: This is the library we installed to convert Markdown to HTML.
* app = Flask(__name__): This creates an instance of our Flask application. __name__ helps Flask locate resources.
* @app.route('/'): This is a “decorator” that tells Flask which URL should trigger the index() function. In this case, / means the root URL (e.g., http://127.0.0.1:5000/).
* app.run(debug=True): This starts the Flask development server. debug=True means that if you make changes to your code, the server will automatically restart, and it will also provide helpful error messages in your browser. Remember to set debug=False for production applications!

Run Your First Flask App

  1. Save app.py.
  2. Go back to your terminal (with the virtual environment active) and run:
    bash
    python app.py
  3. You should see output similar to:
    “`

    • Serving Flask app ‘app’
    • Debug mode: on
      WARNING: This is a development server. Do not use it in a production deployment.
      Use a production WSGI server instead.
    • Running on http://127.0.0.1:5000
      Press CTRL+C to quit
      “`
  4. Open your web browser and go to http://127.0.0.1:5000. You should see “Welcome to My Flask Blog!”

Great! Our Flask app is up and running. Now, let’s make it display actual blog posts written in Markdown.

Creating Blog Posts

Inside your posts/ directory, create a new file named my-first-post.md (the .md extension is important for Markdown files):

Welcome to my very first blog post on my new Flask-powered blog!

This post is written entirely in **Markdown**, which makes it super easy to format.

## What is Markdown good for?
*   Writing blog posts
*   README files for projects
*   Documentation

It's simple, readable, and converts easily to HTML.

Enjoy exploring!

You can create more .md files in the posts/ directory, each representing a blog post.

Displaying Individual Blog Posts

Now, let’s modify app.py to read and display our Markdown files.

from flask import Flask, render_template, abort
import os
import markdown

app = Flask(__name__)
POSTS_DIR = 'posts' # Define the directory where blog posts are stored

def get_post_slugs():
    posts = []
    for filename in os.listdir(POSTS_DIR):
        if filename.endswith('.md'):
            slug = os.path.splitext(filename)[0] # Get filename without .md
            posts.append(slug)
    return posts

def read_markdown_post(slug):
    filepath = os.path.join(POSTS_DIR, f'{slug}.md')
    if not os.path.exists(filepath):
        return None, None # Post not found

    with open(filepath, 'r', encoding='utf-8') as f:
        content = f.read()

    # Optional: Extract title from the first heading in Markdown
    lines = content.split('\n')
    title = "Untitled Post"
    if lines and lines[0].startswith('# '):
        title = lines[0][2:].strip() # Remove '# ' and any leading/trailing whitespace

    html_content = markdown.markdown(content) # Convert Markdown to HTML
    return title, html_content

@app.route('/')
def index():
    post_slugs = get_post_slugs()
    # In a real app, you might want to read titles for the list too.
    return render_template('index.html', post_slugs=post_slugs)

@app.route('/posts/<slug>')
def post(slug):
    title, content = read_markdown_post(slug)
    if content is None:
        abort(404) # Show a 404 Not Found error if post doesn't exist

    return render_template('post.html', title=title, content=content)

if __name__ == '__main__':
    app.run(debug=True)

New Additions Explained:
* POSTS_DIR = 'posts': A constant to easily reference our posts directory.
* get_post_slugs(): This function iterates through our posts/ directory, finds all .md files, and returns their names (without the .md extension). These names are often called “slugs” in web development, as they are part of the URL.
* read_markdown_post(slug): This function takes a slug (e.g., my-first-post), constructs the full file path, reads the content, and then uses markdown.markdown() to convert it into HTML. It also tries to extract a title from the first H1 heading.
* @app.route('/posts/<slug>'): This is a dynamic route. The <slug> part is a variable that Flask captures from the URL. So, if someone visits /posts/my-first-post, Flask will call the post() function with slug='my-first-post'.
* abort(404): If read_markdown_post returns None (meaning the file wasn’t found), we use abort(404) to tell the browser that the page doesn’t exist.
* render_template('post.html', title=title, content=content): Instead of returning raw HTML, we’re now telling Flask to use an HTML template file (post.html) and pass it variables (title and content) that it can display.

Creating HTML Templates

Now we need to create the HTML files that render_template will use. Flask looks for templates in a folder named templates/ by default.

templates/index.html (List of Posts)

This file will display a list of all available blog posts.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Flask Blog</title>
    <style>
        body { font-family: sans-serif; margin: 20px; line-height: 1.6; }
        h1 { color: #333; }
        ul { list-style: none; padding: 0; }
        li { margin-bottom: 10px; }
        a { text-decoration: none; color: #007bff; }
        a:hover { text-decoration: underline; }
    </style>
</head>
<body>
    <h1>Welcome to My Flask Blog!</h1>
    <h2>Recent Posts:</h2>
    {% if post_slugs %}
    <ul>
        {% for slug in post_slugs %}
        <li><a href="/posts/{{ slug }}">{{ slug.replace('-', ' ').title() }}</a></li>
        {% endfor %}
    </ul>
    {% else %}
    <p>No posts yet. Check back soon!</p>
    {% endif %}
</body>
</html>

Explanation of Jinja2 (Templating Language):
* {% if post_slugs %} and {% for slug in post_slugs %}: These are control structures provided by Jinja2, the templating engine Flask uses. They allow us to write logic within our HTML, like checking if a list is empty or looping through items.
* {{ slug }}: This is how you display a variable’s value in Jinja2. Here, slug.replace('-', ' ').title() is a simple way to make the slug look nicer for display (e.g., my-first-post becomes “My First Post”).

templates/post.html (Individual Post View)

This file will display the content of a single blog post.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }} - My Flask Blog</title>
    <style>
        body { font-family: sans-serif; margin: 20px; line-height: 1.6; }
        h1 { color: #333; }
        a { text-decoration: none; color: #007bff; }
        a:hover { text-decoration: underline; }
        .post-content img { max-width: 100%; height: auto; } /* Basic responsive image styling */
    </style>
</head>
<body>
    <nav><a href="/">← Back to Home</a></nav>
    <article class="post-content">
        <h1>{{ title }}</h1>
        {{ content | safe }} {# The 'safe' filter is important here! #}
    </article>
</body>
</html>

Explanation:
* {{ title }}: Displays the title of the post.
* {{ content | safe }}: This displays the HTML content that was generated from Markdown. The | safe filter is crucial here! By default, Jinja2 escapes HTML (converts < to &lt;, > to &gt;) to prevent security vulnerabilities like XSS. However, since we want to display the actual HTML generated from our trusted Markdown, we tell Jinja2 that this content is “safe” to render as raw HTML.

Running Your Complete Blog

  1. Make sure you have app.py, the posts/ folder with my-first-post.md, and the templates/ folder with index.html and post.html all in their correct places within my-flask-blog/.
  2. Ensure your virtual environment is active.
  3. Stop your previous Flask app (if it’s still running) by pressing CTRL+C in the terminal.
  4. Run the updated app:
    bash
    python app.py
  5. Open your browser and visit http://127.0.0.1:5000. You should now see a list of your blog posts.
  6. Click on “My First Post” (or whatever you named your Markdown file) to see the individual post page!

Congratulations! You’ve just built a basic blog using Flask and Markdown!

Next Steps and Further Improvements

This is just the beginning. Here are some ideas to expand your blog:

  • Styling (CSS): Make your blog look prettier by adding more comprehensive CSS to your templates/ (or create a static/ folder for static files like CSS and images).
  • Metadata: Add more information to your Markdown posts (like author, date, tags) by using “front matter” (a block of YAML at the top of the Markdown file) and parse it in app.py.
  • Pagination: If you have many posts, implement pagination to show only a few posts per page.
  • Search Functionality: Allow users to search your posts.
  • Comments: Integrate a third-party commenting system like Disqus.
  • Database: For more complex features (user accounts, true content management), you’d typically integrate a database like SQLite (with Flask-SQLAlchemy).
  • Deployment: Learn how to deploy your Flask app to a real web server so others can see it!

Building this basic blog is an excellent stepping stone into web development. You’ve touched upon routing, templating, handling files, and using external libraries – all fundamental concepts in modern web applications. Keep experimenting and building!


Comments

Leave a Reply