For the first time since 2013, I've rewritten my blog. I'll talk about what's new in a minute, but since this is the first major rewrite of my blog in its seven-plus years of existence, I thought it might be fun to reminisce over its history for a bit.
In the Beginning
After I set this site up in 8th grade, every time I added something new, I would add a little note to the home page of the site. That's where the first three blog posts came from; they were just notes I manually typed into the static home page file. It was good enough for a website that had all of like 5 pages on it.
But even as early as that third post, I already understood that adding text to the home page was not a sustainable way of maintaining what was effectively a changelog. So, since I was still building out this website and needed content for it anyways, the next logical leap was to implement some type of blog system.
You'll see in that third post that I was considering using static pages for the blog. To be clear, this was not a proposal to use a static site generator, as I didn't even know what those were back then. This was me planning to just manually type HTML pages for each blog post and manually update a main page with a list of links to each post.
It's hopefully rather obvious why I ultimately decided against this idea, and instead opted for a more automated system of displaying my blog posts. Which leads us to...
The problem with not having the site checked into version control until 2019 is that I don't have a good record of when things "launched" officially, instead having to use blog posts as a proxy. If we trust that my first post was made in a timely fashion (which to be fair it probably was, as I was likely very excited about getting the blog running), then version 1 launched on November 23rd, 2013.
Most of the code was taken from a tutorial from 2008, meaning it already wasn't a particularly modern tutorial even at the time, so of course my code has aged terribly.
I'll be referencing a post from that December where I released the state of my blog's codebase at the time. This is technically version 2 of my blog, but it's not different enough to be a big deal - more on that in a minute. Also, isn't it cute how I thought not posting on the blog for three whole days was a long gap?
The first thing that stood out to me when I look at my old code is that I'm forced to remember how I exclusively used PascalCase for quite a while. It took me, if I remember correctly, probably years before truly coming around to the clearly superior camelCase, and to this day if you dig around my site, there's still stuff written in PascalCase. In fact, checking my primary stylesheet, uni.css, at the time of this writing the second declaration I see is for a class titled
But that's not even on the list of actual problems. For that, I'd start with the fact that it was built on the old mysql plugin which even in 2013 was not recommended anymore, in favor of the newer mysqli and PDO plugins. On top of that was my complete lack of understanding as to what an object is or how they're supposed to be used. The fact that I just copied and pasted someone else's code meant that I had no idea what this BlogPost class I was copying in meant. The biggest impact is that there was a bunch of features I never truly used, like post tags and author, but it got worse. More on that later.
Still, version 1 wasn't terrible, owing mainly to the fact that it was almost entirely someone else's code. It got worse.
Version 2 (and 2.1)
The version 2 announcement post went up days after the initial launch, on November 28th, 2013. I have to admit, I don't have the strongest recollection of what exactly changed between the two, so I can only go off the rather short announcement post I made.
It seems like v1 was just every blog post in full on the main blog page, and v2 was when each post got its own page, like an actual blog. I said at the time that "it's the same system YouTube uses for their videos," which is really quite dumb because the only thing that my blog has in common with YouTube is that the posts are identified by a unique ID passed in via a URL query parameter. To be fair, this isn't that common; most websites seem to encode that information into the URL endpoint itself instead of as a query parameter, but it's really not that rare and I don't think something that simple warrants a comparison to YouTube.
But whatever. Version 2.1 launched the next month, limiting the main blog page to just 5 posts and introducing the ability to filter by month. And this is where the problems I promised earlier came to a head.
See, the code I copied for the foundation of my blog fundamentally relied on a function called
GetBlogPosts() which selected everything in my table of blog posts and processed the results into an array of the aforementioned BlogPost objects. I used this function as the basis of everything I did with the blog. Needed to display the first five posts on the main page? Call this function and just echo the first five posts. Need to get a list of all the months for the filter list? Call this function and count all the unique months in the returned array. Perhaps you're starting to see the problem.
But oh it gets worse. The constructor inside the BlogPost class needed to figure out the name of the author and the tags associated with a post. These were all stored in separate tables, so this was accomplished by having the constructor itself make two additional SQL queries to gather that information on construction. Per object.
As a result, basically every single operation on my blog required querying for every single post, and then in the loop to process those into objects, making an additional 2n requests into the database. Given that most databases take logarithmic time to lookup an entry, this means that something like loading the first five articles for the main blog page, which really should not be more than O(log n) at the worst, is now magically an O(n log n) operation.
Clearly this doesn't really matter since I only have like 30 posts, but it's kind of disgusting to know that loading just the main blog page was such a bloated operation. What even was the point of only showing the first five posts on that page if the entire table had to be fetched (six times, if I count correctly, since each call to
GetBlogPosts requires 3 queries per entry, and populating the month filter requires making a second call to it)?
None of this even mentions the fact that after filtering by month, I had an entire separate page, powered by a separate PHP file, to show the results of that filter, with almost identical code from the main page (in fact it was copied and pasted before being tweaked). So even more WET (write everything twice) code abound.
After 2013 ended (with one last tweak to only show a few months by default in the month filter) , very little happened. I did lose two blog posts over the years due to not having a backup of the MySQL database I had in production, which is rather sad. The first was from early 2015, called "A Post of Ones." I had some clever running theme throughout the post with the number 1 - it had been one year and one day since my last post, which was the impetus, but there was definitely at least one other "one" that I'm completely drawing a blank on. I made a post about losing that post here. The second time this happened was mid 2018, where I lost my post explaining my initial foray into node.js, where I set up an Azure instance to host a socket.io server for my Bringing Down the Landlord game. Postmortem was here. Both of these were rather nice posts, I thought, and I'm genuinely quite disappointed I didn't have backups for them. These days, every time I make a new blog post, I immediately dump it into the Wayback machine precisely because I don't want to lose these anymore. I guess this is the problem with having the blog be on MySQL instead of static pages checked into version control with the rest of the site; I need to manually backup the database too.
There were four noteworthy changes over the years. First, after the first time I lost a post on my blog detailed above, I removed the tags on each post, because they were useless and I hated having to set them up in the database each time I posted. Note that I still had the same PHP class for each blog post though, so I was still running all those database operations to fetch the now undisplayed tags (as well as the author name, which was always useless since I'm the only one who, by definition, posts on my personal site). Second, when I redesigned the site in 2016, the blog posts obviously were also redesigned. As part of that process, I temporarily removed the month filters because I didn't have a good idea for how to design them. Third, I brought back the month filters (finally) in January 2018 when I finally got serious about cleaning up my site for the first time since starting college. This is also when I removed the blog posts from the home page. Still, none of the backend code had changed. Fourth and finally, when I migrated to Google Cloud in summer 2019 (even though the post about it didn't go up until that winter), I finally moved away from the old, deprecated mysql extension, as the Bitnami LAMP Stack I had deployed was on PHP 7 which no longer has it. I used mysqli as a drop-in replacement, and that's when I first started to notice just how terrible the blog code had become.
So let's fix it, shall we?
My new blog engine is based on two core classes, Blog and Post. I'm releasing the code publicly here.
Blog is a singleton that maintains a database connection and provides methods that wrap targeted queries, such as posts by month, most recent posts, individual post, and even a separate query to just get a list of every month a post was made (since MySQL can
SELECT DISTINCT with a formatted date).
Meanwhile Post is just a simple class representing a single post. Most methods in Blog return an array of Post objects. Very little happens inside Post; most of the methods are convenience methods that I would have had to use in many places, namely printing the date and truncating the sanitized body of the post. Otherwise, all its instance variables are public because it's basically being used as a simple struct. Side note: I do love how simple the PHP 8 syntactic sugar for declaring member variables are.
There is still a hard-coded database password in there; that unfortunately didn't change. After losing two whole posts, I'm just petrified of anything that's not checked into version control, so you'll have to forgive the fact I didn't make those into environment variables. Besides, my database should be set up to only accept connections from localhost anyways, so it shouldn't be a significant security issue.
The other thing is that the check for whether or not we're in production simply checks whether the OS is Windows or not, since I develop locally on a Windows machine. This is of course a terrible environment check, but it consistently works for me (my server runs Linux), and it's simple, so good enough for now.
One last thing: I was interested in perhaps playing with some type of abstraction layer on top of the database, but it doesn't look like PHP comes with anything like that built in, and I really wasn't in the mood to setup a dependency manager, so I'm still writing my own raw SQL queries at the moment. This is also why I didn't switch from mysqli to PDO - if I need to write MySQL queries by myself anyways, then I don't see the point in using a library that works across database types. On the bright side, my thing should be fairly safe, since everything is handled with prepared statements. Also I no longer query for tags or author, since the tags are useless. I just hard-code my name into the byline on the actual blog page. Perhaps I'll need to change that in the future, but frankly I really doubt it.
The month filter is now also the same code as the primary blog page. Since the method in Blog to get the newest posts and the method to get all posts in a month both return an array of Post objects, I can easily just feed that into the same PHP code that generates the list of blog posts, consolidating that into one place, which is much more maintainable.
From the user's perspective, perhaps the most obvious change is the design. I completely redesigned the look of the blog pages, and am generally happy with the result, save for the fact that the filter looks a little derpy on mobile. Also the arrow next to "Continue Reading" doesn't seem to render on my phone even though I've explicitly included a font that has that character, so I don't know what's up with that - when I tested it on my dev server, it worked fine with my phone, so I'm confused. I guess I'll have to figure that out eventually.
More importantly, however, I'm concerned that my current design sensibilities are starting to diverge from my site's main design. My recent works have been filled with much more drop shadows, animated motion, and clear borders, in contrast to my site's design of clean, borderless, solid-color blocks. It was starting to grow noticeable on the Projects page, but it's very clear now looking at the new blog design.
I haven't figured out what I'm going to do about that. The current site design does date back to 2016, but I'm really not sure if I want to overhaul the entire site again. At the same time, having two obviously different design languages does kind of annoy me. This is the problem with not knowing anything about professional design - I have no idea what to do here. Oh well. I'll figure it out eventually.
Either way, that's the new blog. It's fascinating, going back through older blog posts, seeing how my posts have turned from simple change announcements into full (rambly, unedited) essays like this one. Doing a big retrospective is kind of fun; the last time I did something like this was for the 2016 redesign, where I updated what was then the about page to describe the design history. I'd say I should do this more often, but I really shouldn't because it does take a while and I see myself getting easily burnt out taking on something like this more than once every few years. Besides, there's not much on this site that has a history deep enough to warrant this treatment besides the blog and the site itself.
I might still make minor tweaks to the blog going forwards, if I feel inspired, but hopefully I've built a good foundation that should last me a few years. We'll see.
Posted By: Michael Xing