DEV Community

Cover image for Building A Lightweight Blogging CMS In 10 Lines of Code
Steven Tey πŸ‘¨πŸ»β€πŸ’»
Steven Tey πŸ‘¨πŸ»β€πŸ’»

Posted on • Originally published at

Building A Lightweight Blogging CMS In 10 Lines of Code

Ever since launching One Word Domains five months ago, I've been wanting to set up a blog where I could:

  1. Document my build process
  2. Write about some of the coolest programming tips and tricks that I learned in the process (this blog post is one of them)
  3. Share some insights on the domain industry - i.e. what are some of the top naming conventions in Silicon Valley

However, I quickly ran into a dilemma when trying to find a suitable blogging CMS (content management system) for my needs:

  • Wordpress is easy to set up, but is also an overkill - I don't need 15 different subpages and a full-fledged user management system (I'm already using PostgreSQL for that)
  • Ghost is a little more challenging to set up (here's a great guide if you're into that) but would require setting up an extra dyno on Heroku or a VPS on Digital Ocean - which would mean an extra $5 - $7 a month
  • Medium is relatively pain-free to set up, but is pretty limited when it comes to customization + you're not really helping your site's SEO with your posts since you'll be contributing to Medium's SEO instead

What I was looking for was a simple and free static-site solution that was easy to customize + integrates well with my existing stack (Heroku, Flask, PostgreSQL, Python, HTML/CSS, JavaScript, jQuery).

I decided to consult my friend Linus, who recommended the Python-Markdown library - which is the same framework that Pelican (the Python version of Hugo) uses.

Intrigued, I began researching the origins of the Python-Markdown library, and that's when I came across this blog post by James Harding. 10 lines of code later, I've successfully set up my very own Markdown-powered static site for the One Word Domains Blog.

Here's how everything went down, step by step:


First, I imported the Flask-FlatPages and Markdown libraries:

import markdown
from flask_flatpages import FlatPages
Enter fullscreen mode Exit fullscreen mode

...and declared them in my requirements.txt file:

Enter fullscreen mode Exit fullscreen mode

Folder Structure

Since I already had an existing Flask app up and running, all I did next was add a /posts folder at the root directory, a separate folder called blog-images under the /static/assets folder, and a few template files in the /templates folder. Here's a rough overview on how my folders were structured:

  β”‚       └──
  β”‚       └──
  β”‚       └──blog.html
  β”‚       └──post.html
        β”‚     └──blog-images
Enter fullscreen mode Exit fullscreen mode

Define FlatPages ENV Variables

Before I started setting up the Flask routes for my blog, I defined the ENV variables for FlatPages in my file, right after initiating the Flask app:

POST_DIR = 'posts'

flatpages = FlatPages(app)
Enter fullscreen mode Exit fullscreen mode

Here, I defined FLATPAGES_ROOT as '' because the folder containing all my markdown files, posts, is located in the root directory – which is why POST_DIR is defined as 'post'.

Flask Routes

Here are the 10 lines of code that I mentioned earlier – which I inserted into my file:

def blog():
    posts = [p for p in flatpages if p.path.startswith('posts')]
    posts.sort(key=lambda item:dt.strptime(item['date'], "%B %d, %Y"), reverse=True)
    return render_template("blog.html", posts=posts)

def blog_post(permalink):
    path = '{}/{}'.format('posts', permalink)
    post = flatpages.get_or_404(path)
    return render_template('post.html', post=post)
Enter fullscreen mode Exit fullscreen mode

I know, I couldn't believe it either.

10 lines of Python code was all I needed to get the One Word Domains Blog up and running.

Let's dive deeper into the lines of code above and see what each one of them does:

  • The first route, /blog hosts the landing page of the blog. Here, the code iterates across all the Markdown files present in the /posts folder and interprets them in the form of a flatpages object. It then sorts them in descending order by published date – here, I'm using the dt.strptime() method because my dates are written in natural language format (October 30, 2020). Lastly, the code renders the blog.html template and sends over all the posts as jinja variables.
  • The second route, /blog/<permalink> takes care of the individual blog posts. The first line of code creates the composite path for each of the Markdown files, which is in the format /posts/ It then gets the files with the flatpages module and renders the post.html template along with all the attributes of the particular blog post.

Markdown Format

Let's take a look at the format of a given Markdown file, say, the one for this blog post, for example:

title: Building A Lightweight Blogging CMS In 10 Lines of Code
subtitle: This is the full story of how The One Word Domains blog was built - with 10 lines of Python code, the Flask-Flatpages library, and a bunch of Markdown files.
date: November 2, 2020
image: post2-thumbnail.png
permalink: markdown-flask-lightweight-cms

Ever since launching One Word Domains five months ago... (content)
Enter fullscreen mode Exit fullscreen mode

As you can see, each Markdown file has the following attributes:

  • title: The title of the blog post
  • subtitle: The subtitle, or 'tagline' of a blog post, usually written to give more context on the post
  • date: The date the blog post was published
  • image: The thumbnail image for the blog post, stored within the /static/assets/blog-images folder that I mentioned earlier
  • permalink: The canonical URL for the blog post. Protip: try and use dashes and keep this below 74 characters so that it doesn't get truncated in the search results
  • content, or html: The bulk of the blog post's content

HTML Templates

Here's a rough outline of my blog.html template:

{% for post in posts %}
<a href="/blog/{{ post.permalink }}">
   <img src="/static/assets/blog-images/{{ post.image }}"/>
   <h1>{{ post.title }}</h1> 
   <p>{{ }}</p>
   <p>{{ post.subtitle }}</p>
{% endfor %}
Enter fullscreen mode Exit fullscreen mode

This code will iterate across all the Markdown files in the /posts folder that I set up earlier and auto-generate previews for each and every one of them.

And here's the one for my post.html file:

<img src="/static/assets/blog-images/{{ post.image }}"/>
<h1>{{ post.title }}</h1> 
<p>{{ }}</p>
{{ post.html|safe }}
Enter fullscreen mode Exit fullscreen mode

Compile and Run

If everything went well, your blog should be live at once you run $ python in your terminal. Yay!

Or, if you're like me and you run into a bunch of errors in your first few attempts - don't give up! Debug your code by pasting the error messages into Google and clicking on the first Stackoverflow post that pops up.

Good luck!

Bonus: Typora

I first started editing my Markdown files in Sublime, which was rather mechanical and cumbersome.

Then, everything changed when the fire nation attacked I discovered this free tool, Typora (or at least, "free during beta", as stated on their site). The intuitive and seamless writing experience that Typora provides is unparalleled, and while **this is not an ad, I highly recommend trying it out.

Top comments (8)

gravesli profile image
gravesli • Edited

I think you are great! I built a display machine state using Python3 with Flask!
Flask State .
Should i can get some improvement suggestions from you? Thanks~

steventey profile image
Steven Tey πŸ‘¨πŸ»β€πŸ’»

That's really sick, great job!

gravesli profile image

Hi, thanks for your reply. Would you give me a star on GitHub? because my project isn't active. ^.^

Thread Thread
steventey profile image
Steven Tey πŸ‘¨πŸ»β€πŸ’»

Okay, will do! :)

wannabehexagon profile image
ItsThatHexagonGuy • Edited

You should try stackedit too! It's a pretty cool markdown editor as well. Also hashnode might've been a decent fit for you I'd say

steventey profile image
Steven Tey πŸ‘¨πŸ»β€πŸ’»

Hey, thanks a lot for the suggestion, Shenesh! I'll definitely look into Stackedit! ☺️

And yeah, I've been using hashnode for a while but I was looking for a more customizable solution for this project πŸ˜…

denpetrov profile image

Thank you for the post.

I created a simple example:

steventey profile image
Steven Tey πŸ‘¨πŸ»β€πŸ’»

This is amazing, thanks for sharing this with me! Just starred your repo! :)